Back to blog
Ai News

Service Workers Explained

6 min read

A service worker is a JavaScript file that runs in the background, separate from a page's main thread, sitting between your web app and the network as a kind of programmable proxy. It intercepts network requests and decides how to respond — serve from cache, fetch from the network, or some combination of both — which is the mechanism that makes offline functionality, background sync, and push notifications possible in a web app without needing a native app wrapper.

The lifecycle, and why it matters

A service worker moves through distinct states, and understanding them matters for avoiding subtle bugs:

  • Install — runs once per new service worker version, typically used to cache a "precache manifest" of essential assets needed for the app to function offline.
  • Waiting — a newly installed service worker waits until all open tabs using the old version are closed before it activates, to avoid a page having two different service worker versions active simultaneously mid-session.
  • Activate — runs when the new worker actually takes over, commonly used to clean up old, now-outdated caches from a previous version.
  • Fetch — once activated, this intercepts every network request within the service worker's scope, giving your code the opportunity to decide how to respond to each one.

Misunderstanding the waiting state is a common source of confusion during development — a developer who deploys a service worker update and doesn't see it take effect immediately (because an old tab is still open) often assumes something is broken, when the behavior is actually working as designed.

The three core caching strategies

Different types of content need different caching behavior, and picking the wrong strategy for a given resource is one of the more common mistakes in service worker implementations:

  • Cache First — serve immediately from cache, updating the cache in the background for next time. Best for static assets that rarely change (fonts, logos, versioned JS/CSS bundles) where speed matters more than always having the absolute latest version.
  • Network First — try the network first, falling back to cache only if the network request fails. Best for API responses and dynamic content where freshness matters more than raw speed — you want the latest data when a connection is available, with cache purely as a fallback for when it isn't.
  • Stale While Revalidate — serve the cached version immediately for a fast response, while simultaneously fetching a fresh version in the background to update the cache for the next request. Best for content where slightly outdated data is an acceptable tradeoff for consistently fast responses — a middle ground between the other two strategies.

Choosing correctly per resource type is what actually determines whether an offline-first app feels reliable and fast versus buggy and inconsistent — applying Cache First to frequently changing API data, for instance, would mean users regularly see stale information with no mechanism to refresh it.

Practical rules that prevent common failures

Version your caches deliberately. When you update a service worker, increment the cache version name so the activate event can identify and delete old caches. Without this, old cached versions of assets can linger indefinitely, occasionally causing confusing bugs where a user is served an outdated asset that should have been replaced.

Don't over-precache. It's tempting to cache everything during the install step for maximum offline coverage, but precaching too much delays service worker activation and wastes user bandwidth on assets that may never actually be needed offline. The better practice is precaching only the essential assets required for core offline functionality, and caching everything else on demand as it's actually requested — building up the cache organically based on real usage rather than trying to anticipate everything upfront.

Security boundaries that shape what a service worker can actually do

Service workers carry real capability — intercepting network requests and controlling responses — which is exactly why the platform enforces strict boundaries around when they run at all. A service worker only works over HTTPS, with the sole exception of localhost for local development; this isn't an arbitrary restriction, it's because a service worker running over plain HTTP could be tampered with by anyone in a position to intercept unencrypted network traffic, effectively handing an attacker the same request-interception power the API is designed to give your own code. Service workers also execute strictly within the origin that registered them and cannot see or intercept requests made by other web applications or other origins running in the same browser — the same-origin boundary that underpins most of the web platform's security model applies here too, and it's what prevents a service worker registered by one site from being able to interfere with traffic on an entirely different site the user happens to have open in another tab.

Background sync in practice, and why Workbox exists

The background sync capability mentioned above — queueing an action to complete once connectivity returns — is built on the browser detecting that a network request inside the service worker has failed, registering a sync event, and firing that event once the browser judges that connectivity has been restored, even if the user has since closed the tab entirely. That last detail matters: background sync can complete a queued action without the app being open at all, which is part of what makes offline-first form submissions and similar patterns reliable rather than dependent on the user happening to still have the tab open when connectivity returns.

Given how many of these lifecycle, caching-strategy, and security details have to be gotten right simultaneously, most production implementations don't hand-write the raw Service Worker and Cache Storage APIs directly. Workbox, Google's open-source library set, wraps those low-level APIs in a considerably more developer-friendly interface — including a dedicated background-sync module that implements the queueing and retry logic described above, plus a documented fallback strategy for browsers that don't yet support the native BackgroundSync API. For most teams building a PWA in 2026, reaching for Workbox rather than implementing caching strategies and background sync from the raw APIs is the practical default, reserving hand-written service worker code for cases with genuinely unusual caching requirements Workbox's built-in strategies don't cover.

Why this matters beyond just "offline mode"

Service workers underpin more than literal offline functionality — they're also the mechanism behind background sync (queueing an action, like a form submission, to complete once connectivity returns) and push notifications (allowing a web app to receive and display notifications even when it's not open in an active tab). For any product considering a Progressive Web App approach — offering app-like reliability and engagement without requiring an app store install — a correctly implemented service worker with the right caching strategy per resource type is the foundational piece that makes the rest of the PWA feature set actually work.

Sources: MagicBell — Offline-First PWAs: Service Worker Caching Strategies, MDN — Service Workers, Progressive Web Apps, Chrome for Developers — workbox-background-sync, MDN — Web Periodic Background Synchronization API

Keep reading

Get new posts as they publish

No spam — just the next post, straight to your inbox.

Discussion