Service workers unlock offline-first capabilities for web apps, yet most implementations are shallow: cache a few assets, call it done. Real offline support requires a coherent strategy, thorough testing, and ongoing maintenance. The difference is measurable—and users notice.
Registration and lifecycle
Start by registering the service worker in your main script, ideally in production builds only. Call navigator.serviceWorker.register() with a path relative to your web root. The browser downloads the service-worker.js file, runs it in an isolated context, and listens for install and activate events. During install , precache critical resources (HTML shell, core CSS, essential JS). During activate , clean up old cache versions. Don't block install with heavy waitUntil() calls—prefer progressive precaching or lazy loading.
Caching strategies: beyond the basics
'Cache first, network fallback' works well for immutable assets (images, fonts): serve from cache immediately, refresh in the background. For dynamic API endpoints, use 'network first': try the network, fall back to cache if offline. For critical documents, 'stale-while-revalidate' is ideal: serve cached content instantly, refresh in the background, update on the next load. Namespace caches by version (assets-v1, api-v2) to manage rollouts cleanly.
Background sync and user awareness
Service workers expose the Background Sync API: register a sync task with registration.sync.register('sync-tag') when offline, it replays automatically on reconnection. Perfect for form submissions, payments, or file uploads. Pair it with the Notifications API to inform users of success or failure. Caveat: Safari on iOS doesn't support Background Sync or persistent notifications—implement polling or local retry queues as fallbacks.
Update management and cache versioning
Version your caches explicitly (assets-v1, api-v2). On each deployment, bump versions. During the activate event, purge old versions with caches.delete() . For the service worker script itself, decide: use skipWaiting() and clients.claim() for instant updates (risky), or let the old worker finish in-flight requests gracefully (safer, slightly stale).
Debugging and production monitoring
Chrome DevTools shows service worker state, cache contents, and offline simulation. Log cache errors to a backend endpoint with local fallback. Track hit/miss rates: high offline success ratios prove your strategy works. Test on slow networks (throttled 3G/4G) and true offline scenarios regularly.
Real-world: Ionic and Angular patterns
With Ionic, service workers integrate via ng build --prod . Precache main app shells and routes. For remote images, use 'cache, expire after 7 days'. With Angular, declare static routes and dynamic APIs in ngsw-config.json . Test on real devices with Capacitor: disable WiFi, verify the app stays usable.
Common pitfalls to avoid
Never cache auth tokens—use sessionStorage or server-side sessions. Don't rely on navigator.onLine as truth. Don't precache sensitive data. Older browsers (pre-2015) lack service worker support—provide graceful degradation. CORS matters: service workers can only serve same-origin or explicitly CORS-enabled resources. Test offline modes on actual hardware, not just DevTools simulation.