Skip to content

Service Worker Activate Event — Cleaning Up Old Caches

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you will learn about Service Worker Activate Event. We cover key concepts, practical examples, and best practices to help you master this topic.

The activate event cleans up outdated caches and claims control of pages, ensuring your PWA uses fresh resources and discards stale data from previous versions after a service worker update.

What You'll Learn

By the end of this tutorial, you will understand how the activate event works, how to clean old caches safely, how to claim clients, and how to handle data Migration between versions.

Why It Matters

Without proper activation handling, old caches accumulate indefinitely on user devices. Users can end up with hundreds of megabytes of stale data from previous versions of your PWA. The activate event is your cleanup crew.

Real-World Use

An e-commerce PWA updated weekly with new product images. Without activate cache cleanup, users accumulated 150MB of old product images. Adding cache pruning during activation reduced storage to under 20MB per user.

Activate Event Flow

Activate Event Flow
    New service worker installed
         ↓
    Previous worker releases control
         ↓
    Activate event fires
         ↓
    List all cache names
         ↓
    ╔════════════════════════════════════╗
    ║ Compare each against current cache ║
    ╚════════════════════════════════════╝
         ↓
    ┌─────────────────┐
    │  Old cache found │ → Delete it
    └─────────────────┘
         ↓
    Claim all clients (pages)
         ↓
    New worker controls all pages

Think of the activate event like moving to a new apartment. The install event packed your boxes (cached resources). The activate event is when you throw away old items you no longer need (old caches) and tell everyone your new address (claim clients).

Basic Activate Handler

const CACHE_NAME = 'my-pwa-v2';

self.addEventListener('activate', event => {
    console.log('Activate: Starting cleanup');

    event.waitUntil(
        caches.keys().then(cacheNames => {
            return Promise.all(
                cacheNames.map(cacheName => {
                    if (cacheName !== CACHE_NAME) {
                        console.log(`Activate: Deleting ${cacheName}`);
                        return caches.delete(cacheName);
                    }
                })
            );
        }).then(() => {
            console.log('Activate: Cleanup complete, claiming clients');
            return self.clients.claim();
        })
    );
});

Output:

Activate: Starting cleanup
Activate: Deleting my-pwa-v1
Activate: Deleting my-pwa-v0
Activate: Cleanup complete, claiming clients

Sophisticated Cache Pruning

For complex scenarios, you might want to keep multiple cache versions or implement a white-list approach:

const CURRENT_CACHES = {
    static: 'static-v3',
    dynamic: 'dynamic-v2',
    images: 'images-v1'
};

self.addEventListener('activate', event => {
    const expectedCaches = Object.values(CURRENT_CACHES);

    event.waitUntil(
        caches.keys().then(cacheNames => {
            return Promise.all(
                cacheNames.map(cacheName => {
                    // Check if this cache name is one we expect
                    if (!expectedCaches.includes(cacheName)) {
                        console.log(`Deleting unexpected cache: ${cacheName}`);
                        return caches.delete(cacheName);
                    }
                })
            );
        }).then(() => {
            console.log('All caches validated, claiming clients');
            return self.clients.claim();
        })
    );
});

This approach explicitly lists the caches your current version expects. Any cache not in the list is deleted. This is safer than comparing against a single name because it handles multiple cache types.

Claiming Clients

self.clients.claim() takes control of all uncontrolled pages. Without it, pages that loaded before activation continue using the old service worker or no service worker at all.

// Understanding clients.claim()
self.addEventListener('activate', event => {
    event.waitUntil(
        // Claim all pages immediately
        self.clients.claim().then(() => {
            console.log('Now controlling all open pages');

            // Optionally notify pages about the update
            return self.clients.matchAll().then(clients => {
                clients.forEach(client => {
                    client.postMessage({
                        type: 'SW_UPDATED',
                        version: CACHE_NAME
                    });
                });
            });
        })
    );
});

