What Is a Service Worker? Offline Web Apps Explained
A service worker is a script that runs separately from your page, intercepting network requests to enable offline access, caching, and push notifications.
A service worker is a JavaScript file that the browser runs in the background, separate from any web page, acting as a programmable network proxy between your app and the network. It can intercept requests, serve cached responses, and keep working even when the page that registered it is closed. This is the mechanism behind offline-capable web apps, background sync, and push notifications.
Why it exists
Before service workers, a web page had no way to control network requests once they left the page’s JavaScript context — a fetch either hit the network or failed. Progressive web apps needed something closer to what native apps get for free: the ability to respond to a request from a local cache when the network is unavailable, update content in the background, or wake up briefly to show a push notification.
The Web Platform shipped service workers as the answer: a separate thread with its own lifecycle, running alongside the page rather than inside it.
The lifecycle
A service worker goes through distinct phases, all triggered by browser events rather than page code:
- Register — the page calls
navigator.serviceWorker.register('/sw.js'), telling the browser to fetch and install the script. - Install — the browser downloads the script and fires an
installevent. This is typically where you open a cache and pre-fetch the assets your app needs offline. - Activate — once installed, the worker activates, which is a good place to clean up old caches from a previous version.
- Fetch / Push / Sync — once active, the worker intercepts
fetchevents for requests within its scope, and can react topushevents (server-sent notifications) orsyncevents (deferred background tasks).
Crucially, a service worker has no direct access to the DOM. It communicates with open pages via postMessage, and it can be terminated by the browser when idle and restarted on the next event — so it shouldn’t hold state in memory between events.
Caching strategies
The most common use of a service worker is intercepting fetch events to decide whether a request should come from the cache, the network, or both. A few standard patterns:
- Cache-first — check the cache; only hit the network on a miss. Good for static assets like fonts and icons that rarely change.
- Network-first — try the network; fall back to the cache on failure. Good for content that should be fresh when possible, like an article page.
- Stale-while-revalidate — serve the cached response immediately, then fetch a fresh copy in the background to update the cache for next time. A good middle ground for most content.
These strategies are hand-rolled with the Cache API inside the fetch event handler, though libraries like Workbox generate this boilerplate for you.
Service workers vs a CDN
It’s worth being clear about what a service worker does and doesn’t replace. A CDN caches responses at edge locations close to the user, reducing round-trip time for every visitor. A service worker caches responses inside a single user’s browser, enabling that specific user to keep using the app when there’s no network at all.
| CDN | Service worker | |
|---|---|---|
| Runs where | Edge servers | User’s browser |
| Helps with | Latency, origin load | Offline access, repeat-visit speed |
| Scope | Every visitor | One user’s browser |
| Requires network | Yes | No, once cached |
They’re complementary: a CDN gets the first byte to the user fast, and a service worker makes the second and subsequent visits resilient to a flaky or absent connection.
What else service workers enable
Beyond caching, the same background execution context powers a few other capabilities:
- Push notifications — a service worker can receive a push message from a server even when no tab is open, and display a system notification.
- Background sync — if a form submission fails because the device is offline, the worker can register a sync task that retries automatically once connectivity returns.
- Precaching for app shells — pairing a service worker with a minimal HTML/CSS “shell” lets an app render instantly from cache while fresh data loads underneath, a pattern common in local-first software.
Common pitfalls
A misconfigured service worker can make debugging painful, since it sits between your code and the network:
- Stale deploys. If your caching strategy is too aggressive, users can get stuck on an old version of your app until the worker updates and activates. Version your cache names and clean up old ones on
activate. - Scope confusion. A service worker registered at
/app/sw.jsonly controls requests under/app/, not the whole origin, unless served from the root. - HTTPS requirement. Service workers only run on HTTPS origins (localhost is exempted for development), since a script with this much network control would be a serious attack vector over plain HTTP.
- Silent failures. Errors inside a service worker don’t show up in the normal page console by default — check the browser’s dedicated service worker devtools panel when debugging.
The takeaway
A service worker is a background script that intercepts network requests, giving a web app control over caching, offline behavior, and push notifications that were previously only possible in native apps. It’s not a replacement for a CDN — the two solve different parts of the latency and reliability problem — but combined with a thoughtful caching strategy, it’s what turns a normal web page into an app that keeps working when the network doesn’t.
Keep reading
Takina · · 4 min read What Is the Beacon API? navigator.sendBeacon()
The Beacon API lets a page send one last async request as it unloads, without blocking navigation or racing the browser's page teardown.
Takina · · 3 min read What Is Fetch Priority? The fetchpriority Attribute
fetchpriority lets you tell the browser which resources matter most, overriding its default heuristics to load critical assets sooner.
Takina · · 5 min read CSS will-change Explained: Compositing and Performance
The CSS will-change property hints the browser to prepare an element for an upcoming change, moving it to its own compositor layer. When to use it and when not to.