ESC

Type to search the knowledge base.

Service Workers Overview

SW lifecycle install/activate/fetch, scope, update story, and what belongs in a service worker vs the page.

advanced3 min read
  • browser
  • service-worker
  • offline
  • pwa
  • cache

A service worker is an event-driven worker that sits between pages and the network for a given scope. It enables offline shells, push, background sync patterns, and custom caching — and it can also brick deploys if you cache HTML forever incorrectly.

Docs: MDN Service Worker API, web.dev Service workers.

Lifecycle

register → install → waiting → activate → controls clients
              ↓
           fetch / push / sync events
// page
if ('serviceWorker' in navigator) {
  await navigator.serviceWorker.register('/sw.js', { scope: '/' });
}
// sw.js
self.addEventListener('install', (event) => {
  event.waitUntil(precache());
  self.skipWaiting(); // careful: may take over early
});

self.addEventListener('activate', (event) => {
  event.waitUntil(cleanupOldCaches().then(() => self.clients.claim()));
});

self.addEventListener('fetch', (event) => {
  event.respondWith(handle(event.request));
});

Scope and HTTPS

SW scripts must be served from HTTPS (or localhost), under the path rules for scope. A worker at /app/sw.js can’t control / unless scope allows. Max scope is limited by Service-Worker-Allowed header when needed.

Updates

Browsers re-check sw.js (byte-different) on navigations. New worker installs, then waits until old clients release — unless skipWaiting + clients.claim. UX pattern: prompt “Update available” then registration.waiting.postMessage('SKIP_WAITING').

Fetch strategies

Use Cache API deliberately — Cache Storage API:

  • Cache-first for hashed static assets
  • Network-first for HTML
  • Never cache opaque personalized API responses without keying

What belongs in SW vs page

SW Page
Offline asset routing Complex UI state
Push event handling Rendering
Precache lists User gestures
Cross-tab coordination (limited) DOM

SW has no DOM. Communicate via postMessage / MessageChannel.

Footguns

  1. Caching index.html with long TTL → stuck clients.
  2. Forgetting to version caches.
  3. Intercepting POST accidentally.
  4. Aggressive skipWaiting causing mixed asset versions mid-session.
  5. Assuming SW runs forever — it stops; design restart-safe handlers.

Interview out-loud

“Service workers intercept fetch for a scope after install/activate. I precache fingerprinted assets, network-first HTML, version caches, and use skipWaiting carefully with an update UX. HTTPS-only, no DOM.”

Kill switch

Always keep a way to unregister broken workers (support article + admin flag that serves a no-op SW or Clear-Site-Data: "storage"). A bad cache strategy can brick clients until you ship a fixed worker — plan the recovery path before the incident.

Update UX copy

When a new worker waits, show a non-blocking toast: “Update available — refresh to apply.” On confirm, postMessage skipWaiting and reload. Avoid forced reloads mid-checkout. Test the waiting state by deploying twice quickly and keeping an old tab open — this is where mixed asset versions appear if skipWaiting is automatic.

Further depth

Teams often under-invest in this topic until an incident or CWV regression. Schedule a one-hour drill: reproduce the failure mode in DevTools, list the top three mitigations for your stack, and file tickets with owners. Revisit after the next major feature that touches networking, rendering, auth, or third parties — those are the moments regressions land. Keep primary documentation links in the runbook so on-call is not searching chat history at 2am.

Concrete artifacts to leave behind: a short architecture note, a CI assertion or header snapshot, and a dashboard panel (lab or field) that would have caught the last bug. Teaching the rest of the team the mental model matters as much as the one-line fix.

Further reading

Related guides