En-têtes HTTP Cache-Control Ce que signifient réellement no-cache, no-store et max-age
Une analyse pratique des directives Cache-Control — ce que les navigateurs et les CDNs font réellement avec no-cache, no-store, max-age, s-maxage et les ETags. Y compris les erreurs qui frappent la plupart des développeurs en production.
Vous avez probablement écrit Cache-Control: no-cache et supposé que le navigateur ignorerait entièrement le cache. Ce n'est pas le cas. no-cache signifie « révalider avant de servir depuis le cache ». Si vous souhaitez que la réponse ne soit jamais stockée, c’est no-store. Cette confusion provoque des bugs de données périmées en production, difficiles à diagnostiquer car tout semble correct dans le volet Réseau lors de la première charge.
Examinons chaque directive de manière claire — ce que les navigateurs font avec, ce que les CDNs font avec, et les erreurs qui surviennent lorsqu’on les mélange.
Référence complète des directives Cache-Control
| Directive | Comportement du navigateur | Comportement du CDN / proxy | Utilisation typique |
|---|---|---|---|
max-age=N | Mettre en cache pendant N secondes | Mettre en cache pendant N secondes (sauf si s-maxage survient) | Actifs statiques, réponses API |
s-maxage=N | Ignoré | Mettre en cache pendant N secondes | TTL du CDN séparé du navigateur |
no-cache | Mettre en cache mais révalider chaque requête | Mettre en cache mais révalider chaque requête | Contenu fréquemment modifié avec des ETags |
no-store | Ne pas stocker partout | Ne pas stocker partout | Réponses d’authentification, données sensibles des utilisateurs |
must-revalidate | Pas de livraison périmée — révalider ou échouer | Pas de livraison périmée — révalider ou échouer | Réponses API où les données périmées sont défectueuses |
proxy-revalidate | Ignoré | Pas de livraison périmée dans les caches partagés | Doit révalider pour les CDNs spécifiques |
private | Le navigateur peut mettre en cache | Ne pas mettre en cache | Pages spécifiques à l’utilisateur |
public | Tout cache peut stocker | Peut mettre en cache | Ressources statiques partagées |
immutable | Ne jamais révalider au sein de max-age | Varie selon le CDN | Actifs hashés ou versionnés |
stale-while-revalidate=N | Servir une version périmée pendant N secondes tout en récupérant une version fraîche | Varie selon le CDN | Vitesse sans périmé rigide |
max-age et s-maxage : TTL du navigateur vs TTL du CDN
max-age=N indique combien de secondes la réponse est fraîche. Après N secondes, la réponse en cache est périmée et doit être révalider avant d’être utilisée.
s-maxage=N est spécifique au CDN. Les navigateurs l’ignorent entièrement. Si vous souhaitez que le CDN mette en cache pendant une heure mais que le navigateur ne le fasse que pendant 5 minutes :
Cache-Control: max-age=300, s-maxage=3600
Le navigateur met en cache pendant 5 minutes. CloudFront, Fastly, Nginx — ils utilisent 3600 secondes. Une erreur courante : définir max-age=0 en pensant qu’elle désactive le cache. Elle ne le fait pas. max-age=0 signifie que la réponse est immédiatement périmée — le navigateur la met toujours en cache et la révalide, probablement en recevant une réponse 304. Si vous ne souhaitez pas que cette réponse soit mise en cache, utilisez no-store.
no-cache : Ce que vous ne pensez pas
Cache-Control: no-cache ne signifie pas « ne pas utiliser le cache ». Il signifie « ne pas servir depuis le cache sans d’abord révalider avec le serveur ».
La séquence lorsqu’un navigateur a une réponse en cache avec no-cache:
- Une nouvelle requête arrive pour la ressource en cache
- Le navigateur envoie l’ETag de la réponse en cache (via
If-None-Match) ou la date de modification dernière à l’serveur - Si le contenu n’a pas changé → le serveur retourne
304 Not Modifiedsans corps - Si le contenu a changé → le serveur retourne
200 OKavec la nouvelle réponse
L’avantage par rapport à no-store: vous obtenez toujours les économies de bande passante des réponses 304. Si votre contenu change occasionnellement mais doit être frais lorsqu’il change, no-cache en combinaison avec un ETag est la combinaison idéale.
no-store : Le véritable « Ne pas mettre en cache »
Cache-Control: no-store signifie que la réponse ne doit pas être stockée nulle part — ni dans le cache du navigateur, ni dans un CDN, ni dans un proxy intermédiaire. Aucune copie, c’est terminé.
Utilisez cela pour :
- Réponses d’authentification (jets de connexion, données de session)
- Données personnelles sensibles
- Contenu unique (confirmations de paiement, pages OTP)
Un détail subtil : no-store ne empêche pas une page d’apparaître dans le cache de navigation du navigateur (bfcache). Les navigateurs conservent une snapshot en mémoire pour des performances de navigation, distincte du cache HTTP. Si vous devez gérer les problèmes de bouton retour après déconnexion, intégrez l’événement pageshow et vérifiez event.persisted.
must-revalidate : Aucune grâce périmée
Les spécifications HTTP de mise en cache permettent aux caches de servir des réponses périmées lorsque le serveur d’origine est inaccessibles — une fonctionnalité de résilience que la plupart des développeurs ne connaissent pas. must-revalidate supprime cette marge : une fois qu’une réponse en cache est périmée, le cache doit révalider ou retourner une erreur 504. Aucune livraison périmée dans aucun cas.
# Without must-revalidate: CDN may serve stale if origin is slow or down
Cache-Control: max-age=3600
# With must-revalidate: stale = error, not a fallback
Cache-Control: max-age=3600, must-revalidate
Utilisez cela pour les réponses API où la livraison de données périmées détruit la fonctionnalité — les comptes d’inventaire, les prix, l’état d’authentification — plutôt que simplement de paraître légèrement incorrects.
private vs public : L’erreur du CDN qui fuit les données utilisateur
private signifie que la réponse est destinée à un utilisateur spécifique. Les navigateurs peuvent la mettre en cache ; les caches partagés (CDNs, reverse proxies) doivent ne pas le faire.
public autorise explicitement tout cache — y compris les caches partagés — à stocker la réponse. Certains caches ne mettent en cache que les réponses de requêtes authentifiées si vous marquez explicitement public.
La vraie erreur dans le monde réel : un développeur copie-collé Cache-Control: public, max-age=3600 d’un actif statique sur une page incluant des données spécifiques à l’utilisateur. Le CDN met en cache la réponse. L’utilisateur B fait la même requête et reçoit la page de l’utilisateur A depuis le cache. Ce n’est pas théorique — GitHub a eu une variante de ce cas en 2018. Marquez explicitement les réponses authentifiées ou spécifiques à l’utilisateur private même si vous pensez que votre CDN « sait » ne pas les mettre en cache.
ETags et requêtes conditionnelles
Les ETags sont la manière dont le serveur dit « voici une empreinte de cette réponse ». Le navigateur stocke l’ETag en compagnie de la réponse en cache et l’envoie à nouveau lors de la prochaine requête via If-None-Match. Si le contenu n’a pas changé, le serveur retourne 304 Not Modified sans corps — même contrôle de fraîcheur que no-cache, avec bien moins de bande passante.
Le no-cache + Flux ETag :
→ GET /api/config HTTP/1.1
← HTTP/1.1 200 OK
Cache-Control: no-cache
ETag: "abc123"
[full response body]
→ GET /api/config HTTP/1.1
If-None-Match: "abc123"
← HTTP/1.1 304 Not Modified
[no body — browser uses its cached copy]
Deux types d’ETags :
- ETags forts (
"abc123") — identiques à l’octet par octet. Nécessaires pour le support des demandes de plage dans les CDNs. - ETags faibles (
W/"abc123") — équivalents en sens mais pas nécessairement identiques à l’octet. Adaptables pour la révalidation du navigateur, pas pour les demandes de plage.
Nginx génère automatiquement des ETags à partir de la date de modification du fichier et de sa taille. Express ne les ajoute pas par défaut — utilisez app.set('etag', 'strong') ou le etag middleware explicitement.
Dernière modification et If-Modified-Since
Même concept que les ETags mais plus grossier — basé sur une date plutôt que sur une empreinte de contenu. Le serveur inclut Last-Modified; le navigateur envoie If-Modified-Since sur les requêtes suivantes.
Le problème : si vous redéployez et que les dates de modification des fichiers changent même si le contenu n’a pas changé, les caches sont invalidés de manière inutile. Une empreinte de contenu basée sur un hash évitera ce problème. Utilisez les ETags autant que possible et considérez la dernière modification comme un remplacement pour les serveurs qui ne supportent pas les ETags.
Vary : L’en-tête qui multiplie silencieusement votre cache
Le Vary en-tête indique aux caches que la réponse peut différer selon d'autres en-têtes de requête. Chaque combinaison unique de ces en-têtes obtient une entrée de cache séparée.
Vary: Accept-Encoding
Cela indique aux caches de stocker des réponses séparées pour gzip, brotli et l’encodage identité. Correct et courant. L’un dangereux : Vary: Cookie. Chaque utilisateur a un ensemble de cookies unique, donc chaque utilisateur obtient son propre entrée de cache — ce qui désactive efficacement le cache partagé. Beaucoup de frameworks ajoutent Vary: Cookie sans le savoir. Si votre taux de hit du CDN est inexplicablement faible malgré des valeurs généreuses de max-age , vérifiez les en-têtes de réponse pour Vary: Cookie s’insinuant depuis le middleware de session.
Vary: * signifie « ne pas mettre en cache du tout » en pratique — chaque requête est traitée comme unique. C’est équivalent à no-store pour les CDNs.
Forcer le cache avec des paramètres de requête
Lorsque vous devez forcer des téléchargements frais de ressources versionnées, l’ajout d’un paramètre de requête est l’approche standard — la chaîne de requête fait partie de l’URL, donc elle est traitée comme une ressource nouvelle par les navigateurs et les CDNs :
/app.js?v=2.1.4
/styles.css?hash=a1b2c3d4e5f6
Si vous construisez des paramètres de mise en cache dynamiquement à partir de hashes ou de chaînes de version contenant des caractères spéciaux, assurez-vous de les encoder en pourcentage avant d’ajouter. Le Encodeur/Décodeur d'URL gère rapidement si vous testez ou construisez des URLs manuellement.
Les trois erreurs qui piquent la plupart des développeurs
1. Utiliser no-cache quand on veut no-store. Si vous gérez des réponses d’authentification, des points de sortie ou tout ce qui contient des données personnelles sensibles, vous voulez no-store. no-cache laissant les données dans le cache du navigateur (marquées comme périmées) ; no-store supprime complètement l’empreinte. La différence importe lorsque les utilisateurs partagent des appareils.
2. Ne pas définir s-maxage pour le contrôle du CDN. Sans s-maxage, votre CDN utilise max-age. Si max-age est courte pour la fraîcheur du navigateur (par exemple, 60 secondes), votre CDN met en cache pendant 60 secondes aussi — ce qui n’est probablement pas ce que vous voulez. Séparez explicitement les deux TTL.
3. public sur des endpoints retournant des données utilisateur. C’est l’incident de sécurité, pas simplement un problème de performance. Toute réponse personnalisée ou authentifiée doit être private. Par défaut, utilisez private et optez pour public seulement pour des ressources partagées réellement.
Assemblage de tout cela
Le modèle mental : no-cache est sur la vérification de fraîcheur — la réponse est dans le cache, elle a besoin d’un certificat du serveur avant d’être utilisée. no-store est sur l’absence de trace. max-age est votre TTL du navigateur. s-maxage est votre TTL du CDN. Les ETags sont ce qui rend la révalidation peu coûteuse.
Assurez-vous de bien distinguer sur tout endpoint qui touche les données utilisateur. Cette erreur unique — copier une en-tête de cache d’un actif statique sur un endpoint authentifié — est celle qui devient une fuite de données entre utilisateurs lorsque le CDN commence à mettre en cache des réponses personnalisées. private/public distinction sur n'importe quel point d'entrée qui touche les données des utilisateurs. Cet unique erreur — copier une entête de cache d'un fichier statique sur un point d'entrée authentifié — est celle qui se transforme en fuite de données entre utilisateurs lorsque votre CDN commence à stocker des réponses personnalisées.
Installez nos extensions
Ajoutez des outils IO à votre navigateur préféré pour un accès instantané et une recherche plus rapide
恵 Le Tableau de Bord Est Arrivé !
Tableau de Bord est une façon amusante de suivre vos jeux, toutes les données sont stockées dans votre navigateur. D'autres fonctionnalités arrivent bientôt !
Outils essentiels
Tout voir Nouveautés
Tout voirMise à jour: Notre dernier outil a été ajouté le 18 juin 2026
