A service worker is an event-driven script that the browser runs in the background, separate from any page, and that sits between a web application and the network as a programmable proxy. In browser JavaScript, it intercepts fetch requests from the pages it controls and decides whether to answer them from a cache, from the network or from generated content.
Service workers are the technical foundation of offline-capable web applications, background synchronization and push notifications. They require a secure context (HTTPS), except on localhost, and they have no access to the DOM, so they communicate with pages only through messages.
How a service worker intercepts the network
A page registers a service worker with navigator.serviceWorker.register('/sw.js'). The browser downloads the script, runs it in its own thread and associates it with a scope, by default the directory where the script lives. Every page whose URL falls inside that scope becomes a controlled client once the worker is active.
When a controlled page requests a resource, the browser dispatches a fetch event to the worker instead of going straight to the network. The handler can call event.respondWith() with any Promise that resolves to a Response. If it does not respond, the request continues to the network as usual, which makes the worker transparent by default.
The worker is not permanently running. The browser starts it when an event arrives and terminates it after a short idle period, typically tens of seconds, which means global variables do not persist between events. State must live in the Cache API or IndexedDB.
The service worker lifecycle
The lifecycle is designed so that only one version controls pages at a time. A new script never takes over pages that an older version is serving unless the developer asks for it explicitly, which prevents a page from loading half its assets with old logic and half with new.
| Phase | Trigger | Typical work |
|---|---|---|
| Registration | register() call from a page | Browser fetches and parses the script |
| Install | First successful download of a new version | Precache the application shell |
| Waiting | Older version still controls open pages | Nothing until those pages close |
| Activate | No clients use the old version | Delete outdated caches, migrate storage |
| Idle and fetch | Requests and messages from clients | Serve responses, handle events |
| Redundant | Replaced or install failed | Worker is discarded |
The browser checks for updates on navigation and at least every 24 hours, comparing the script byte by byte. Calling self.skipWaiting() during install and clients.claim() during activate shortens the waiting phase, but it can mix versions in already open tabs, so it should be used only when old and new caches are compatible.
Caching strategies a service worker implements
The service worker itself does not cache anything automatically. The developer chooses a caching strategy per request type, and the choice determines freshness, speed and offline behavior.
| Strategy | Behavior | Best suited for |
|---|---|---|
| Cache first | Use the cache, fall back to network | Versioned static assets and fonts |
| Network first | Use the network, fall back to cache | HTML pages and frequently changing data |
| Stale while revalidate | Serve cache now, update it in the background | Avatars, non-critical API data |
| Network only | Never cache | Payments, analytics, authenticated mutations |
| Cache only | Never touch the network | Precached application shell |
Network first should include a timeout of a few seconds before falling back to the cache; otherwise a slow connection, often called lie-fi, leaves the user waiting longer than a true offline state would. Cache storage is subject to browser quotas that depend on available disk space, and the browser may evict it under storage pressure unless persistent storage is granted.
Service worker compared with related browser technologies
A service worker is a specialized kind of worker. It shares the off-main-thread execution model of a web worker but differs sharply in lifetime, scope and purpose. It also builds on the Fetch API and on Promise-based async/await code throughout.
| Technology | Lifetime | Main purpose |
|---|---|---|
| Service worker | Event-driven, outlives pages | Network proxy, offline, push, sync |
| Web worker | Tied to the page that created it | Heavy computation off the main thread |
| Shared worker | Lives while any connected page is open | Shared state between tabs of one origin |
| HTTP cache | Managed by the browser | Automatic reuse based on headers |
| Fetch API | Per request | Making requests from any script |
The HTTP cache still works underneath a service worker. A request that the worker forwards with fetch() can be satisfied by the HTTP cache, so header policy and worker strategy must be designed together to avoid serving a resource that is stale in both layers.
Pitfalls and debugging
Most service worker incidents come from stale code that refuses to leave. Because the worker controls pages before they load, a buggy version can keep serving broken assets until it is replaced, which is why a kill switch strategy should exist before the first deployment.
- Caching the worker script itself: serve sw.js with Cache-Control no-cache so updates are detected.
- Unversioned cache names: activate should delete old caches by name, or storage grows indefinitely.
- Too wide a scope: a worker at the site root controls every path, including unrelated sections.
- Opaque responses: cross-origin responses without CORS count heavily against storage quota.
- Forgetting `event.waitUntil()`: the browser may terminate the worker before asynchronous work finishes.
Browser developer tools expose an Application panel where developers can bypass the worker for the network, force updates, unregister workers and inspect cache contents, which turns most of these issues into quick diagnoses.
The short version
A service worker is like a personal assistant standing at the door of a website. Every time the page asks for a file, the assistant decides whether to fetch it from the internet or hand over a copy it saved earlier.
Because the assistant keeps those copies, the site can still open without a connection, and it often opens faster. The assistant also receives messages while the page is closed, which is how websites can show notifications.
Where it makes a difference
A field inspection team uses a web application on tablets in warehouses where mobile coverage drops out regularly. Before the change, a lost signal meant a blank error page and forms that lost their data.
The developers add a service worker that precaches the application shell of roughly 30 files during install, uses network first with a three-second timeout for inspection lists and stores unsent form submissions in IndexedDB. A background sync event replays those submissions when the connection returns.
Inspectors now open the application instantly even in dead zones, complete their checklists offline and see entries upload automatically later. The outcome is no lost inspections and noticeably faster repeat loads, because the shell is served from local cache instead of the network.