// In the page JavaScript, listen for messages
navigator.serviceWorker.addEventListener('message', event => {
    if (event.data.type === 'SW_UPDATED') {
        console.log('Service worker updated to:', event.data.version);
    }
});

Output:

Now controlling all open pages
Service worker updated to: my-pwa-v2

Handling Data Migration

When your cache schema changes between versions, the activate event is the right place to migrate data:

// Migration example: restructured cache layout
const OLD_CACHE = 'my-pwa-v2';
const NEW_CACHE = 'my-pwa-v3';

self.addEventListener('activate', event => {
    event.waitUntil(
        // Open old cache and migrate entries
        caches.open(OLD_CACHE).then(oldCache => {
            return oldCache.keys().then(requests => {
                return caches.open(NEW_CACHE).then(newCache => {
                    const migrationPromises = requests.map(request => {
                        return oldCache.match(request).then(response => {
                            // Optionally transform response before storing
                            if (response && response.ok) {
                                return newCache.put(request, response);
                            }
                        });
                    });
                    return Promise.all(migrationPromises);
                });
            });
        }).then(() => {
            // Delete old cache after migration
            return caches.delete(OLD_CACHE);
        }).then(() => {
            return self.clients.claim();
        })
    );
});

Cleanup Verification

After activation, you can verify cleanup worked:

// In DevTools console
caches.keys().then(keys => {
    console.log('Current caches:', keys);
    keys.forEach(key => {
        console.log(` - ${key}`);
    });
});

// Should only show active caches, old ones should be gone

Output:

Current caches:
 - static-v3
 - dynamic-v2
 - images-v1

Common Mistakes

  1. Not deleting old caches. Caches persist until explicitly deleted. Unused caches waste storage and may serve stale content if a cache name matches by accident.
  2. Calling clients.claim() before cleanup. Claim clients after cache cleanup to ensure pages get the clean cache state immediately.
  3. Hardcoding cache names. Use a cache name Strategy (prefix + version) rather than hardcoding. This makes automated cleanup easier.
  4. Forgetting to handle unexpected caches. Other scripts or browser extensions may create caches. Use an allowlist approach rather than deleting everything except your known cache.
  5. Not waiting for cleanup promises. The activate event terminates if you do not use event.waitUntil(). Always wrap async operations in waitUntil.

Practice Questions

  1. What is the primary purpose of the activate event?
  2. Why should you call clients.claim() during activation?
  3. How do you safely delete old caches without accidentally deleting caches from other applications?
  4. What happens if you do not use event.waitUntil() in the activate handler?
  5. When would you need to migrate data between cache versions?

Challenge: Write an activate handler that manages three cache types (static, dynamic, images). Use a white-list approach where only the current version of each cache type is kept. Delete all other caches. Verify by creating test caches and checking they are removed.

FAQ

Can the activate event fail?

Yes, if any promise passed to event.waitUntil() rejects, the activation fails and the service worker is discarded. Always add .catch() to cleanup operations.

What happens if I do not call clients.claim()?

Pages loaded before activation continue using the old service worker. The new worker controls only pages loaded after activation. Users must refresh to get the update.

Can I skip the activate event entirely?

The activate event always fires. You can leave the handler empty, but old caches will never be cleaned and clients will not be claimed.

How do I know which caches belong to my app?

Use a consistent naming convention like appname-cachetype-version. This makes it easy to identify and clean your caches without affecting other applications.

Does clients.claim() affect all tabs or just my app?

It claims only pages within the service worker's scope. Other origins and out-of-scope pages are not affected.

Mini Project

Create a service worker with three cache versions: static-v1, images-v1, dynamic-v1. Write an activate handler that deletes only caches not in your current white-list. Test by programmatically creating old caches (caches.open('old-cache')) and verifying they are deleted on activation. Log all cache operations.

What's Next

Your service worker lifecycle is complete. Now learn how the fetch event intercepts network requests and implements Caching strategies.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro