Service Worker avec Claude Code : cache, mises à jour et offline
Guide pratique pour Service Worker avec Claude Code : cache, cycle de mise à jour, UX offline et exemples.
Un Service Worker est très utile pour une PWA et une expérience offline, mais il devient vite dangereux s’il est ajouté sans règles. Si vous demandez à Claude Code “ajoute du cache” sans limites, l’application peut garder un vieux HTML, stocker des données privées ou refuser d’afficher une nouvelle version après déploiement.
Le plus simple est de voir le Service Worker comme un petit proxy entre le navigateur et le serveur. Il intercepte certaines requêtes et choisit entre le réseau, Cache API ou une page offline préparée. Ce guide couvre les décisions à prendre, un exemple copiable, l’invalidation de cache, le cycle de mise à jour, l’UX offline et les erreurs fréquentes.
Gardez les sources officielles à portée de main : MDN Service Worker API, le guide service workers de web.dev, les recommandations de cache web.dev et Chrome Workbox docs. Pour le contexte ClaudeCodeLab, lisez aussi le guide PWA avec Claude Code, les stratégies de cache et le guide IndexedDB.
Rôle d’un Service Worker
Un Service Worker est du JavaScript exécuté hors de la page. Il ne peut pas toucher le DOM, donc il ne modifie pas directement les boutons, formulaires ou états React. Il peut en revanche intercepter des requêtes éligibles et répondre avec le réseau, avec Cache API ou avec un fallback offline.
Le script de page disparaît quand l’onglet se ferme. Le Service Worker est réveillé par événements : install, activate, fetch, push et parfois la synchronisation en arrière-plan. Pour une première version solide, concentrez-vous sur fetch, Cache API et le cycle de mise à jour.
sequenceDiagram
participant User as Utilisateur
participant Page
participant SW as Service Worker
participant Cache as Cache API
participant Net as Serveur
User->>Page: Ouvre le site
Page->>SW: Enregistre /sw.js
Page->>SW: Lance une requête fetch
SW->>Cache: Vérifie le cache
alt En cache
Cache-->>SW: Réponse stockée
else Pas en cache
SW->>Net: Demande la réponse récente
Net-->>SW: Réponse fraîche
end
SW-->>Page: Réponse à afficher
Ce n’est donc pas une baguette magique de performance, mais un contrôleur de trafic. La qualité dépend de ce que vous stockez, de ce que vous supprimez, et de ce que l’utilisateur voit quand le réseau tombe.
Cas d’usage utiles
| Cas | Pourquoi c’est utile | Attention |
|---|---|---|
| Documentation ou blog | Articles, CSS, images et polices s’affichent plus vite au retour | Un HTML trop longtemps en cache masque les corrections |
| Dashboard SaaS | La navigation reste visible sur réseau instable | Ne pas cacher facturation, compte ou données privées |
| Formulaire terrain | L’utilisateur garde brouillons et actions en attente offline | POST va dans IndexedDB, pas dans Cache API |
| Catalogue ecommerce ou média | Les miniatures évitent des téléchargements répétés | Prix, stock et images protégées demandent des règles de fraîcheur |
Masa a testé ce modèle sur un petit site pédagogique. Cacher seulement images et polices a amélioré la deuxième visite. En revanche, mettre les pages d’article en Cache First a retardé la correction de fautes visibles. Pour Claude Code, il faut donc décrire la propriété et la durée de chaque cache, pas demander “cache tout”.
Brief Claude Code
Ajoutez les interdictions et la validation directement dans la demande.
Ajoute un Service Worker à l'app Vite existante.
Exigences:
- Place /sw.js dans public et utilise le scope /
- Cache seulement les assets statiques en GET
- Utilise Network First pour les navigations HTML
- Retourne /offline.html si la navigation échoue offline
- Ne cache pas API, POST, pages authentifiées ni autres origines
- Inclue une date ou version dans le nom du cache
- Supprime les anciens caches pendant activate
- Affiche une demande de rechargement si le nouveau worker est waiting
Validation:
- Vérifier Chrome DevTools > Application > Service Workers
- Passer Network en Offline et confirmer /offline.html
- Changer CACHE_VERSION et confirmer la suppression des vieux caches
Les exclusions sont essentielles. Une erreur sur API ou pages privées devient vite un bug de données, pas seulement une gêne d’interface.
Exemple minimal copiable
Placez ces quatre fichiers dans un dossier vide, par exemple sw-demo, puis lancez un serveur local. Un Service Worker exige HTTPS ou localhost; ouvrir le fichier HTML directement ne suffit pas.
python -m http.server 5173
Ouvrez http://localhost:5173.
<!-- index.html -->
<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Service Worker Demo</title>
<style>
body {
font-family: system-ui, sans-serif;
margin: 2rem;
line-height: 1.7;
}
button {
padding: 0.7rem 1rem;
}
</style>
</head>
<body>
<h1>Service Worker Demo</h1>
<p id="status">En attente d'enregistrement.</p>
<button type="button" onclick="location.reload()">Recharger</button>
<script src="/register-sw.js"></script>
</body>
</html>
// register-sw.js
const status = document.querySelector("#status");
let reloadRequested = false;
let updatePromptShown = false;
function setStatus(message) {
if (status) status.textContent = message;
}
function askToReload(worker) {
if (updatePromptShown) return;
updatePromptShown = true;
const ok = window.confirm(
"Une nouvelle version est disponible. Recharger maintenant ?",
);
if (ok) {
reloadRequested = true;
worker.postMessage({ type: "SKIP_WAITING" });
}
}
async function registerServiceWorker() {
if (!("serviceWorker" in navigator)) {
setStatus("Ce navigateur ne supporte pas les Service Workers.");
return;
}
try {
const registration = await navigator.serviceWorker.register("/sw.js", {
scope: "/",
});
setStatus(`Service Worker enregistré: ${registration.scope}`);
if (registration.waiting && navigator.serviceWorker.controller) {
askToReload(registration.waiting);
}
registration.addEventListener("updatefound", () => {
const worker = registration.installing;
if (!worker) return;
worker.addEventListener("statechange", () => {
const hasOldController = Boolean(navigator.serviceWorker.controller);
if (worker.state === "installed" && hasOldController) {
askToReload(worker);
}
});
});
} catch (error) {
console.error(error);
setStatus("Échec de l'enregistrement du Service Worker.");
}
}
navigator.serviceWorker?.addEventListener("controllerchange", () => {
if (!reloadRequested) return;
window.location.reload();
});
registerServiceWorker();
// sw.js
const CACHE_VERSION = "2026-06-02-v1";
const CACHE_PREFIX = "claude-sw-demo";
const CACHE_NAME = `${CACHE_PREFIX}-${CACHE_VERSION}`;
const APP_SHELL = [
"/",
"/index.html",
"/offline.html",
"/register-sw.js",
];
self.addEventListener("install", (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => cache.addAll(APP_SHELL)),
);
});
self.addEventListener("activate", (event) => {
event.waitUntil(
caches.keys().then((names) =>
Promise.all(
names
.filter((name) => name.startsWith(CACHE_PREFIX))
.filter((name) => name !== CACHE_NAME)
.map((name) => caches.delete(name)),
),
),
);
self.clients.claim();
});
self.addEventListener("message", (event) => {
if (event.data?.type === "SKIP_WAITING") {
self.skipWaiting();
}
});
self.addEventListener("fetch", (event) => {
const { request } = event;
if (request.method !== "GET") return;
const url = new URL(request.url);
if (url.origin !== self.location.origin) return;
if (request.mode === "navigate") {
event.respondWith(networkFirstNavigation(request));
return;
}
if (["style", "script", "font", "image"].includes(request.destination)) {
event.respondWith(staleWhileRevalidate(request));
}
});
async function networkFirstNavigation(request) {
const cache = await caches.open(CACHE_NAME);
try {
const response = await fetch(request);
if (response.ok) cache.put(request, response.clone());
return response;
} catch {
return (
(await cache.match(request)) ||
(await cache.match("/offline.html")) ||
new Response("Offline", { status: 503 })
);
}
}
async function staleWhileRevalidate(request) {
const cache = await caches.open(CACHE_NAME);
const cached = await cache.match(request);
const fetched = fetch(request)
.then((response) => {
if (response.ok) cache.put(request, response.clone());
return response;
})
.catch(() => cached || new Response("Offline", { status: 503 }));
return cached || fetched;
}
<!-- offline.html -->
<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Vous êtes hors ligne</title>
</head>
<body>
<main>
<h1>Vous êtes hors ligne</h1>
<p>Reconnectez-vous, puis rechargez cette page.</p>
<button type="button" onclick="location.reload()">Réessayer</button>
</main>
</body>
</html>
L’exemple utilise Network First pour la navigation et Stale While Revalidate pour CSS, JavaScript, polices et images. Network First tente le serveur, puis revient au cache ou à offline.html. Stale While Revalidate affiche le cache immédiatement tout en rafraîchissant en arrière-plan. Ne l’appliquez pas sans réflexion aux actualités, prix, stocks ou écrans authentifiés.
Mise à jour et invalidation
Le cycle de mise à jour est le point le plus fragile. Quand sw.js change, le navigateur installe un nouveau worker. Si une ancienne page reste ouverte, ce worker peut rester en waiting. Le code d’enregistrement détecte cet état, demande confirmation à l’utilisateur et envoie SKIP_WAITING seulement en cas d’accord.
Le worker appelle ensuite self.skipWaiting(), s’active, supprime les anciens caches et réclame les clients. Sans cette séquence, un utilisateur peut garder un vieux app-cache-v1 longtemps après un correctif.
Nommez les caches avec une date, un numéro de release ou un commit. Si votre build produit des fichiers hashés, synchronisez la liste de precache avec la sortie du build. Quand une liste manuelle devient fragile, Workbox est utile, mais il ne décide pas quelles données métier sont sûres à cacher.
UX offline
Le support offline ne s’arrête pas à une réponse Cache API. L’utilisateur doit savoir si son travail est enregistré, en attente de synchronisation ou en échec. Pour les formulaires, ne stockez pas les POST dans Cache API. Enregistrez les brouillons ou jobs dans IndexedDB côté page, puis réessayez quand le navigateur revient online. Background Sync peut aider, mais son support varie; prévoyez aussi l’événement online et un bouton visible de reprise.
Dans la demande à Claude Code, précisez le texte offline, le bouton de retry, les états de brouillon et les messages d’échec. Une application terrain devrait au minimum distinguer “envoyé”, “enregistré sur cet appareil” et “échec de synchronisation”.
Erreurs fréquentes
La première erreur est le scope. Un worker placé en /app/sw.js contrôle /app/ par défaut, pas tout le site. Pour contrôler tout le site, placez-le en /sw.js et utilisez le scope /.
La deuxième erreur est une URL 404 dans cache.addAll(). Une seule entrée manquante fait échouer tout l’install. Après un changement de Claude Code, vérifiez l’état dans DevTools Application et Cache Storage.
La troisième erreur est le cache de données privées. Ne cachez pas /api/me, pages de facturation, HTML admin ou JSON spécifique à l’utilisateur sans stratégie explicite de suppression. Le cache navigateur reste un stockage sur l’appareil.
La quatrième erreur est l’absence d’UX de mise à jour. De vieux workers peuvent garder de vieux JS et CSS. Versionnez les caches, supprimez pendant activate et laissez l’utilisateur recharger lorsqu’un worker attend.
Enfin, un Service Worker n’est pas un stockage permanent. Le navigateur peut évincer les caches. Les réponses opaque cross-origin sont difficiles à mesurer. Le DOM n’est pas accessible. Cela fonctionne seulement en HTTPS ou localhost. Ne promettez pas un offline complet sans test du flux réel.
Résumé et CTA
Un Service Worker améliore les revisites, l’offline et la qualité PWA. La voie sûre consiste à décider la propriété des caches, le cycle de mise à jour, les règles de données privées et les écrans offline avant de laisser Claude Code modifier les fichiers.
ClaudeCodeLab accompagne les conversions PWA, la conception de cache, les formulaires offline, les migrations Workbox et les revues d’implémentation Claude Code. Pour rendre un site plus rapide sans servir de données anciennes ou privées, démarrez par la formation et consultation Claude Code.
Testée dans Chrome local, cette configuration crée claude-sw-demo-2026-06-02-v1 dans Application après le premier chargement. En passant Network sur Offline puis en rechargeant, offline.html s’affiche. En modifiant CACHE_VERSION, les anciens caches sont supprimés pendant activate, ce qui en fait une bonne base de vérification.
PDF gratuit: cheatsheet Claude Code
Saisissez votre email et téléchargez une page avec commandes, habitudes de review et workflow sûr.
Nous protégeons vos données et n'envoyons pas de spam.
À propos de l'auteur
Masa
Ingénieur spécialisé dans les workflows pratiques avec Claude Code.
Articles liés
Échelle de sécurité des permissions Claude Code
Passer du read-only aux éditions limitées, preuves et checks de déploiement sans perdre le contrôle.
Claude Code Small PR Proof Pack : rendre les petits changements reviewables
Un pack de preuve pour PR Claude Code : diff, vérifications, URL publique, CTA et rollback.
Gate de review avant commit avec Claude Code
Review avant commit avec Claude Code : diff, build, URL publique, liens Gumroad, CTA consultation, tests manquants et fichiers hors scope.