JavaScript Service Worker Table
| Piece | What it does | Field note |
|---|---|---|
navigator.serviceWorker.register(sw.js) | Install the proxy | HTTPS only; scope = folder of the sw.js path |
install event | Pre-cache the shell | waitUntil() keeps install alive until caching finishes |
activate event | Clean old caches | Delete stale cache versions HERE - the version GC slot |
fetch event | Intercept requests | respondWith() answers from cache or network - your routing table |
Cache-first / network-first | The two base strategies | Cache-first: speed; network-first: freshness - pick per resource |
skipWaiting + clients.claim | Activate NOW | Faster rollout, riskier - two sw versions can serve mixed pages |
updatefound / update flow | The update dance | New sw installs in background, waits until old tabs close (default) |
Cache Storage | Request/Response pairs | NOT localStorage - versioned, fetchable, the offline backbone |
A service worker is a programmable network proxy that lives in the browser: registered once (HTTPS only), it intercepts every fetch from its scope and answers with Cache Storage, the network, or a blend of both. Offline mode, instant repeat loads and custom caching are all the same mechanism.
Bottom line: the lifecycle is the hard part - install pre-caches the shell (waitUntil keeps it alive until done), activate deletes stale cache versions (the version GC slot), and a NEW worker WAITS until old tabs close unless you call skipWaiting. Skipping the wait means two worker versions can serve mixed pages - the classic service worker footgun.
The honest part: a broken service worker is sticky. Because it sits between your site and the network, a bad caching strategy keeps serving stale content to returning visitors even after you fix the code - which is why cache versioning plus activate-time deletion is not optional hygiene, it is the escape hatch.
How to use
- Version every cache: const CACHE = 'app-v12' - on activate, delete every cache whose name does not start with app-v12; old versions never survive a deploy.
- Pick strategies per resource: cache-first for hashed assets (immutable), network-first for HTML and API JSON (freshness), stale-while-revalidate for the middle ground.
- Roll out updates politely: updatefound notifies the user, the new worker waits, and activation happens on user consent - never surprise-refresh a form mid-edit.
Frequently asked questions
What exactly is the service worker's scope, and why does it matter?
Scope is the folder of the sw.js URL: a worker at /sw.js controls the whole origin; one at /js/sw.js controls only /js/**. Every page request is intercepted only if it falls inside the scope, and a page can only register workers whose scope covers it (the Service-Worker-Allowed header can widen this). Practical consequences: register from the root, and remember the scope cannot be changed after registration - a rename means unregister plus re-register, which is why the file lives at the root in almost every production setup.
Why does my updated site show old content to returning users?
The sticky-worker problem. The old service worker is still active (it waits for old tabs to close by default) and its fetch handler keeps serving its cached responses - so your deploy lands, but returning visitors get the cached shell with references to old, possibly deleted, asset files. The escape hatches, in order of safety: version caches and delete old ones in activate (so the new worker, once active, cannot serve dead versions), call skipWaiting plus clients.claim for immediate activation (accepting the mixed-version risk), and always keep a self-destruct path (unregister + clear caches) for emergencies. The deeper fix: never cache HTML that references hashed assets you also cache, unless versions move together.
How do cache-first and network-first actually differ in behavior?
Priority of truth. Cache-first answers every request from Cache Storage and only falls back to the network on a miss - instant and offline-friendly, correct for content-addressed assets (app-v12.js never changes meaning), poison for anything that changes (the API response from last March). Network-first tries the network and falls back to the cache when offline - fresh when online, functional when not, at the cost of latency. Stale-while-revalidate is the hybrid: serve cached immediately, fetch fresh in the background for next time. Assign strategies per resource type - the routing table IS the service worker's intelligence.
What can a service worker NOT do?
It has no DOM access (like dedicated workers - it cannot touch the page), and its lifetime is event-driven: the browser may terminate it whenever idle, so in-memory globals do NOT persist between events - state lives in Cache Storage, IndexedDB or messages. It cannot intercept requests from other origins' pages, and it will not intercept range requests for media streaming well (browsers bypass the worker for video ranges in many cases). Updates are also rate-limited: the browser re-checks the worker script on navigation (bypassing HTTP caching for up to 24 hours by spec), so worker updates are near-real-time even if the pages it serves are cached.