Service workers are powerful. They sit between your site and the network, intercepting every fetch request. They can cache pages for offline access, serve stale content while revalidating in the background, and preload resources the user is likely to need next. They can also break your site in ways that are nearly impossible to diagnose if you do not understand their scope.
The scope of a service worker determines which pages it controls. By default, the scope is the directory where the service worker file lives. A service worker at /sw.js controls the entire origin. A service worker at /app/sw.js only controls pages under /app/. This seems straightforward until you realize that scope conflicts, stale caches, and missing update logic can leave users stuck on old versions of your site with no way to clear the problem short of manually clearing browser data.
The scope problem
Most sites register their service worker at the root path and give it control over everything. That means every page on your site, including pages that should never be cached. Admin panels, checkout flows, personalized dashboards, pages with CSRF tokens. If your service worker uses a cache-first strategy and catches one of these pages, the user could see stale data, submit forms with expired tokens, or access a cached version of someone else's session in shared-device scenarios.
The fix is intentional scope design. Your service worker's fetch handler should have an explicit list of URL patterns it caches and a default network-first or network-only strategy for everything else. But many service workers are generated by frameworks (Workbox, next-pwa, gatsby-plugin-offline) with default configurations that cache more aggressively than the developer intended.
Install, activate, and the waiting trap
A service worker has a lifecycle: install, waiting, activate. When you deploy a new version of your service worker, the new version installs but does not activate until all tabs running the old version are closed. This means users who keep your site open in a tab can run old service worker code for days or weeks.
The skipWaiting() call bypasses this behavior and activates the new service worker immediately. Most production service workers use it. But skipWaiting() combined with a cache-first strategy means the new service worker activates while the page is still running old cached HTML. The page might reference CSS or JS files that the new service worker's precache has already replaced. The result is broken styling, missing scripts, or white screens.
The safer pattern is skipWaiting() plus clients.claim() plus a version check that forces a page reload when the service worker version changes. This is well-documented but rarely implemented correctly in custom service worker code.
What the audit checks
The Service Worker Audit fetches your sw.js file and inspects the actual code. It checks for install and activate handler presence, skipWaiting() usage, navigation preload configuration, caching strategy patterns, and offline fallback setup.
The audit flags service workers that do not include a fetch handler (registered but doing nothing), service workers that cache everything without a staleness strategy, and service workers with scope configurations that extend beyond what the page structure warrants. It also checks for common anti-patterns like caching API responses without a TTL, caching POST requests (which the Cache API does not support but some service workers attempt), and missing error handling in the fetch event.
Navigation preload is another item the audit checks. Introduced in Chrome 59, navigation preload allows the service worker to make the navigation request to the network at the same time the service worker is booting up. Without it, navigation requests are blocked until the service worker starts, adding 50 to 200 milliseconds of delay on every page load. The feature is widely supported but many service workers do not use it.
When to skip the service worker entirely
Not every site needs a service worker. If your site is a collection of static pages with no offline requirement and no dynamic caching needs, a service worker adds complexity without clear benefit. The browser's HTTP cache already handles caching for static assets based on Cache-Control headers.
Service workers make sense when you need offline support, background sync, push notifications, or fine-grained cache control that HTTP headers cannot express. For sites built around tools that work client-side, like the audit tools on this site, a service worker can cache the tool shell and let it work offline after the first visit.
If you are building lean sites for clients and keeping infrastructure simple, as I discuss in The $100 Network, the question is always whether the complexity pays for itself. An audit helps you answer that by showing what your service worker actually does versus what you think it does.
Related tools
- Preconnect Hints Hygiene Audit checks resource hints that service workers may override
- Remote Dependency Audit identifies external resources your service worker might need to handle
- Render Block Audit finds resources that delay initial render
- Web App Manifest Audit validates the manifest file that pairs with your service worker for PWA functionality
- Font Loading Strategy covers font caching strategies that interact with service worker cache
Fact-check notes and sources
- Service worker scope defaults: MDN Web Docs, "ServiceWorkerContainer.register()," scope parameter documentation.
- Navigation preload specification: W3C Service Workers specification, Section 6 "NavigationPreloadManager." Shipped in Chrome 59 (June 2017).
skipWaiting()andclients.claim()lifecycle: Google Developers, "The Service Worker Lifecycle" by Jake Archibald, updated 2023.- Cache API does not support POST requests: W3C Cache API specification, Section 5.4, "Request matching." Only GET and HEAD methods are cacheable.
- HTTP cache versus service worker cache: web.dev, "Prevent unnecessary network requests with the HTTP Cache" and "Service workers and the Cache Storage API."
- Service worker boot time delay (50-200ms): measured by Chrome performance team; documented in the Navigation Preload explainer on GitHub.
This post is informational, not web-development consulting advice. Service worker behavior varies across browser engines.