Service Worker Lifecycle — Install, Activate, and Fetch Explained
In this tutorial, you will learn about Service Worker Lifecycle. We cover key concepts, practical examples, and best practices to help you master this topic.
The service worker lifecycle has three phases: install (pre-cache resources), activate (clean old caches), and fetch (intercept network requests) — each with specific responsibilities that control how your PWA handles offline and caching.
What You'll Learn
By the end of this tutorial, you will understand the complete service worker lifecycle, how to handle each lifecycle event, and how to manage service worker updates without breaking your PWA.
Why It Matters
The service worker lifecycle determines when your app caches resources, when old caches are cleaned, and how updates are applied. Misunderstanding the lifecycle leads to stale content, failed caching, and user confusion when updates do not appear.
Real-World Use
A news PWA that cached articles during install but never cleaned old caches filled users' storage with 200MB of stale data. Implementing the activate event to prune old caches solved the problem and reduced storage usage by 80%.
Lifecycle Overview
Service Worker Lifecycle
Registration
↓
┌─────────────┐
│ Installing │ ← Install event fires
└──────┬──────┘
↓ (success)
┌─────────────┐
│ Installed │ ← Waiting (not yet active)
└──────┬──────┘
↓ (no active worker or skipWaiting())
┌─────────────┐
│ Activating │ ← Activate event fires
└──────┬──────┘
↓ (success)
┌─────────────┐
│ Activated │ ← Controlling pages, fetch events fire
└──────┬──────┘
↓ (new version detected)
┌─────────────┐
│ Updating │ ← New install, old waits
└──────┬──────┘
↓
┌─────────────┐
│ Redundant │ ← Old worker retired
└─────────────┘
Think of the service worker lifecycle like a shift change at a Factory. The new worker arrives (install), waits until the current shift finishes (waiting), takes over (activate), and handles all new requests (fetch). The old worker leaves when the new one is fully in control.
Registration
Before the lifecycle begins, you must register the service worker from your web page:
// Register service worker from your main JavaScript file
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js')
.then(registration => {
console.log('SW registered:', registration.scope);
console.log('State:', registration.active ? 'active' : 'installing');
})
.catch(error => {
console.log('SW registration failed:', error);
});
});
}
Output:
SW registered: https://example.com/
State: installing
Register the service worker after the page loads to avoid delaying the initial render. The load event ensures the page is fully rendered before downloading the service worker script.
Install Event
The install event fires once when the service worker is first registered or when a new version is detected. This is where you pre-cache critical resources your app needs to work offline.
// sw.js — Install event
const CACHE_NAME = 'my-pwa-v1';
const PRECACHE_URLS = [
'/',
'/index.html',
'/styles/main.css',
'/scripts/app.js',
'/images/logo.png',
'/offline.html'
];
self.addEventListener('install', event => {
console.log('SW: Install event fired');
// Pre-cache critical resources
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => {
console.log('SW: Caching app shell resources');
return cache.addAll(PRECACHE_URLS);
})
.then(() => {
console.log('SW: Installation complete');
// Force the waiting service worker to become active
return self.skipWaiting();
})
);
});
Output:
SW: Install event fired
SW: Caching app shell resources
SW: Installation complete
event.waitUntil() tells the browser not to terminate the service worker until the promise resolves. If the promise rejects, the installation fails and the service worker is discarded.
skipWaiting() forces the newly installed service worker to skip the waiting phase and activate immediately. Without it, the new worker waits until all pages using the old worker are closed.
Activate Event
The activate event fires after the service worker is installed and ready to take control. Use this event to clean up old caches and migrate data.
// sw.js — Activate event
self.addEventListener('activate', event => {
console.log('SW: Activate event fired');
const currentCacheName = CACHE_NAME;
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
// Delete old cache versions
if (cacheName !== currentCacheName) {
console.log('SW: Deleting old cache:', cacheName);
return caches.delete(cacheName);
}
})
);
}).then(() => {
console.log('SW: Activation complete, claiming clients');
// Start controlling all open pages immediately
return self.clients.claim();
})
);
});
Output:
SW: Activate event fired
SW: Deleting old cache: my-pwa-v0
SW: Activation complete, claiming clients
self.clients.claim() takes control of all open pages immediately. Without it, pages loaded before the new service worker activates continue using the old worker.
Fetch Event
After activation, the service worker intercepts all network requests from pages it controls:
// sw.js — Fetch event
self.addEventListener('fetch', event => {
console.log('SW: Fetch intercepted:', event.request.url);
event.respondWith(
caches.match(event.request)
.then(cachedResponse => {
// Return cached response if available
if (cachedResponse) {
console.log('SW: Serving from cache:', event.request.url);
return cachedResponse;
}
// Otherwise fetch from network
console.log('SW: Fetching from network:', event.request.url);
return fetch(event.request)
.then(response => {
// Optionally cache the response for future
return response;
})
.catch(error => {
console.log('SW: Network request failed:', error);
// Return offline fallback
return caches.match('/offline.html');
});
})
);
});
Output:
SW: Fetch intercepted: https://example.com/styles/main.css
SW: Serving from cache: https://example.com/styles/main.css
SW: Fetch intercepted: https://example.com/api/data
SW: Fetching from network: https://example.com/api/data
Update Flow
When you deploy a new service worker with changes, the browser detects the byte difference and runs the new worker through the lifecycle again:
- New worker installs (install event fires)
- New worker enters waiting state if pages are still controlled by the old worker
- When all pages close or
skipWaiting()is called, the old worker is retired - New worker activates (activate event fires)
- New worker takes control of all pages
// Listen for new service worker installation
navigator.serviceWorker.register('/sw.js').then(registration => {
// Check if there's a waiting worker (update available)
if (registration.waiting) {
console.log('Update available, waiting worker exists');
// Notify user about update
}
// Listen for new worker installations
registration.addEventListener('updatefound', () => {
const newWorker = registration.installing;
console.log('New service worker detected');
newWorker.addEventListener('statechange', () => {
console.log('Worker state:', newWorker.state);
if (newWorker.state === 'installed') {
console.log('New service worker installed');
}
});
});
});
Output:
New service worker detected
Worker state: installing
Worker state: installed
Worker state: activating
Worker state: activated
Common Mistakes
- Not calling skipWaiting(). Without skipWaiting, the new service worker waits until all tabs are closed. Users must close and reopen the app to get updates.
- Not cleaning old caches during activate. Old caches accumulate indefinitely, filling user storage. Always delete caches from previous versions.
- Registering every page load. Register the service worker once. Multiple registration calls are safe (the browser ignores duplicates), but they add unnecessary checks.
- Assuming immediate control after install. The service worker does not control the page that registered it until the next page load or after clients.claim().
- Forgetting error handling in waitUntil. If the promise passed to waitUntil rejects, the installation fails. Always handle errors with .catch().
Practice Questions
- What are the three main lifecycle events in a service worker?
- Why should you register the service worker after the window load event?
- What does skipWaiting() do and why is it important?
- Why should you clean old caches during the activate event?
- What happens if the install event promise rejects?
Challenge: Write a service worker that pre-caches 5 URLs during install, cleans old caches during activate, and logs every intercepted fetch URL. Test it on a local page and verify all three lifecycle events fire correctly.
FAQ
Mini Project
Create a service worker that pre-caches a simple app shell (index.html, style.css, app.js, and a logo image). Implement install, activate (with cache cleanup), and fetch events. Register it from an HTML page and verify using Chrome DevToolsk "DevTools" >}} → Application → Service Workers panel.
What's Next
You understand the lifecycle. Now dive deeper into the install event and learn how to pre-cache resources effectively.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro