MaĂźtrisez l'API Fetch : interception de requĂȘtes pour des modifications dynamiques et mise en cache des rĂ©ponses pour une performance accrue des applications web mondiales.
API Fetch AvancĂ©e : Interception de RequĂȘtes vs. Mise en Cache des RĂ©ponses pour les Applications Web Mondiales
Dans le paysage en constante Ă©volution du dĂ©veloppement web, la performance et la rĂ©activitĂ© sont primordiales. Pour un public mondial, oĂč la latence du rĂ©seau et la stabilitĂ© de la connexion peuvent varier considĂ©rablement, optimiser la maniĂšre dont nos applications rĂ©cupĂšrent et gĂšrent les donnĂ©es n'est pas seulement une bonne pratique â c'est une nĂ©cessitĂ©. L'API Fetch, un standard moderne pour effectuer des requĂȘtes rĂ©seau en JavaScript, offre des fonctionnalitĂ©s puissantes qui vont au-delĂ des simples requĂȘtes GET et POST. Parmi ces fonctionnalitĂ©s avancĂ©es, l'interception de requĂȘtes et la mise en cache des rĂ©ponses se distinguent comme des techniques cruciales pour construire des applications web mondiales robustes et efficaces.
Cet article explorera en profondeur l'interception de requĂȘtes et la mise en cache des rĂ©ponses en utilisant l'API Fetch. Nous examinerons leurs concepts fondamentaux, leurs stratĂ©gies de mise en Ćuvre pratiques, et comment elles peuvent ĂȘtre exploitĂ©es en synergie pour crĂ©er une expĂ©rience utilisateur supĂ©rieure pour les utilisateurs du monde entier. Nous aborderons Ă©galement les considĂ©rations relatives Ă l'internationalisation et Ă la localisation lors de la mise en Ćuvre de ces modĂšles.
Comprendre les Concepts Fondamentaux
Avant d'entrer dans les dĂ©tails, clarifions ce que l'interception de requĂȘtes et la mise en cache des rĂ©ponses impliquent dans le contexte de l'API Fetch.
Interception de RequĂȘtes
L'interception de requĂȘtes fait rĂ©fĂ©rence Ă la capacitĂ© d'intercepter les requĂȘtes rĂ©seau sortantes effectuĂ©es par votre code JavaScript avant qu'elles ne soient envoyĂ©es au serveur. Cela vous permet de :
- Modifier les requĂȘtes : Ajouter des en-tĂȘtes personnalisĂ©s (par ex., jetons d'authentification, versionnement de l'API), changer le corps de la requĂȘte, altĂ©rer l'URL, ou mĂȘme annuler une requĂȘte sous certaines conditions.
- Journaliser les requĂȘtes : Suivre l'activitĂ© rĂ©seau Ă des fins de dĂ©bogage ou d'analyse.
- Simuler des requĂȘtes (mocking) : Simuler des rĂ©ponses de serveur pendant le dĂ©veloppement ou les tests sans avoir besoin d'un backend actif.
Bien que l'API Fetch elle-mĂȘme n'offre pas de mĂ©canisme direct et intĂ©grĂ© pour intercepter les requĂȘtes de la mĂȘme maniĂšre que certaines bibliothĂšques tierces ou les anciens intercepteurs XMLHttpRequest (XHR), sa flexibilitĂ© nous permet de construire des modĂšles d'interception robustes, notamment grĂące aux Service Workers.
Mise en Cache des Réponses
La mise en cache des rĂ©ponses, d'autre part, implique de stocker les rĂ©sultats des requĂȘtes rĂ©seau localement cĂŽtĂ© client. Lorsqu'une requĂȘte ultĂ©rieure est faite pour la mĂȘme ressource, la rĂ©ponse en cache peut ĂȘtre servie au lieu de faire un nouvel appel rĂ©seau. Cela entraĂźne des amĂ©liorations significatives en matiĂšre de :
- Performance : Une récupération plus rapide des données réduit les temps de chargement et améliore la réactivité perçue.
- Support hors ligne : Les utilisateurs peuvent accĂ©der aux donnĂ©es prĂ©cĂ©demment rĂ©cupĂ©rĂ©es mĂȘme lorsque leur connexion internet est indisponible ou instable.
- Réduction de la charge du serveur : Moins de trafic vers le serveur signifie des coûts d'infrastructure plus bas et une meilleure scalabilité.
L'API Fetch fonctionne de maniĂšre transparente avec les mĂ©canismes de mise en cache du navigateur et peut ĂȘtre amĂ©liorĂ©e avec des stratĂ©gies de mise en cache personnalisĂ©es implĂ©mentĂ©es via les Service Workers ou les API de stockage du navigateur comme localStorage ou IndexedDB.
Interception de RequĂȘtes avec les Service Workers
Les Service Workers sont la pierre angulaire pour la mise en Ćuvre de modĂšles d'interception de requĂȘtes avancĂ©s avec l'API Fetch. Un Service Worker est un fichier JavaScript qui s'exĂ©cute en arriĂšre-plan, sĂ©parĂ©ment de votre page web, et agit comme un proxy rĂ©seau programmable entre le navigateur et le rĂ©seau.
Qu'est-ce qu'un Service Worker ?
Un Service Worker s'enregistre pour Ă©couter des Ă©vĂ©nements, dont le plus important est l'Ă©vĂ©nement fetch. Lorsqu'une requĂȘte rĂ©seau est faite depuis la page que le Service Worker contrĂŽle, le Service Worker reçoit un Ă©vĂ©nement fetch et peut alors dĂ©cider comment y rĂ©pondre.
Enregistrer un Service Worker
La premiÚre étape consiste à enregistrer votre Service Worker. Cela se fait généralement dans votre fichier JavaScript principal :
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js')
.then(function(registration) {
console.log('Service Worker registered with scope:', registration.scope);
})
.catch(function(error) {
console.error('Service Worker registration failed:', error);
});
}
Le chemin /sw.js pointe vers votre script de Service Worker.
Le Script du Service Worker (sw.js)
à l'intérieur de votre fichier sw.js, vous écouterez l'événement fetch :
self.addEventListener('fetch', function(event) {
// La logique d'interception de requĂȘte va ici
});
ImplĂ©menter la Logique d'Interception de RequĂȘtes
Au sein de l'Ă©couteur d'Ă©vĂ©nement fetch, event.request donne accĂšs Ă l'objet de la requĂȘte entrante. Vous pouvez alors l'utiliser pour :
1. Modifier les En-tĂȘtes de RequĂȘte
Supposons que vous ayez besoin d'ajouter une clĂ© d'API Ă chaque requĂȘte sortante vers un point de terminaison d'API spĂ©cifique. Vous pouvez intercepter la requĂȘte, en crĂ©er une nouvelle avec l'en-tĂȘte ajoutĂ©, puis continuer :
self.addEventListener('fetch', function(event) {
const url = new URL(event.request.url);
const apiKey = 'VOTRE_CLĂ_API_GLOBALE'; // Ă charger depuis une source sĂ©curisĂ©e ou une configuration
if (url.origin === 'https://api.example.com') {
// Cloner la requĂȘte pour pouvoir la modifier
const modifiedRequest = new Request(event.request, {
headers: {
'X-API-Key': apiKey,
// Vous pouvez aussi fusionner les en-tĂȘtes existants :
// ...Object.fromEntries(event.request.headers.entries()),
// 'X-Custom-Header': 'value'
}
});
// RĂ©pondre avec la requĂȘte modifiĂ©e
event.respondWith(fetch(modifiedRequest));
} else {
// Pour les autres requĂȘtes, continuer normalement
event.respondWith(fetch(event.request));
}
});
ConsidĂ©rations mondiales : Pour les applications mondiales, les clĂ©s d'API peuvent devoir ĂȘtre spĂ©cifiques Ă une rĂ©gion ou gĂ©rĂ©es par un service d'authentification central qui gĂšre le routage gĂ©ographique. Assurez-vous que votre logique d'interception rĂ©cupĂšre ou applique correctement la clĂ© appropriĂ©e pour la rĂ©gion de l'utilisateur.
2. Rediriger les RequĂȘtes
Vous pourriez vouloir rediriger les requĂȘtes vers un serveur diffĂ©rent en fonction de la localisation de l'utilisateur ou d'une stratĂ©gie de test A/B.
self.addEventListener('fetch', function(event) {
const url = new URL(event.request.url);
const userLocation = getUserLocation(); // Placeholder pour la logique de localisation
if (url.pathname === '/api/data') {
let targetUrl = url.toString();
if (userLocation === 'europe') {
targetUrl = 'https://api.europe.example.com/data';
} else if (userLocation === 'asia') {
targetUrl = 'https://api.asia.example.com/data';
}
// Cloner et rediriger
const redirectedRequest = new Request(targetUrl, {
method: event.request.method,
headers: event.request.headers,
body: event.request.body,
mode: 'cors'
});
event.respondWith(fetch(redirectedRequest));
} else {
event.respondWith(fetch(event.request));
}
});
function getUserLocation() {
// Dans une application réelle, cela impliquerait une recherche GeoIP, des paramÚtres utilisateur ou l'API de géolocalisation du navigateur.
// Pour la démonstration, supposons une logique simple.
return 'asia'; // Exemple
}
ConsidĂ©rations mondiales : La redirection dynamique est vitale pour les applications mondiales. Le gĂ©o-routage peut rĂ©duire considĂ©rablement la latence en dirigeant les utilisateurs vers le serveur d'API le plus proche. L'implĂ©mentation de `getUserLocation()` doit ĂȘtre robuste, utilisant potentiellement des services de gĂ©olocalisation par IP optimisĂ©s pour la vitesse et la prĂ©cision dans le monde entier.
3. Annuler les RequĂȘtes
Si une requĂȘte n'est plus pertinente (par exemple, l'utilisateur quitte la page), vous pourriez vouloir l'annuler.
let ongoingRequests = {};
self.addEventListener('fetch', function(event) {
const requestId = Math.random().toString(36).substring(7);
ongoingRequests[requestId] = event.request;
event.respondWith(
fetch(event.request).finally(() => {
delete ongoingRequests[requestId];
})
);
});
// Exemple de la maniĂšre dont vous pourriez annuler une requĂȘte depuis le thread principal (moins courant pour l'interception elle-mĂȘme, mais dĂ©montre le contrĂŽle)
function cancelRequest(requestUrl) {
for (const id in ongoingRequests) {
if (ongoingRequests[id].url === requestUrl) {
// Note : L'API Fetch n'a pas d' 'abort' direct pour une requĂȘte *aprĂšs* son envoi via un SW.
// Ceci est plus illustratif. Pour une véritable annulation, AbortController est utilisé *avant* fetch.
console.warn(`Attempting to cancel request for: ${requestUrl}`);
// Une approche plus pratique consisterait Ă vĂ©rifier si une requĂȘte est toujours pertinente avant d'appeler fetch dans le SW.
break;
}
}
}
Note : L'annulation rĂ©elle d'une requĂȘte aprĂšs l'appel de `fetch()` dans le Service Worker est complexe. L'API `AbortController` est la maniĂšre standard d'annuler une requĂȘte `fetch`, mais elle doit ĂȘtre passĂ©e Ă l'appel `fetch` lui-mĂȘme, souvent initiĂ© depuis le thread principal. Les Service Workers interceptent principalement puis dĂ©cident comment rĂ©pondre.
4. Simuler des Réponses pour le Développement
Pendant le développement, vous pouvez utiliser votre Service Worker pour retourner des données simulées (mock), en contournant le réseau réel.
self.addEventListener('fetch', function(event) {
const url = new URL(event.request.url);
if (url.pathname === '/api/users') {
// VĂ©rifier s'il s'agit d'une requĂȘte GET
if (event.request.method === 'GET') {
const mockResponse = {
status: 200,
statusText: 'OK',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify([
{ id: 1, name: 'Alice', region: 'North America' },
{ id: 2, name: 'Bob', region: 'Europe' },
{ id: 3, name: 'Charlie', region: 'Asia' }
])
};
event.respondWith(new Response(mockResponse.body, mockResponse));
} else {
// Gérer les autres méthodes si nécessaire ou laisser passer
event.respondWith(fetch(event.request));
}
} else {
event.respondWith(fetch(event.request));
}
});
Considérations mondiales : La simulation peut inclure des variations de données pertinentes pour différentes régions, aidant les développeurs à tester le contenu et les fonctionnalités localisés sans nécessiter une configuration de backend mondial entiÚrement fonctionnelle.
Stratégies de Mise en Cache des Réponses avec l'API Fetch
Les Service Workers sont Ă©galement incroyablement puissants pour mettre en Ćuvre des stratĂ©gies sophistiquĂ©es de mise en cache des rĂ©ponses. C'est lĂ que la magie du support hors ligne et de la rĂ©cupĂ©ration de donnĂ©es ultra-rapide brille vraiment.
Tirer Parti du Cache du Navigateur
Le navigateur lui-mĂȘme dispose d'un cache HTTP intĂ©grĂ©. Lorsque vous utilisez `fetch()` sans logique spĂ©ciale de Service Worker, le navigateur vĂ©rifiera d'abord son cache. Si une rĂ©ponse valide et non expirĂ©e en cache est trouvĂ©e, elle sera servie directement. Les en-tĂȘtes de contrĂŽle du cache envoyĂ©s par le serveur (par ex., `Cache-Control: max-age=3600`) dictent combien de temps les rĂ©ponses sont considĂ©rĂ©es comme fraĂźches.
Mise en Cache Personnalisée avec les Service Workers
Les Service Workers vous donnent un contrÎle granulaire sur la mise en cache. Le modÚle général consiste à intercepter un événement `fetch`, à tenter de récupérer la réponse depuis le cache, et si elle n'est pas trouvée, à la récupérer sur le réseau puis à la mettre en cache pour une utilisation future.
1. Stratégie Cache-First (Le cache d'abord)
C'est une stratĂ©gie courante oĂč le Service Worker essaie d'abord de servir la rĂ©ponse depuis son cache. Si elle n'est pas trouvĂ©e dans le cache, il effectue une requĂȘte rĂ©seau, sert la rĂ©ponse du rĂ©seau et la met en cache pour la prochaine fois.
const CACHE_NAME = 'my-app-v1';
const urlsToCache = [
'/',
'/styles.css',
'/script.js'
];
self.addEventListener('install', function(event) {
// Effectuer les étapes d'installation
event.waitUntil(
caches.open(CACHE_NAME)
.then(function(cache) {
console.log('Opened cache');
return cache.addAll(urlsToCache);
})
);
});
self.addEventListener('fetch', function(event) {
event.respondWith(
caches.match(event.request)
.then(function(response) {
// Le cache a été trouvé - retourner la réponse
if (response) {
return response;
}
// Pas dans le cache - récupérer depuis le réseau
return fetch(event.request).then(
function(response) {
// Vérifier si nous avons reçu une réponse valide
if (!response || response.status !== 200 || response.type !== 'basic') {
return response;
}
// IMPORTANT : Clonez la réponse. Une réponse est un flux (stream)
// et comme nous voulons que le navigateur consomme la réponse
// ainsi que le cache, nous devons la cloner pour avoir
// deux flux.
const responseToCache = response.clone();
caches.open(CACHE_NAME)
.then(function(cache) {
cache.put(event.request, responseToCache);
});
return response;
}
);
})
);
});
// Optionnel : Nettoyer les anciens caches lorsqu'une nouvelle version du SW est installée
self.addEventListener('activate', function(event) {
const cacheWhitelist = [CACHE_NAME];
event.waitUntil(
caches.keys().then(function(cacheNames) {
return Promise.all(
cacheNames.map(function(cacheName) {
if (cacheWhitelist.indexOf(cacheName) === -1) {
return caches.delete(cacheName);
}
})
);
})
);
});
2. Stratégie Network-First (Le réseau d'abord)
Cette stratĂ©gie priorise la rĂ©cupĂ©ration de donnĂ©es fraĂźches depuis le rĂ©seau. Si la requĂȘte rĂ©seau Ă©choue (par ex., pas de connexion), elle se rabat sur la rĂ©ponse en cache.
self.addEventListener('fetch', function(event) {
event.respondWith(
fetch(event.request).catch(function() {
// Si fetch échoue, se rabattre sur le cache
return caches.match(event.request);
})
);
});
ConsidĂ©rations mondiales : La stratĂ©gie Network-first est excellente pour le contenu dynamique oĂč la fraĂźcheur est critique, mais vous voulez tout de mĂȘme une rĂ©silience pour les utilisateurs avec des connexions intermittentes, ce qui est courant dans de nombreuses parties du monde.
3. Stale-While-Revalidate
C'est une stratĂ©gie plus avancĂ©e et souvent prĂ©fĂ©rĂ©e pour le contenu dynamique. Elle sert immĂ©diatement la rĂ©ponse en cache (rendant l'interface utilisateur rapide) tandis qu'en arriĂšre-plan, elle effectue une requĂȘte rĂ©seau pour revalider le cache. Si la requĂȘte rĂ©seau renvoie une version plus rĂ©cente, le cache est mis Ă jour.
self.addEventListener('fetch', function(event) {
event.respondWith(
caches.open(CACHE_NAME).then(function(cache) {
return cache.match(event.request).then(function(cachedResponse) {
// Si une réponse en cache existe, la retourner immédiatement
if (cachedResponse) {
// Commencer la récupération depuis le réseau en arriÚre-plan
fetch(event.request).then(function(networkResponse) {
// Si la réponse réseau est valide, mettre à jour le cache
if (networkResponse && networkResponse.status === 200 && networkResponse.type === 'basic') {
cache.put(event.request, networkResponse.clone());
}
}).catch(function() {
// La récupération réseau a échoué, ne rien faire, déjà servi depuis le cache
});
return cachedResponse;
}
// Pas de réponse en cache, récupérer depuis le réseau et la mettre en cache
return fetch(event.request).then(function(networkResponse) {
if (networkResponse && networkResponse.status === 200 && networkResponse.type === 'basic') {
cache.put(event.request, networkResponse.clone());
}
return networkResponse;
});
});
})
);
});
ConsidĂ©rations mondiales : Cette stratĂ©gie offre le meilleur des deux mondes â vitesse perçue et donnĂ©es Ă jour. Elle est particuliĂšrement efficace pour les applications mondiales oĂč les utilisateurs peuvent ĂȘtre Ă©loignĂ©s du serveur d'origine et subir une latence Ă©levĂ©e ; ils obtiennent les donnĂ©es instantanĂ©ment depuis le cache, et le cache est mis Ă jour pour les requĂȘtes suivantes.
4. Stratégie Cache-Only (Cache uniquement)
Cette stratĂ©gie ne sert que depuis le cache et ne fait jamais de requĂȘte rĂ©seau. Elle est idĂ©ale pour les ressources critiques et immuables ou lorsque le mode hors ligne est une exigence absolue.
self.addEventListener('fetch', function(event) {
event.respondWith(
caches.match(event.request).then(function(response) {
// Si la réponse est trouvée dans le cache, la retourner, sinon retourner une erreur ou un fallback
return response || new Response('Network error - Offline content not available', { status: 404 });
})
);
});
5. Stratégie Network-Only (Réseau uniquement)
Cette stratĂ©gie effectue simplement une requĂȘte rĂ©seau et n'utilise jamais le cache. C'est le comportement par dĂ©faut de `fetch()` sans Service Worker, mais peut ĂȘtre explicitement dĂ©fini dans un Service Worker pour des ressources spĂ©cifiques.
self.addEventListener('fetch', function(event) {
event.respondWith(fetch(event.request));
});
Combiner l'Interception de RequĂȘtes et la Mise en Cache des RĂ©ponses
La vĂ©ritable puissance de l'API Fetch pour les applications mondiales Ă©merge lorsque vous combinez l'interception de requĂȘtes et la mise en cache des rĂ©ponses. Votre Service Worker peut agir comme un hub central, orchestrant une logique rĂ©seau complexe.
Exemple : Appels API Authentifiés avec Mise en Cache
ConsidĂ©rons une application de commerce Ă©lectronique. Les donnĂ©es de profil utilisateur et les listes de produits peuvent ĂȘtre mises en cache, mais des actions comme l'ajout d'articles Ă un panier ou le traitement d'une commande nĂ©cessitent une authentification et doivent ĂȘtre gĂ©rĂ©es diffĂ©remment.
// Dans sw.js
const CACHE_NAME = 'my-app-v2';
// Mettre en cache les ressources statiques
self.addEventListener('install', function(event) {
event.waitUntil(
caches.open(CACHE_NAME)
.then(function(cache) {
return cache.addAll([
'/', '/index.html', '/styles.css', '/app.js',
'/images/logo.png'
]);
})
);
});
self.addEventListener('fetch', function(event) {
const requestUrl = new URL(event.request.url);
// GĂ©rer les requĂȘtes API
if (requestUrl.origin === 'https://api.globalstore.com') {
// Interception de RequĂȘte : Ajouter un Jeton d'Authentification pour les appels API
const authHeader = { 'Authorization': `Bearer ${getAuthToken()}` }; // Placeholder
const modifiedRequest = new Request(event.request, {
headers: {
...Object.fromEntries(event.request.headers.entries()),
...authHeader
}
});
// Stratégie de Mise en Cache de Réponse : Stale-While-Revalidate pour le catalogue de produits
if (requestUrl.pathname.startsWith('/api/products')) {
event.respondWith(
caches.open(CACHE_NAME).then(function(cache) {
return cache.match(modifiedRequest).then(function(cachedResponse) {
// Si une réponse en cache existe, la retourner immédiatement
if (cachedResponse) {
// Commencer la récupération depuis le réseau en arriÚre-plan pour les mises à jour
fetch(modifiedRequest).then(function(networkResponse) {
if (networkResponse && networkResponse.status === 200 && networkResponse.type === 'basic') {
cache.put(modifiedRequest, networkResponse.clone());
}
}).catch(function() { /* Ignorer les erreurs réseau ici */ });
return cachedResponse;
}
// Pas de réponse en cache, récupérer depuis le réseau et la mettre en cache
return fetch(modifiedRequest).then(function(networkResponse) {
if (networkResponse && networkResponse.status === 200 && networkResponse.type === 'basic') {
cache.put(modifiedRequest, networkResponse.clone());
}
return networkResponse;
});
});
})
);
}
// Network-First pour les données spécifiques à l'utilisateur (ex: panier, commandes)
else if (requestUrl.pathname.startsWith('/api/user') || requestUrl.pathname.startsWith('/api/cart')) {
event.respondWith(
fetch(modifiedRequest).catch(function() {
// Se rabattre sur le cache si le réseau échoue (pour consultation hors ligne des données précédemment chargées)
return caches.match(modifiedRequest);
})
);
}
// Network-only pour les opérations critiques (ex: passer une commande)
else {
event.respondWith(fetch(modifiedRequest));
}
}
// Pour les autres requĂȘtes (ex: ressources externes), utiliser fetch par dĂ©faut
else {
event.respondWith(fetch(event.request));
}
});
function getAuthToken() {
// Cette fonction doit récupérer le jeton d'authentification, potentiellement depuis localStorage
// ou un cookie. Soyez attentif aux implications de sécurité.
return localStorage.getItem('authToken') || 'guest'; // Exemple
}
Considérations mondiales :
- Authentification : La fonction `getAuthToken()` doit ĂȘtre robuste. Pour une application mondiale, un fournisseur d'identitĂ© central qui gĂšre OAuth ou les JWT est courant. Assurez-vous que les jetons sont stockĂ©s et accessibles de maniĂšre sĂ©curisĂ©e.
- Points de Terminaison API : L'exemple suppose un seul domaine d'API. En rĂ©alitĂ©, vous pourriez avoir des API rĂ©gionales, et la logique d'interception devrait en tenir compte, en utilisant potentiellement l'URL de la requĂȘte pour dĂ©terminer quel domaine d'API utiliser.
- Actions Utilisateur Hors Ligne : Pour des actions comme l'ajout au panier hors ligne, vous mettriez généralement les actions en file d'attente dans `IndexedDB` et les synchroniseriez lorsque la connexion est rétablie. Le Service Worker peut détecter l'état en ligne/hors ligne et gérer cette file d'attente.
Implémenter la Mise en Cache pour le Contenu Internationalisé
Lorsque vous traitez avec un public mondial, votre application sert probablement du contenu dans plusieurs langues et régions. Les stratégies de mise en cache doivent s'adapter à cela.
Varier les RĂ©ponses en Fonction des En-tĂȘtes
Lors de la mise en cache de contenu internationalisĂ©, il est crucial de s'assurer que la rĂ©ponse en cache correspond aux prĂ©fĂ©rences de langue et de locale de la requĂȘte. L'en-tĂȘte `Accept-Language` est essentiel ici. Vous pouvez l'utiliser dans vos appels `caches.match`.
// à l'intérieur d'un gestionnaire d'événement fetch dans sw.js
self.addEventListener('fetch', function(event) {
const request = event.request;
const url = new URL(request.url);
if (url.pathname.startsWith('/api/content')) {
event.respondWith(
caches.open(CACHE_NAME).then(function(cache) {
// CrĂ©er une clĂ© qui inclut l'en-tĂȘte Accept-Language pour varier les entrĂ©es de cache
const cacheKey = new Request(request.url, {
headers: {
'Accept-Language': request.headers.get('Accept-Language') || 'en-US'
}
});
return cache.match(cacheKey).then(function(cachedResponse) {
if (cachedResponse) {
console.log('Serving from cache for locale:', request.headers.get('Accept-Language'));
// Potentiellement revalider en arriĂšre-plan si stale-while-revalidate
return cachedResponse;
}
// Récupérer depuis le réseau et mettre en cache avec une clé spécifique à la locale
return fetch(request).then(function(networkResponse) {
if (networkResponse.ok) {
// Cloner la réponse pour la mise en cache
const responseToCache = networkResponse.clone();
cache.put(cacheKey, responseToCache);
}
return networkResponse;
});
});
})
);
} else {
event.respondWith(fetch(request));
}
});
Considérations mondiales :
- En-tĂȘte `Accept-Language` : Assurez-vous que votre backend traite correctement l'en-tĂȘte `Accept-Language` pour servir le contenu localisĂ© appropriĂ©. Le cĂŽtĂ© client (navigateur) envoie souvent cet en-tĂȘte automatiquement en fonction des paramĂštres du systĂšme d'exploitation/navigateur de l'utilisateur.
- En-tĂȘte `Vary` : Lorsque vous servez du contenu depuis un serveur qui doit respecter la mise en cache basĂ©e sur des en-tĂȘtes comme `Accept-Language`, assurez-vous que le serveur inclut un en-tĂȘte `Vary: Accept-Language` dans ses rĂ©ponses. Cela indique aux caches intermĂ©diaires (y compris le cache HTTP du navigateur et le cache du Service Worker) que le contenu de la rĂ©ponse peut varier en fonction de cet en-tĂȘte.
- Contenu Dynamique vs. Ressources Statiques : Les ressources statiques comme les images ou les polices n'ont peut-ĂȘtre pas besoin de varier selon la locale, ce qui simplifie leur mise en cache. Le contenu dynamique, cependant, bĂ©nĂ©ficie grandement d'une mise en cache sensible Ă la locale.
Outils et BibliothĂšques
Bien que vous puissiez construire une logique sophistiquĂ©e d'interception de requĂȘtes et de mise en cache directement avec les Service Workers et l'API Fetch, plusieurs bibliothĂšques peuvent simplifier le processus :
- Workbox : Un ensemble de bibliothĂšques et d'outils de Google qui facilite la mise en Ćuvre d'un Service Worker robuste. Il fournit des stratĂ©gies de mise en cache prĂ©-construites, du routage et d'autres utilitaires utiles, rĂ©duisant considĂ©rablement le code rĂ©pĂ©titif. Workbox abstrait une grande partie de la complexitĂ© du cycle de vie des Service Workers et de la gestion du cache.
- Axios : Bien que non directement liĂ© aux Service Workers, Axios est un client HTTP populaire qui offre des intercepteurs intĂ©grĂ©s pour les requĂȘtes et les rĂ©ponses. Vous pouvez utiliser Axios en conjonction avec un Service Worker pour une gestion plus rationalisĂ©e des requĂȘtes rĂ©seau cĂŽtĂ© client.
Exemple avec Workbox
Workbox simplifie considérablement les stratégies de mise en cache :
// Dans sw.js (en utilisant Workbox)
importScripts('https://storage.googleapis.com/workbox-cdn/releases/6.0.0/workbox-sw.js');
const CACHE_NAME = 'my-app-v2';
// Pré-mise en cache des ressources essentielles
workbox.precaching.precacheAndRoute([
'/', '/index.html', '/styles.css', '/app.js',
'/images/logo.png'
]);
// Mettre en cache les requĂȘtes API avec stale-while-revalidate
workbox.routing.registerRoute(
/https:\/\/api\.globalstore\.com\/api\/products/, // Regex pour correspondre aux URL de l'API des produits
new workbox.strategies.StaleWhileRevalidate({
cacheName: CACHE_NAME,
plugins: [
// Optionnellement, ajouter la mise en cache pour différentes locales si nécessaire
// new workbox.cacheableResponse.CacheableResponsePlugin({
// statuses: [0, 200]
// })
]
})
);
// Mettre en cache les données spécifiques à l'utilisateur avec la stratégie network-first
workbox.routing.registerRoute(
/https:\/\/api\.globalstore\.com\/api\/(user|cart)/, // Regex pour l'API utilisateur/panier
new workbox.strategies.NetworkFirst({
cacheName: CACHE_NAME,
plugins: [
new workbox.expiration.ExpirationPlugin({
// Mettre en cache seulement 5 entrées, expire aprÚs 30 jours
maxEntries: 5,
maxAgeSeconds: 30 * 24 * 60 * 60, // 30 jours
}),
new workbox.cacheableResponse.CacheableResponsePlugin({
statuses: [0, 200]
})
]
})
);
// Network-only pour les opérations critiques (exemple)
workbox.routing.registerRoute(
/https:\/\/api\.globalstore\.com\/api\/order/,
new workbox.strategies.NetworkOnly()
);
// Gestionnaire personnalisĂ© pour ajouter l'en-tĂȘte d'autorisation Ă toutes les requĂȘtes API
workbox.routing.registerRoute(
/https:\/\/api\.globalstore\.com/,
new workbox.strategies.NetworkFirst({
cacheName: CACHE_NAME,
plugins: [
{
requestWillFetch: async ({ request, url, event, delta }) => {
const token = localStorage.getItem('authToken');
const headers = new Headers(request.headers);
if (token) {
headers.set('Authorization', `Bearer ${token}`);
}
return new Request(url, { ...request, headers });
}
}
]
})
);
ConsidĂ©rations mondiales : Les configurations de Workbox peuvent ĂȘtre adaptĂ©es aux besoins internationaux. Par exemple, vous pourriez utiliser le routage avancĂ© de Workbox pour servir diffĂ©rentes versions du cache en fonction de la langue ou de la rĂ©gion dĂ©tectĂ©e de l'utilisateur, le rendant trĂšs adaptable Ă une base d'utilisateurs mondiale.
Bonnes Pratiques et Considérations pour les Applications Mondiales
Lors de la mise en Ćuvre de l'interception de requĂȘtes et de la mise en cache des rĂ©ponses pour un public mondial, gardez ces bonnes pratiques Ă l'esprit :
- AmĂ©lioration Progressive : Assurez-vous que votre application est fonctionnelle mĂȘme sans fonctionnalitĂ©s avancĂ©es comme les Service Workers. Les fonctionnalitĂ©s de base devraient fonctionner sur les anciens navigateurs et dans les environnements oĂč les Service Workers pourraient ĐœĐ” ĂȘtre pris en charge.
- SĂ©curitĂ© : Soyez extrĂȘmement prudent lors de la manipulation de donnĂ©es sensibles comme les jetons d'authentification pendant l'interception de requĂȘtes. Stockez les jetons de maniĂšre sĂ©curisĂ©e (par ex., en utilisant des cookies HttpOnly le cas Ă©chĂ©ant, ou des mĂ©canismes de stockage sĂ©curisĂ©s). Ne jamais coder en dur des secrets.
- Invalidation du Cache : La mise en Ćuvre d'une stratĂ©gie d'invalidation de cache robuste est cruciale. Des donnĂ©es pĂ©rimĂ©es peuvent ĂȘtre pires que pas de donnĂ©es du tout. Envisagez l'expiration basĂ©e ĐœĐ° le temps, le versionnement et l'invalidation Ă©vĂ©nementielle.
- Surveillance des Performances : Surveillez en continu les performances de votre application dans différentes régions et conditions de réseau. Des outils comme Lighthouse, WebPageTest et RUM (Real User Monitoring) sont inestimables.
- Gestion des Erreurs : Concevez votre logique d'interception et de mise en cache pour gérer gracieusement les erreurs réseau, les problÚmes de serveur et les réponses inattendues. Fournissez des expériences de repli significatives pour les utilisateurs.
- Importance de l'En-tĂȘte `Vary` : Pour les rĂ©ponses en cache qui dĂ©pendent des en-tĂȘtes de requĂȘte (comme `Accept-Language`), assurez-vous que votre backend envoie correctement l'en-tĂȘte `Vary`. C'est fondamental pour un comportement de mise en cache correct selon les diffĂ©rentes prĂ©fĂ©rences des utilisateurs.
- Optimisation des Ressources : Ne mettez en cache que ce qui est nécessaire. Les grandes ressources qui changent peu sont de bons candidats pour une mise en cache agressive. Les données dynamiques qui changent fréquemment nécessitent des stratégies de mise en cache plus dynamiques.
- Taille du Bundle : Soyez attentif Ă la taille de votre script de Service Worker lui-mĂȘme. Un SW trop volumineux peut ĂȘtre lent Ă installer et Ă activer, ce qui affecte l'expĂ©rience utilisateur initiale.
- ContrĂŽle Utilisateur : Envisagez de donner aux utilisateurs un certain contrĂŽle sur le comportement de la mise en cache si applicable, bien que cela soit moins courant pour les applications web typiques.
Conclusion
L'interception de requĂȘtes et la mise en cache des rĂ©ponses, en particulier lorsqu'elles sont alimentĂ©es par les Service Workers et l'API Fetch, sont des outils indispensables pour construire des applications web mondiales performantes et rĂ©silientes. En interceptant les requĂȘtes, vous gagnez le contrĂŽle sur la maniĂšre dont votre application communique avec les serveurs, permettant des ajustements dynamiques pour l'authentification, le routage, et plus encore. En mettant en Ćuvre des stratĂ©gies de mise en cache intelligentes, vous amĂ©liorez considĂ©rablement les temps de chargement, permettez l'accĂšs hors ligne et rĂ©duisez la charge du serveur.
Pour un public international, ces techniques ne sont pas de simples optimisations ; elles sont fondamentales pour offrir une expérience utilisateur cohérente et positive, indépendamment de la situation géographique ou des conditions du réseau. Que vous construisiez une plateforme de commerce électronique mondiale, un portail d'actualités riche en contenu ou une application SaaS, la maßtrise des capacités avancées de l'API Fetch distinguera votre application.
N'oubliez pas de tirer parti d'outils comme Workbox pour accélérer le développement et garantir la robustesse de vos stratégies. Testez et surveillez en permanence les performances de votre application dans le monde entier pour affiner votre approche et offrir la meilleure expérience possible à chaque utilisateur.