← Retour au blog
PWAService WorkersCache StrategiesOffline-FirstWeb Performance

PWA : maîtriser les stratégies de cache et les mises à jour sans casser l'UX

La plupart des PWA restent bloquées sur l'offline binaire : ça marche ou ça marche pas. Or, le vrai défi commence après : comment servir du contenu frais sans pétrifier l'app dans une version obsolète ? Comment notifier l'utilisateur qu'une mise à jour attend sans le forcer à recharger ? Comment déboguer un cache qui sert du poison ? Ces questions séparent les PWA qui survivent en production de celles qui deviennent des usines à frustration.

Les stratégies de cache au-delà du "cache first"

La stratégie "cache first" (servir le cache, requête réseau en arrière-plan) fonctionne pour les assets statiques : CSS, JS minifiés, polices. Mais elle devient dangereuse pour les données métier. Un utilisateur télécharge votre app, consomme une page de produit en offline, puis revient online : il voit une info obsolète de 3 jours. Le "network first" (réseau d'abord, fallback cache) résout partiellement le problème, sauf quand le réseau est lent : l'utilisateur attend 8 secondes avant de voir le contenu caché. La vraie solution ? Combiner plusieurs stratégies selon le contexte. Les assets versionnés (bundle.abc123.js) : cache first avec versioning. Les données API (GET /api/products) : network first avec timeout court (3s), puis cache. Les images et thumbnails : stale-while-revalidate (servir le cache immédiatement, rafraîchir en arrière-plan). Les routes critiques (panier, checkout) : network only avec fallback vers une page "offline" explicite.

Concrètement, avec Workbox (abstraire Service Worker à la main est masochiste), vous déclarez ça dans votre service worker : workbox.routing.registerRoute(/\/api\//,new workbox.strategies.NetworkFirst({cacheName:'api-cache',plugins:[new workbox.expiration.ExpirationPlugin({maxEntries:50,maxAgeSeconds:86400})]})); Cette ligne dit : toute requête vers /api/ doit chercher le réseau d'abord, avec un timeout implicite, et garder max 50 entrées en cache pendant 24h. Pour les images : workbox.routing.registerRoute(/\.(?:png|gif|jpg|jpeg|svg)$/,new workbox.strategies.StaleWhileRevalidate({cacheName:'images'})); Servir immédiatement depuis le cache, puis mettre à jour silencieusement.

La vraie plaie : les mises à jour service worker

Un développeur déploie une correction critique. L'utilisateur actif reçoit le nouveau service worker en arrière-plan. Mais le nouveau worker n'active pas automatiquement ; l'ancien reste en contrôle. L'utilisateur continue sur du code bugué sans le savoir. Ou pire : le nouveau worker active, le cache est soudainement vide (parce qu'on a changé les noms de cache), et l'app crash. Cela arrive quotidiennement en production. La raison ? Le lifecycle du service worker est intentionnellement conservateur : un nouveau worker entre en "waiting" et attend que tous les clients (onglets, fenêtres) qui utilisent l'ancien worker ferment. Seul le dernier à partir déverrouille l'activation.

La stratégie robuste : implémenter une notification "mise à jour disponible" et laisser l'utilisateur décider. Quand un nouveau service worker arrive en "waiting", on émet un événement : if(reg.waiting){reg.waiting.postMessage({type:'SKIP_WAITING'});} Le nouveau worker écoute et s'active : self.addEventListener('message',event=>{if(event.data.type==='SKIP_WAITING')self.skipWaiting();}); Côté UI Angular, vous écoutez le controllerchange : navigator.serviceWorker.controller.onchange=()=>{showNotification('Nouvelle version disponible. Recharger ?');} L'utilisateur clique, vous faites un location.reload() . Clean, transparent, zéro frustration cachée.

Versionner les caches, pas juste les assets

Erreur classique : déployer une version 2 de votre API qui change le format de réponse, mais garder le même nom de cache ("api-cache"). Les clients offline voient des réponses au format ancien, les parsent, crash. Solution : versioner les caches par date de déploiement ou numéro de version. Dans Workbox, passez cacheName:'api-cache-2026-08-29' (ou mieux, injectez ça depuis votre build). Lors du deploy suivant, créez des caches neufs, et nettoyez les anciens dans le 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)))));}); Cela garantit que l'ancien service worker ne sert jamais un cache incompatible.

Le piège de la "mise en cache invisible"

Vous déployez une PWA, tout fonctionne offline. Quelques semaines plus tard, un utilisateur signale que les images ne chargent plus offline. Vous debuggez, trouvez que les images sont cachées... mais sous un domaine CDN différent. Pourquoi ? Parce que votre HTML a changé, les URLs d'images ont changé, mais vous aviez caché les anciennes URLs. Elles restent en cache, inaccessibles, prenant de la place disque. Deuxième piège : un utilisateur active votre app, elle cache 50 MB de données. Il ne la réouvre jamais. Son téléphone accumule des PWA avec 50 MB chacune, il manque d'espace. Vous n'aviez pas implémenté de limite de cache (maxAgeSeconds, maxEntries). Troisième piège : vous cachez les réponses 404 ou 500 pour l'offline. Un utilisateur lance une recherche, reçoit un 404 (produit inexistant), l'app le cache, il va offline puis online, tente de chercher le même produit, reçoit toujours le 404 caché d'avant. Mentir en offline est pire que dire la vérité ("Vous êtes offline, impossible de vérifier").

Debugging et monitoring

Ouvrez DevTools (F12 → Application → Service Workers). Vous voyez l'état du worker (active, waiting, redundant). Cliquez sur "Update on reload" pour forcer une vérification à chaque rechargement pendant le dev. Inspectez l'onglet "Cache Storage" pour voir exactement ce qui traîne. Testez l'offline en cochant "Offline" dans Network, puis rechargez. Attention : offline dans DevTools n'est pas vraiment offline (le service worker n'a pas d'accès réseau, mais l'app ne sait pas encore qu'elle est offline). Pour tester correctement, utilisez un throttle réseau (SLOW 4G) plutôt que "Offline" pur. En production, loggez les erreurs de cache : quand une requête network-first timeout et que le cache est vide, c'est un signal d'alerte. Envoyer ça à Sentry ou votre APM.

Conclusion : des PWA qui grandissent sans pourrir

Les PWA qui survivent en production ne sont pas celles qui cachent tout. Ce sont celles qui cachent intelligemment (par stratégie, par contexte), qui gèrent explicitement les mises à jour, qui nettoient après elles, et qui ne mentent jamais à l'utilisateur. Investir 2-3 jours pour implémenter une vraie stratégie de cache, une notification de mise à jour, et du monitoring sauve des semaines de débogage et des utilisateurs frustrés. Commencez par Workbox, mesurez les performances offline/online avec Lighthouse, et testez avec DevTools + Network throttling. Le reste suit naturellement.

Développeur Angular & Mobile freelance — Strasbourg.

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