← Retour au blog
PWAService WorkersCache StrategiesOffline-FirstWeb Performance

PWA: Mastering Cache Strategies and Service Worker Updates Without Breaking UX

Most PWAs remain stuck in binary offline: it works or it doesn't. The real challenge starts after: how do you serve fresh content without freezing the app in an obsolete version? How do you notify users about available updates without forcing a reload? How do you debug a cache serving stale poison? These questions separate PWAs that survive production from those that become frustration factories. The difference isn't technical luck—it's thinking through cache lifecycle, update strategy, and what users actually experience when the network fails.

Cache Strategies Beyond "Cache First"

The "cache first" strategy (serve cache, network request in the background) works for static assets: CSS, minified JS, fonts. But it becomes dangerous for business data. A user downloads your app, consumes a product page offline, then goes online: they see info from three days ago. "Network first" (network first, fallback to cache) partially solves this, except when the network is slow: the user waits eight seconds before seeing cached content. The real solution? Combine multiple strategies by context. Versioned assets (bundle.abc123.js): cache first with versioning. API data (GET /api/products): network first with short timeout (3s), then cache. Images and thumbnails: stale-while-revalidate (serve cache immediately, refresh in the background). Critical routes (cart, checkout): network only with explicit offline fallback. This isn't guesswork—it's learned from watching real users suffer through poorly cached PWAs.

Concretely, with Workbox (hand-rolling Service Worker is masochism), you declare this in your service worker: workbox.routing.registerRoute(/\/api\//,new workbox.strategies.NetworkFirst({cacheName:'api-cache',plugins:[new workbox.expiration.ExpirationPlugin({maxEntries:50,maxAgeSeconds:86400})]})); This line says: every request to /api/ must hit the network first with an implicit timeout, and keep max 50 entries in cache for 24 hours. For images: workbox.routing.registerRoute(/\.(?:png|gif|jpg|jpeg|svg)$/,new workbox.strategies.StaleWhileRevalidate({cacheName:'images'})); Serve immediately from cache, then silently refresh.

The Real Pain: Service Worker Updates

A developer deploys a critical fix. The active user receives the new service worker in the background. But the new worker doesn't activate automatically; the old one stays in control. The user continues on buggy code without knowing it. Or worse: the new worker activates, the cache is suddenly empty (because you renamed cache names), and the app crashes. This happens daily in production. Why? The service worker lifecycle is intentionally conservative: a new worker enters "waiting" and waits for all clients (tabs, windows) using the old worker to close. Only the last one to leave unlocks activation.

The robust strategy: implement an "update available" notification and let the user decide. When a new service worker arrives in "waiting", emit an event: if(reg.waiting){reg.waiting.postMessage({type:'SKIP_WAITING'});} The new worker listens and activates: self.addEventListener('message',event=>{if(event.data.type==='SKIP_WAITING')self.skipWait();}); On the UI side, listen to controllerchange: navigator.serviceWorker.controller.onchange=()=>{showNotification('New version available. Reload?');} User clicks, you call location.reload() . Clean, transparent, zero hidden frustration.

Version Your Caches, Not Just Assets

Classic mistake: deploy API v2 that changes response format, but keep the same cache name ("api-cache"). Offline clients see old-format responses, parse them, crash. Solution: version caches by deploy date or version number. In Workbox, pass cacheName:'api-cache-2026-08-29' (better: inject this from your build). On the next deploy, create fresh caches and clean old ones in the service worker: self.addEventListener('activate',event=>{event.waitUntil(caches.keys().then(names=>Promise.all(names.filter(name=>name.startsWith('api-cache-')&&name!==CURRENT_CACHE).map(name=>caches.delete(name)))));}); This guarantees the old service worker never serves an incompatible cache.

The Trap of "Invisible Caching"

You deploy a PWA, offline works great. Weeks later, a user reports images don't load offline. You debug, find images are cached... but under a different CDN domain. Why? Your HTML changed, image URLs changed, but you cached the old URLs. They sit in cache, inaccessible, wasting disk space. Second trap: a user activates your app, it caches 50 MB of data. They never reopen it. Their phone accumulates PWAs with 50 MB each; they run out of space. You didn't implement cache limits (maxAgeSeconds, maxEntries). Third trap: you cache 404 or 500 responses for offline. A user searches, gets a 404 (product doesn't exist), the app caches it, they go offline then online, search again, get the old cached 404. Lying offline is worse than telling the truth ("You're offline, can't verify").

Debugging and Monitoring

Open DevTools (F12 → Application → Service Workers). You see worker state (active, waiting, redundant). Click "Update on reload" to force a check on every reload during dev. Inspect the "Cache Storage" tab to see exactly what's sitting around. Test offline by checking "Offline" in Network, then reload. Caveat: offline in DevTools isn't truly offline (the service worker has no network access, but the app doesn't know yet). To test properly, use network throttling (SLOW 4G) instead of pure "Offline". In production, log cache errors: when a network-first request times out and cache is empty, that's an alert. Send it to Sentry or your APM.

Conclusion: PWAs That Grow Without Rotting

PWAs that survive production aren't those that cache everything. They're the ones that cache intelligently (by strategy, by context), that explicitly manage updates, that clean up after themselves, and that never lie to users. Investing two or three days to implement a real caching strategy, an update notification, and monitoring saves weeks of debugging and frustrated users. Start with Workbox, measure offline/online performance with Lighthouse, and test with DevTools plus network throttling. The rest follows naturally.

Développeur Angular & Mobile freelance — Strasbourg.

© 2026 Emilien Pons — Tous droits réservés.Conçu avec Angular, PrimeNG et ❤️