Service Worker Lifecycle
The service worker lifecycle
Section titled “The service worker lifecycle”Every service worker goes through a strictly defined state machine managed by the browser. Understanding this lifecycle is essential — skip it and your updates silently fail to activate, or you break pages that are still running the old worker.
State machine
Section titled “State machine”stateDiagram-v2 [*] --> Parsed : browser parses sw.js Parsed --> Installing : install event fires Installing --> Installed : installEvent.waitUntil() resolves Installing --> Redundant : waitUntil() rejects Installed --> Activating : no old SW active, or skipWaiting() Installed --> Waiting : old SW still controlling pages Waiting --> Activating : all old-SW tabs closed, or skipWaiting() Activating --> Activated : activateEvent.waitUntil() resolves Activating --> Redundant : waitUntil() rejects Activated --> Redundant : replaced by a newer SW Waiting --> Redundant : replaced by a newer SW
The install event
Section titled “The install event”The install event fires the moment the browser has parsed and downloaded your service worker file. This is the ideal time to pre-cache the resources your app needs for offline use.
const CACHE_NAME = 'v1';const PRECACHE_URLS = ['/', '/styles.css', '/app.js'];
self.addEventListener('install', (event) => { event.waitUntil( caches.open(CACHE_NAME).then((cache) => cache.addAll(PRECACHE_URLS)) );});event.waitUntil() accepts a Promise. If the Promise resolves, the SW moves to the Installed state. If it rejects (e.g., a network error while pre-caching), the SW moves to Redundant and is discarded — nothing breaks for the user; the old SW keeps running.
The waiting state
Section titled “The waiting state”After installation, the new SW sits in Waiting if any tab is still controlled by the previous version. The browser will not activate the new SW until every tab running the old one is closed (or navigated away from).
To skip the waiting period programmatically, call self.skipWaiting() inside the install handler:
self.addEventListener('install', (event) => { event.waitUntil( caches.open('v1').then((cache) => cache.addAll(['/'])).then(() => self.skipWaiting()) );});The activate event
Section titled “The activate event”Once the old SW is gone (or skipWaiting() was called), the new SW fires its activate event. This is the right time to clean up old caches so stale assets do not consume storage indefinitely.
const CURRENT_CACHES = ['v2'];
self.addEventListener('activate', (event) => { event.waitUntil( caches.keys().then((names) => Promise.all( names .filter((name) => !CURRENT_CACHES.includes(name)) .map((name) => caches.delete(name)) ) ).then(() => self.clients.claim()) );});self.clients.claim() makes the newly activated SW take control of all open pages immediately, without waiting for the next navigation.
Lifecycle summary
Section titled “Lifecycle summary”| State | What happens | What you do |
|---|---|---|
| Installing | SW is being set up | Pre-cache assets in install |
| Waiting | New SW is ready but held back | Optionally call skipWaiting() |
| Activating | New SW is taking over | Delete old caches in activate |
| Activated | SW is in full control | Serve requests via fetch event |
| Redundant | SW was replaced or failed | Nothing — browser disposes of it |