Cache-Control est un en-tête HTTP qui définit les règles de mise en cache des ressources côté client, serveurs proxy et CDN à l’aide d’un ensemble de directives. Contrairement à l’en-tête obsolète Expires, Cache-Control prend en charge des dizaines de combinaisons : max-age définit la durée de vie en secondes, private et public contrôlent la disponibilité du cache, no-cache et no-store — vérification forcée. Selon Google Web Dev (2025), une configuration correcte de Cache-Control peut réduire le temps de chargement des pages de 50 à 80 % pour les visites répétées. Cela rend l’en-tête crucial pour les performances des applications Web et mobiles.
Points clés
Cache-Control est un en-tête HTTP, normalisé dans HTTP/1.1 (RFC 7234), qui permet au serveur de spécifier comment et pendant combien de temps les clients, les proxy et les CDN peuvent mettre en cache la réponse. Contrairement à Expires (HTTP/1.0), Cache-Control utilise des directives — des commandes textuelles combinées avec des virgules : Cache-Control: public, max-age=3600, must-revalidate. L’en-tête offre un contrôle précis sur chaque maillon de la chaîne de mise en cache.
La mise en cache est l’un des mécanismes fondamentaux des performances des applications Web et mobiles. Sans elle, chaque requête utilisateur irait directement au serveur, provoquant une charge excessive et de la latence. Cache-Control définit trois niveaux de cache : navigateur/application (cache privé), serveurs proxy (cache partagé) et CDN (cache distribué). Chaque niveau interprète les directives différemment.
Une configuration incorrecte de Cache-Control est l’une des causes les plus courantes de problèmes de performance. Un cache trop agressif fait que les utilisateurs voient des données obsolètes. Un cache trop faible entraîne des requêtes excessives au serveur et un chargement lent. Selon Akamai (2025), l’optimisation de Cache-Control pour le contenu statique réduit la charge du serveur de 70 à 90 % et améliore le temps de chargement de 40 à 60 % pour les utilisateurs mobiles.
Cache-Control est apparu dans HTTP/1.1 (RFC 2616, 1999) en remplacement d’Expires. Expires avait un problème fondamental : il utilisait une date absolue qui dépendait des fuseaux horaires du serveur et du client. Cache-Control a résolu ce problème en passant au temps relatif (max-age en secondes à partir du moment de la réception de la réponse). Plus tard, dans RFC 7234 (2014), de nouvelles directives ont été ajoutées : immutable pour les ressources statiques, stale-while-revalidate et stale-if-error pour la validation différée.
Cache-Control comprend plus de 10 directives réparties en trois groupes : les directives de requête (client → serveur), les directives de réponse (serveur → client) et les extensions. En pratique, le développement mobile utilise 6 à 7 directives de réponse principales qui couvrent 95 % des scénarios de cache. Examinons chacune avec des exemples et des recommandations.
| Directive | Signification | Exemple |
|---|---|---|
| max-age | Durée de vie en secondes à partir de la réponse | max-age=3600 — 1 heure |
| s-maxage | max-age pour le cache partagé (proxy, CDN) | s-maxage=86400 — 1 jour pour CDN |
| public | Autorise la mise en cache par tous (y compris les proxy) | public, max-age=3600 |
| private | Autorise le cache uniquement pour le navigateur/l’application | private, max-age=600 |
| no-cache | Ne pas utiliser sans validation (304 requis) | no-cache |
| no-store | Interdire complètement la mise en cache | no-store |
| must-revalidate | Après max-age, doit revalider auprès de l’origine | max-age=3600, must-revalidate |
| immutable | La ressource ne changera pas (pour les ressources statiques versionnées) | max-age=31536000, immutable |
max-age est la directive la plus importante. Elle interdit au client d’effectuer une requête au serveur pendant la durée spécifiée. Pour les ressources statiques (CSS, JS, images), max-age est généralement défini de 1 jour à 1 an. Pour les réponses API — de 0 seconde (données toujours fraîches) à 5-10 minutes (données de référence). s-maxage permet de définir différentes durées de vie pour le CDN et le navigateur : le CDN stocke une copie pendant 1 jour, le navigateur pendant 1 heure.
Ces deux directives sont souvent confondues. no-cache n’interdit pas la mise en cache — il exige de valider la copie mise en cache à chaque utilisation via une requête conditionnelle (If-Modified-Since ou If-None-Match). Si le serveur répond avec 304 — le client utilise le cache. Si 200 — il le met à jour. no-store, quant à lui, interdit complètement de sauvegarder la réponse dans un cache, y compris sur le disque et en mémoire. Utilisez no-store uniquement pour les données sensibles — jetons, données de paiement, documents personnels.
L’en-tête Expires (HTTP/1.0) spécifie également la durée de vie de la ressource mais utilise une date absolue : Expires: Thu, 03 Jul 2026 12:00:00 GMT. Cache-Control max-age utilise le temps relatif à partir du moment de la réponse. La différence est cruciale pour les systèmes distribués : si le serveur et le client sont dans des fuseaux horaires différents, Expires peut être mal interprété. Cache-Control n’a pas ce problème — 3600 secondes sont toujours 3600 secondes.
Lorsque les deux en-têtes sont présents, Cache-Control a priorité sur Expires. Ceci est défini dans RFC 7234 : « Si une réponse inclut un champ Cache-Control avec la directive max-age, le destinataire DOIT ignorer le champ Expires. » En pratique, il est recommandé de ne pas renvoyer Expires pour les clients modernes, car Cache-Control couvre tous les scénarios d’Expires. Cependant, pour la compatibilité ascendante avec les anciens proxy et navigateurs, les deux en-têtes peuvent être renvoyés.
Expires a survécu principalement pour le contenu statique sur Nginx et Apache — ces serveurs ajoutent automatiquement les deux en-têtes. Si votre projet rencontre Expires sans Cache-Control, remplacez-le par Cache-Control avec max-age : la précision du contrôle du cache s’améliore et la dépendance au fuseau horaire est éliminée. Pour la migration, il suffit de configurer le serveur pour ajouter Cache-Control au lieu d’Expires.
# Nginx : Cache-Control pour les fichiers statiques
location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
expires 30d;
add_header Cache-Control "public, immutable, max-age=2592000";
}
# Différentes politiques pour différents types de contenu
location /api/config {
expires -1;
add_header Cache-Control "no-cache, must-revalidate";
}
location /api/static-data {
expires 5m;
add_header Cache-Control "public, max-age=300";
}
Dans la configuration Nginx, les fichiers statiques (CSS, JS, images) sont définis avec Cache-Control pendant 30 jours avec l’attribut immutable — cet attribut indique au navigateur que la ressource ne change jamais à cette URL (versionnage via hash dans le nom du fichier). Les points de terminaison API utilisent no-cache pour les données dynamiques et public avec un max-age court pour les données de référence — listes fréquemment demandées et rarement modifiées.
Dans les applications mobiles, Cache-Control joue un rôle particulier en raison des limites des réseaux mobiles : latence élevée, connexion instable, limites de trafic. Une mise en cache appropriée permet d’afficher les données à l’utilisateur instantanément, même hors ligne, et de les mettre à jour en arrière-plan. OkHttp sur Android et URLSession sur iOS disposent de systèmes de cache intégrés qui respectent Cache-Control.
OkHttp utilise CacheInterceptor, qui lit Cache-Control à partir de la réponse et gère automatiquement la mise en cache. Si le serveur a renvoyé Cache-Control: max-age=3600, OkHttp n’effectuera pas de requête au serveur pendant une heure. Après l’expiration de max-age, OkHttp envoie une requête conditionnelle avec If-Modified-Since et If-None-Match. Configuration du cache dans OkHttp : OkHttpClient.Builder().cache(Cache(directory, maxSize)).
fun createCachedClient(cacheDir: File): OkHttpClient {
return OkHttpClient.Builder()
.cache(Cache(cacheDir, 10L * 1024 * 1024))
.addNetworkInterceptor { chain ->
val response = chain.proceed(chain.request())
response.newBuilder()
.header("Cache-Control",
"public, max-age=300")
.removeHeader("Pragma")
.build()
}
.build()
}
Le code crée un OkHttpClient avec un cache de 10 Mo et remplace Cache-Control via NetworkInterceptor. Si le serveur ne renvoie pas Cache-Control ou utilise Expires, l’intercepteur ajoute public, max-age=300 (5 minutes). L’intercepteur supprime l’en-tête obsolète Pragma (HTTP/1.0) pour la compatibilité. La mise en cache sur iOS fonctionne de manière similaire via URLCache.shared avec les paramètres memoryCapacity et diskCapacity.
La directive stale-while-revalidate permet d’afficher à l’utilisateur un cache obsolète pendant que l’application récupère de nouvelles données en arrière-plan. Cela procure un effet de réponse instantanée : l’utilisateur voit le contenu immédiatement et, après une seconde, il est mis à jour vers la version actuelle. Pris en charge par OkHttp à partir de la version 3.10 et URLCache sur iOS 14+. Exemple : Cache-Control: max-age=3600, stale-while-revalidate=300 — 1 heure de cache actualisé, puis 5 minutes d’affichage de données obsolètes avec actualisation en arrière-plan.
Différents types de ressources nécessitent différentes stratégies de mise en cache. Examinons les configurations optimales pour les scénarios typiques du développement mobile. Pour le contenu statique avec un hash dans le nom du fichier (bundle.abc123.js), vous pouvez définir max-age jusqu’à 1 an avec immutable. Pour les listes API rarement mises à jour (annuaires, catégories) — max-age de 5 minutes à 1 heure avec stale-while-revalidate.
| Type de ressource | Cache-Control | Explication |
|---|---|---|
| Ressources statiques versionnées | public, max-age=31536000, immutable | 1 an, les fichiers ne changent pas (hash dans l’URL) |
| Ressources statiques non versionnées | public, max-age=86400, must-revalidate | 1 jour avec revalidation forcée après |
| API : données de référence | public, max-age=600, stale-while-revalidate=60 | 10 minutes de cache + 1 minute obsolète |
| API : données utilisateur | private, max-age=60 | 1 minute, uniquement pour un utilisateur spécifique |
| API : données sensibles | no-store | Interdiction complète de mise en cache |
| Pages HTML | no-cache, must-revalidate | Validation à chaque requête, 304 si inchangé |
Il est important de se souvenir de la sécurité : pour les réponses contenant des données personnelles de l’utilisateur, définissez toujours private. Sans cette directive, un proxy public (par exemple, d’entreprise) peut mettre en cache la réponse et la fournir à un autre utilisateur. Pour les jetons d’authentification et les informations de paiement, utilisez no-store — même un cache privé ne doit pas stocker ces données sur le disque.
Pour vérifier l’exactitude de Cache-Control, utilisez l’en-tête Age (combien de secondes le cache a été stocké) et X-Cache (hit/miss sur le CDN). Dans le navigateur — l’onglet Réseau, la colonne Taille affiche « from disk cache » ou « 304 Not Modified ». Si une ressource devrait être mise en cache mais se charge à chaque fois, vérifiez si le serveur ajoute Cache-Control: no-cache ou Pragma: no-cache avec vos directives.
Foire aux questions
max-age s’applique à tous les caches (y compris les navigateurs), s-maxage s’applique uniquement aux caches partagés (proxy, CDN). Si s-maxage est spécifié, le CDN ignore max-age et utilise s-maxage. Cela permet de définir des durées de vie différentes pour le navigateur et le CDN.
Non, après avoir envoyé une réponse avec max-age, le client n’effectuera pas de requête jusqu’à l’expiration du minuteur. Pour une invalidation immédiate du cache, vous devez modifier l’URL de la ressource (ajouter une version/hash) et envoyer des notifications push ou des messages WebSocket pour une réinitialisation forcée.
La directive immutable (RFC 8246) indique au navigateur que la ressource ne changera jamais à cette URL. Le navigateur n’essaie même pas d’effectuer une requête conditionnelle lors de l’actualisation de la page — il utilise le cache jusqu’à l’expiration de max-age. Fonctionne uniquement avec les fichiers versionnés.
Googlebot prend en compte Cache-Control : une mise en cache longue accélère le crawling répété. noindex avec un cache rapide est acceptable. no-store peut ralentir l’indexation car Googlebot chargera la page à partir de zéro à chaque fois. Un max-age trop court augmente la charge du serveur lors du crawling.
Via helmet ou un middleware : res.set('Cache-Control', 'public, max-age=3600'). Pour les fichiers statiques, utilisez express.static avec le paramètre maxAge : express.static('public', {maxAge: '1y'}). Pour les routes dynamiques — individuellement dans chaque gestionnaire.
Résumé
Nous développerons une application mobile clé en main
IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.
Lisez aussi