OUJOOD.COM
Pourquoi combiner les deux APIs ?
JavaScript PWA – mise à jour 2026Le Service Worker et l'API Cache Storage sont deux APIs distinctes qui deviennent vraiment puissantes ensemble. Séparément, chacune a une utilité limitée pour le mode hors ligne : le Service Worker sait intercepter les requêtes, mais sans Cache Storage il n'a nulle part où stocker les réponses. Le Cache Storage sait stocker des ressources, mais sans Service Worker rien n'intercepte les requêtes pour les y rediriger.
Ensemble, ils forment le socle technique des Progressive Web Apps (PWA) : le Service Worker joue le rôle d'un proxy intelligent entre la page et le réseau, et le Cache Storage est son entrepôt local. Chaque requête HTTP peut être traitée selon une stratégie précise — servir depuis le cache, aller sur le réseau, ou combiner les deux.
Ce tutoriel suppose que vous avez lu les deux guides précédents. Si ce n'est pas le cas, commencez par les Service Workers puis l'API Cache Storage avant de continuer.
Architecture générale
Voici comment les deux APIs s'articulent dans une application web hors ligne :
- À l'installation du Service Worker : on utilise
cache.addAll()pour pré-cacher les ressources statiques indispensables — la page d'accueil, le CSS, le JS, les images clés. - À chaque requête réseau : l'événement
fetchdu Service Worker intercepte la requête et applique une stratégie de cache selon le type de ressource. - À la mise à jour : l'événement
activatenettoie les anciens caches viacaches.delete().
Étape 1 : enregistrer le Service Worker
Le point d'entrée reste le même : on enregistre le Service Worker depuis la page HTML principale. Ce code ne change pas selon la stratégie de cache choisie.
// index.html — enregistrement du Service Worker
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/service-worker.js')
.then(function(registration) {
console.log('Service Worker enregistré, portée :', registration.scope);
})
.catch(function(erreur) {
console.log('Échec d\'enregistrement :', erreur);
});
});
}
Étape 2 : pré-cacher les ressources à l'installation
L'événement install est le bon moment pour constituer le cache initial. On ouvre un cache nommé avec un numéro de version, et on y charge toutes les ressources dont l'application a besoin pour fonctionner hors ligne dès la première visite.
Le numéro de version dans le nom du cache est essentiel : quand vous déployez une nouvelle version, changer ce nom déclenchera automatiquement le nettoyage de l'ancien cache dans activate.
// service-worker.js const CACHE_NOM = 'app-cache-v1'; const RESSOURCES_STATIQUES = [ '/', '/style.css', '/app.js', '/logo.png', '/offline.html' ]; self.addEventListener('install', function(event) { self.skipWaiting(); event.waitUntil( caches.open(CACHE_NOM).then(function(cache) { console.log('Pré-cache des ressources statiques...'); // addAll() échoue entièrement si une seule URL est inaccessible return cache.addAll(RESSOURCES_STATIQUES); }) ); });
Étape 3 : nettoyer les anciens caches à l'activation
Quand une nouvelle version du Service Worker s'active, les anciens caches deviennent inutiles. On les supprime dans activate en comparant les noms existants avec le nom du cache courant.
self.addEventListener('activate', function(event) {
event.waitUntil(
clients.claim().then(function() {
return caches.keys().then(function(noms) {
return Promise.all(
noms
.filter(function(nom) {
// Garder uniquement le cache de la version courante
return nom !== CACHE_NOM;
})
.map(function(nom) {
console.log('Suppression ancien cache :', nom);
return caches.delete(nom);
})
);
});
})
);
});
Étape 4 : choisir une stratégie de cache par type de ressource
Il n'existe pas une seule bonne stratégie de cache — le bon choix dépend du type de ressource. Une image de logo change rarement : le cache en premier est parfait. Une liste de produits change souvent : le réseau en premier est plus adapté.
Stratégie cache-first : ressources statiques
On cherche d'abord dans le cache. Si la ressource est présente, on la sert immédiatement sans toucher au réseau. Sinon, on va la chercher sur le réseau et on la met en cache pour la prochaine fois. C'est la stratégie idéale pour les fichiers qui changent peu : CSS, JavaScript, images, polices.
self.addEventListener('fetch', function(event) {
if (event.request.method !== 'GET') return;
event.respondWith(
caches.match(event.request).then(function(reponseCache) {
if (reponseCache) {
return reponseCache; // Servi depuis le cache : aucun appel réseau
}
// Absent du cache : requête réseau + mise en cache dynamique
return fetch(event.request).then(function(reponseReseau) {
if (reponseReseau && reponseReseau.status === 200) {
var clone = reponseReseau.clone();
caches.open(CACHE_NOM).then(function(cache) {
cache.put(event.request, clone);
});
}
return reponseReseau;
}).catch(function() {
// Hors ligne et ressource absente du cache : page de secours
return caches.match('/offline.html');
});
})
);
});
Stratégie network-first : contenu dynamique
On essaie d'abord le réseau pour avoir la version la plus récente. Si la connexion échoue, on bascule sur le cache. C'est la bonne stratégie pour les pages dont le contenu change régulièrement : actualités, listes de produits, profils utilisateurs.
self.addEventListener('fetch', function(event) {
if (event.request.method !== 'GET') return;
event.respondWith(
fetch(event.request)
.then(function(reponseReseau) {
// Réseau disponible : on met à jour le cache au passage
if (reponseReseau && reponseReseau.status === 200) {
var clone = reponseReseau.clone();
caches.open(CACHE_NOM).then(function(cache) {
cache.put(event.request, clone);
});
}
return reponseReseau;
})
.catch(function() {
// Réseau inaccessible : on tente le cache, sinon page offline
return caches.match(event.request).then(function(reponseCache) {
return reponseCache || caches.match('/offline.html');
});
})
);
});
Stratégie mixte : adapter selon l'URL
En pratique, une application réelle combine les deux stratégies selon le type de ressource demandée. On inspecte l'URL de la requête pour décider quelle stratégie appliquer.
self.addEventListener('fetch', function(event) {
if (event.request.method !== 'GET') return;
var url = new URL(event.request.url);
// Ressources statiques connues : cache-first
if (RESSOURCES_STATIQUES.includes(url.pathname)) {
event.respondWith(cacheFirst(event.request));
return;
}
// Appels API et pages dynamiques : network-first
if (url.pathname.startsWith('/api/') || url.pathname.endsWith('.php')) {
event.respondWith(networkFirst(event.request));
return;
}
// Tout le reste : cache-first avec fallback offline
event.respondWith(cacheFirst(event.request));
});
function cacheFirst(requete) {
return caches.match(requete).then(function(reponseCache) {
if (reponseCache) return reponseCache;
return fetch(requete).then(function(reponseReseau) {
if (reponseReseau && reponseReseau.status === 200) {
var clone = reponseReseau.clone();
caches.open(CACHE_NOM).then(function(cache) {
cache.put(requete, clone);
});
}
return reponseReseau;
}).catch(function() {
return caches.match('/offline.html');
});
});
}
function networkFirst(requete) {
return fetch(requete).then(function(reponseReseau) {
if (reponseReseau && reponseReseau.status === 200) {
var clone = reponseReseau.clone();
caches.open(CACHE_NOM).then(function(cache) {
cache.put(requete, clone);
});
}
return reponseReseau;
}).catch(function() {
return caches.match(requete).then(function(reponseCache) {
return reponseCache || caches.match('/offline.html');
});
});
}
Service Worker complet : tout assembler
Voici le fichier service-worker.js final qui combine les trois événements et la stratégie mixte. C'est un point de départ solide pour n'importe quelle PWA.
// service-worker.js — version complète avec stratégie mixte const CACHE_NOM = 'app-cache-v1'; const RESSOURCES_STATIQUES = ['/', '/style.css', '/app.js', '/logo.png', '/offline.html']; // Installation : pré-cache des ressources indispensables self.addEventListener('install', function(event) { self.skipWaiting(); event.waitUntil( caches.open(CACHE_NOM).then(function(cache) { return cache.addAll(RESSOURCES_STATIQUES); }) ); }); // Activation : nettoyage des anciens caches self.addEventListener('activate', function(event) { event.waitUntil( clients.claim().then(function() { return caches.keys().then(function(noms) { return Promise.all( noms.filter(function(n) { return n !== CACHE_NOM; }) .map(function(n) { return caches.delete(n); }) ); }); }) ); }); // Fetch : stratégie mixte selon le type de ressource self.addEventListener('fetch', function(event) { if (event.request.method !== 'GET') return; var url = new URL(event.request.url); if (url.pathname.startsWith('/api/') || url.pathname.endsWith('.php')) { event.respondWith(networkFirst(event.request)); } else { event.respondWith(cacheFirst(event.request)); } }); function cacheFirst(requete) { return caches.match(requete).then(function(r) { if (r) return r; return fetch(requete).then(function(reponse) { if (reponse && reponse.status === 200) { caches.open(CACHE_NOM).then(function(cache) { cache.put(requete, reponse.clone()); }); } return reponse; }).catch(function() { return caches.match('/offline.html'); }); }); } function networkFirst(requete) { return fetch(requete).then(function(reponse) { if (reponse && reponse.status === 200) { caches.open(CACHE_NOM).then(function(cache) { cache.put(requete, reponse.clone()); }); } return reponse; }).catch(function() { return caches.match(requete).then(function(r) { return r || caches.match('/offline.html'); }); }); }
Inspecter le cache dans le navigateur
Chrome DevTools permet de visualiser et de gérer le contenu du Cache Storage sans écrire une seule ligne de code. Ouvrez les outils de développement, allez dans l'onglet Application, puis Cache Storage dans le panneau gauche. Vous y voyez tous les caches par nom, et pour chacun la liste des ressources stockées avec leur URL, leur statut et leur taille.
Pour forcer le rechargement du Service Worker pendant le développement, cochez Update on reload dans Application > Service Workers. Cela évite d'avoir à fermer et rouvrir les onglets à chaque modification du fichier.
Remarque : Le Cache Storage est limité par le quota de stockage du navigateur, partagé avec IndexedDB et d'autres APIs. En pratique, les navigateurs modernes allouent entre 10 % et 20 % de l'espace disque disponible par origine. Pour les applications critiques, pensez à surveiller l'espace utilisé avec l'API navigator.storage.estimate().
Par carabde: le 13 avril 2026