Le cache navigateur évite de retélécharger un fichier déjà vu sur cet appareil. Il accélère la deuxième visite, pas la première. On le règle avec l’en-tête Cache-Control, fichier par fichier : longue durée pour une feuille de style au nom versionné, durée nulle pour une page qui contient un panier ou un jeton.
Ce que le navigateur a le droit de garder
max-age est une durée en secondes. public autorise aussi les caches partagés (proxy, CDN). private limite au navigateur de l’utilisateur. no-store interdit de conserver la réponse. immutable dit que le contenu de cette URL ne changera pas pendant max-age, ce qui évite une revalidation inutile.
s-maxage ne concerne que les caches partagés. Il ne remplace pas max-age pour le navigateur. Si vous ne servez que le navigateur, max-age suffit.
Sans changement de nom de fichier, un max-age d’un an fige le CSS chez ceux qui sont déjà venus. La règle tient en une phrase : l’URL change quand le contenu change, ou la durée reste courte.
Une durée par type de ressource
| Ressource | En-tête type | Pourquoi |
|---|---|---|
| CSS, JS, police, avec empreinte dans le nom | public, max-age=31536000, immutable | L’ancienne URL peut rester en cache, la nouvelle a un autre nom |
| Image de contenu (photo article, vignette) | public, max-age=2592000 | 30 jours, sauf si l’URL est elle aussi versionnée |
| HTML public et stable | no-cache ou max-age=0, must-revalidate | Le navigateur redemande, le serveur peut répondre 304 |
| HTML de compte, panier, formulaire avec jeton | private, no-store | Rien ne doit être rejoué depuis le disque |
| Réponse d’API liée à un utilisateur | private, no-store | Même motif |
no-cache ne veut pas dire « ne pas stocker ». Cela veut dire « revalider avant de réutiliser ». Pour un secret ou un panier, c’est no-store qu’il faut.
Les images lourdes restent lourdes au premier téléchargement : le cache n’enlève pas le besoin de les encoder correctement, voir Images WebP et AVIF. La compression du transfert (Brotli ou gzip) est indépendante et se règle à part : Brotli et gzip.
Où poser l’en-tête
Sur Nginx, un bloc par extension versionnée :
add_header Cache-Control "public, max-age=31536000, immutable"; dans le location des assets au nom hashé.
Sur Apache, Header set Cache-Control "public, max-age=31536000, immutable" dans un FilesMatch limité à ces fichiers.
Ne posez pas cet en-tête sur tout le virtual host. Le HTML hériterait d’un an de cache, y compris une page de connexion. Vérifiez avec une requête qui ne suit que les en-têtes :
curl -sI https://exemple.fr/assets/app.8f3a1c.js
Vous devez voir cache-control et un code 200 (ou 304 si vous revalidez). Contrôlez aussi la page HTML : elle ne doit pas annoncer un an. Un test d’URL sur la page et sur un asset montre tout de suite si les deux répondent la même politique par erreur.
Les constructeurs (Vite, webpack, le versioning de nombreux thèmes) ajoutent l’empreinte au nom. Si le thème sert style.css sans query ni hash, gardez un max-age de quelques heures, ou ajoutez le versioning. Un query string ?v=3 est moins fiable qu’un nom de fichier : certains caches l’ignorent ou le normalisent.
Vider, ou mieux, changer l’URL
Demander aux visiteurs de vider leur cache n’est pas une mise en production. Au déploiement, le HTML neuf pointe vers app.9b2.js. Ceux qui ont encore app.8f3.js le gardent, sans impact. Ceux qui rechargent le HTML obtiennent le nouveau nom.
Le seul cas où un vidage distant est nécessaire : vous avez déjà envoyé un long max-age sur une URL que vous devez corriger d’urgence (fichier légal, script cassé). Vous ne pouvez pas l’effacer des disques. Vous publiez une nouvelle URL et vous faites pointer le HTML dessus. L’ancienne finit par expirer.
Après la mise en ligne, rechargez une fois en navigation privée (cache vide) et une fois en navigation normale. La première mesure le coût à froid. La seconde doit redescendre fortement sur les CSS et JS inchangés. Si la seconde retélécharge tout, l’en-tête n’est pas celui que vous croyez : relisez curl -sI.
