</> HTML5Advent
ENFRESDEITPT

// apis · Web Platform Advent #4

Service Workers : cache, hors-ligne et mises à jour

Guide pratique des service workers — enregistrement, cycle de vie install/activate, Cache API, servir une page hors-ligne et déployer des mises à jour sans casse.

L'écran d'un smartphone en mode avion affichant l'heure, sans connexion réseau

Un service worker est un script que le navigateur exécute en arrière-plan, indépendamment de votre page. Il se place entre votre application et le réseau comme un proxy programmable : il peut intercepter les requêtes, y répondre depuis un cache et garder votre site fonctionnel même hors-ligne. C'est la base des Progressive Web Apps.

Deux règles incontournables d'abord. Un service worker ne s'exécute qu'en HTTPS (ou sur localhost en développement), et il n'a aucun accès au DOM — il dialogue avec les pages via des événements et des messages, jamais en touchant document.

Enregistrer un service worker

On enregistre le worker depuis le script normal de la page. Vérifiez d'abord le support, puis indiquez le fichier au navigateur :

if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker
      .register('/sw.js')
      .then((reg) => console.log('Scope :', reg.scope))
      .catch((err) => console.error('Échec SW :', err));
  });
}

L'emplacement du fichier compte : un worker à /sw.js contrôle tout l'origine, tandis qu'un worker à /app/sw.js ne contrôle que les pages sous /app/. Cette frontière est sa portée (scope).

Le cycle de vie : install et activate

Un service worker a sa propre vie, indépendante de la page. Les deux événements qui comptent le plus sont install (déclenché une fois, idéal pour pré-cacher les ressources) et activate (idéal pour nettoyer les anciens caches) :

const CACHE = 'site-v1';
const ASSETS = ['/', '/index.html', '/styles.css', '/app.js', '/offline.html'];

self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(CACHE).then((cache) => cache.addAll(ASSETS))
  );
});

self.addEventListener('activate', (event) => {
  event.waitUntil(
    caches.keys().then((keys) =>
      Promise.all(keys.filter((k) => k !== CACHE).map((k) => caches.delete(k)))
    )
  );
});

Appeler event.waitUntil() indique au navigateur de ne pas considérer la phase terminée tant que votre promesse n'est pas résolue — sinon il pourrait détruire le worker en plein travail.

Une baie de serveur avec des emplacements de disques durs et des voyants d'état
La Cache API stocke les réponses sur l'appareil de l'utilisateur, pas sur un serveur comme celui de la photo — c'est cette copie locale qui permet de charger une page sans réseau.

Intercepter les requêtes avec fetch

L'événement fetch est le cœur du système. Vous décidez de ce que renvoie chaque requête. Une stratégie courante et sûre est le cache d'abord, réseau en secours :

self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request).then((cached) => {
      return cached || fetch(event.request);
    })
  );
});

Pour afficher une page hors-ligne personnalisée quand le cache et le réseau échouent tous deux, ajoutez un catch :

self.addEventListener('fetch', (event) => {
  event.respondWith(
    fetch(event.request).catch(() => caches.match('/offline.html'))
  );
});

Les stratégies de cache en un coup d'œil

StratégieComportementIdéale pour
Cache d'abordServir le cache, sinon le réseauRessources statiques (CSS, JS, polices)
Réseau d'abordTenter le réseau, sinon le cacheHTML, données d'API qui changent
Stale-while-revalidateServir le cache, le mettre à jour en arrière-planAvatars, flux, listes de contenu

Déployer des mises à jour sans casser l'expérience

Quand vous modifiez sw.js, le navigateur détecte les nouveaux octets et installe le nouveau worker, mais il reste en attente tant qu'un onglet utilise encore l'ancien. Cela évite de faire tourner deux versions à la fois. Pour prendre le relais plus tôt, appelez self.skipWaiting() dans install et clients.claim() dans activate — mais seulement si vous êtes sûr que la nouvelle version reste compatible avec les pages déjà ouvertes.

Incrémentez toujours le nom du cache (site-v1site-v2) quand les ressources changent. Le gestionnaire activate ci-dessus supprime alors l'ancien cache, et les utilisateurs n'obtiennent jamais un mélange d'anciens et de nouveaux fichiers.

Voilà toute la boucle de base : enregistrer, pré-cacher à l'install, nettoyer à l'activate et répondre aux requêtes au fetch. Commencez par une stratégie cache d'abord pour les fichiers statiques et réseau d'abord pour votre HTML, et vous obtenez un site qui charge instantanément aux visites suivantes et survit à une coupure de connexion.