L’en-tête HTTP Cache-Control, celui qu’on désigne parfois par cache control header, décide combien de temps une réponse peut rester en mémoire, qui peut la réutiliser et si elle doit être revérifiée avant d’être servie. Sur une API, ce réglage influence à la fois les performances, la charge serveur et la sécurité des données exposées. Ici, je vais aller droit au but: comprendre les directives utiles, choisir la bonne politique selon le type de réponse et éviter les erreurs qui ruinent un cache ou, pire, exposent des informations.
Les bons réglages de cache reposent surtout sur la fraîcheur, la portée et le niveau de risque
- Cache-Control se lit toujours avec le contexte: navigateur, CDN, proxy, API.
-
no-cachepermet de stocker puis de revalider;no-storeinterdit le stockage. -
privateprotège les réponses personnalisées;publicouvre la porte aux caches partagés. -
max-age,s-maxage,stale-while-revalidateetimmutableservent surtout à réduire les allers-retours inutiles. -
Varyest essentiel dès qu’une réponse change selon les cookies ou l’identité.
Ce que contrôle réellement cet en-tête
Je l’utilise comme un contrat entre le serveur et les caches: combien de temps une réponse peut être gardée, si elle peut être partagée entre utilisateurs et si elle doit être revérifiée avant d’être réutilisée. Sur une API, cela influence directement les temps de réponse, la bande passante et le nombre de hits sur l’origine.
La distinction la plus importante est simple: un cache privé vit dans le navigateur, alors qu’un cache partagé peut être utilisé par un CDN ou un proxy entre votre serveur et plusieurs clients. C’est pour ça qu’une réponse de profil utilisateur, même si elle est rapide à servir, ne doit jamais être traitée comme un catalogue public.
Je préfère aussi être explicite plutôt que de laisser le serveur ou le proxy deviner. Sans politique claire, vous obtenez parfois un comportement heuristique: acceptable pour un site statique, dangereux pour une API qui sert des données sensibles ou qui change souvent. La logique à garder en tête est la suivante: stocker, revalider ou interdire. C’est exactement ce que l’on va détailler maintenant avec les directives utiles.

Les directives qui comptent vraiment côté API
Je ne traite pas toutes les directives comme équivalentes. Certaines bloquent la conservation, d’autres optimisent la réutilisation et d’autres encore ajoutent une tolérance utile quand un backend a des pics ou des incidents.
| Directive | Effet concret | Quand je l’utilise | Piège fréquent |
|---|---|---|---|
no-store |
La réponse ne doit pas être stockée par aucun cache. | Données très sensibles, jetons, informations de session, réponses qui ne doivent laisser aucune trace locale. | Beaucoup l’emploient par réflexe, alors qu’il est plus fort que nécessaire. |
no-cache |
La réponse peut être stockée, mais elle doit être validée avant réutilisation. | Contenu qui doit rester à jour sans interdire le cache, avec ETag ou Last-Modified. |
Ce n’est pas “ne pas cacher”. C’est “ne pas réutiliser sans vérification”. |
max-age=... |
Durée pendant laquelle la réponse reste fraîche. | Ressources publiques, listes, pages stables, données dont la cadence de mise à jour est connue. | Un TTL trop long sur une donnée volatile crée des réponses obsolètes. |
private |
La réponse ne doit être stockée que par un cache privé, côté navigateur. | Réponses personnalisées, vues après connexion, contenu dépendant d’un cookie. | Sans cette directive, une réponse individualisée peut fuiter vers un cache partagé. |
public |
La réponse peut être conservée par un cache partagé. | Contenu identique pour tout le monde, ou réponse que vous voulez rendre cacheable au bord du réseau. | À éviter sur une réponse qui dépend de l’identité de l’utilisateur. |
s-maxage=... |
TTL spécifique aux caches partagés, qui prime sur max-age pour eux. |
Quand vous voulez une durée différente entre navigateur et CDN. | Ignoré par les caches privés, donc inutile si vous n’avez pas de cache partagé. |
immutable |
Le cache peut considérer la réponse comme inchangée tant qu’elle est fraîche. | Fichiers fingerprintés ou versionnés, comme des bundles JS ou CSS. | Si l’URL ne change pas quand le contenu change, vous allez garder une mauvaise version. |
stale-while-revalidate=... |
Le cache peut servir une version un peu ancienne pendant qu’il recharge en arrière-plan. | Flux, listes, contenus qui tolèrent quelques secondes de retard. | Très utile pour la fluidité, moins pour des données ultra sensibles au délai. |
stale-if-error=... |
Une réponse ancienne peut être réutilisée en cas d’erreur amont. | Services où une dégradation légère vaut mieux qu’une panne visible. | Ne doit pas masquer durablement un problème réel du backend. |
must-revalidate |
Si la réponse est périmée, elle doit être revérifiée avant réutilisation. | Quand la fraîcheur est stricte et qu’un contenu obsolète est inacceptable. | Souvent redondant si no-cache suffit déjà à exprimer l’intention. |
Deux remarques m’évitent beaucoup d’erreurs: no-cache ne signifie pas “ne pas stocker”, et les directives de requête comme only-if-cached ou max-stale sont des demandes adressées au cache, pas une garantie que tous les intermédiaires obéiront. Sur une API, je construis donc d’abord la politique de réponse, puis j’ajoute éventuellement la validation conditionnelle avec ETag ou Last-Modified.
Une fois ces briques posées, le vrai travail consiste à choisir la politique adaptée à chaque type de réponse.
Choisir la bonne stratégie selon le type de réponse
Je pars toujours de la nature de la donnée, pas du désir abstrait d’“aller plus vite”. Une réponse publique et stable ne mérite pas le même traitement qu’un profil utilisateur, et une page HTML d’entrée n’a pas les mêmes contraintes qu’un fichier statique fingerprinté.
| Type de réponse | Réglage que je conseille | Pourquoi | Quand je changerais d’avis |
|---|---|---|---|
| Profil utilisateur, panier, données après connexion |
private, no-cache ou private, no-store si la sensibilité est maximale |
La réponse reste hors des caches partagés et doit être revérifiée avant usage. | Je ne passe à no-store que si aucune conservation locale n’est acceptable. |
| Catalogue public, liste d’articles, données qui changent peu |
public, max-age=60, stale-while-revalidate=30 avec ETag
|
Le client reçoit vite une réponse, puis le cache se rafraîchit sans bloquer l’utilisateur. | Je baisse le TTL si la donnée évolue plus vite que prévu. |
| HTML de point d’entrée, route d’une SPA, page à revalider à chaque déploiement |
no-cache avec validation conditionnelle |
Le navigateur peut garder une copie, mais il vérifie systématiquement qu’elle est encore valable. | Je ne mets no-store que si la page contient des données réellement sensibles. |
| Fichier versionné ou fingerprinté | public, max-age=31536000, immutable |
Le contenu ne change pas sous la même URL, donc la revalidation devient inutile tant que la version est fraîche. | Si le fichier change sans changement d’URL, la politique devient dangereuse. |
| Réponse servie via CDN avec durée différente entre bordure et navigateur | public, max-age=60, s-maxage=300 |
Le navigateur reste assez réactif, tandis que le cache partagé garde la réponse plus longtemps. | Je l’utilise seulement quand je maîtrise vraiment la couche de cache intermédiaire. |
Dès qu’une réponse dépend d’un cookie, d’un jeton ou d’un autre signal d’identité, j’ajoute aussi la logique de variation qui va avec, le plus souvent via Vary: Cookie. Sans ça, vous pouvez avoir la bonne politique de durée mais la mauvaise séparation entre variantes.
Quand la règle est claire, la configuration devient presque mécanique; les exemples ci-dessous montrent comment je l’écris concrètement.
Des exemples concrets qui tiennent debout en production
Une API publique qui peut tolérer une minute de retard
Je choisis cette approche pour des contenus comme des listes d’articles, des tendances ou des données qui peuvent être un peu périmées sans casser l’expérience. L’idée n’est pas de tricher sur la fraîcheur, mais d’éviter que chaque visite provoque un aller-retour inutile vers l’origine.
Cache-Control: public, max-age=60, stale-while-revalidate=30
ETag: "articles-v42"
Le couple max-age + stale-while-revalidate donne une bonne sensation de rapidité, et ETag réduit encore le coût des revalidations. Si votre contenu change plus vite, je raccourcis simplement la fenêtre.
Une réponse personnalisée après authentification
Dès qu’un endpoint sert un profil, un panier ou une configuration propre à l’utilisateur, je verrouille la portée avant de discuter de performance. C’est là qu’une configuration propre évite les fuites de données entre caches partagés et sessions différentes.
Cache-Control: private, no-cache
Vary: Cookie
ETag: "profile-17"
private limite la conservation au navigateur, no-cache force la validation avant réutilisation, et Vary: Cookie protège contre le mélange de variantes. Si les données sont vraiment sensibles, je remplace no-cache par no-store, mais seulement quand la conservation locale n’a aucun intérêt.
Lire aussi : TypeScript & Node.js - Backend robuste et API évolutive
Des assets front versionnés
Pour un bundle JS, un fichier CSS ou une image fingerprintée, je préfère une stratégie agressive et simple. Tant que l’URL change à chaque nouvelle version, le cache peut conserver le fichier longtemps sans risque de servir une vieille ressource.
Cache-Control: public, max-age=31536000, immutable
Ici, la vraie condition de réussite n’est pas le header lui-même, c’est le fait que le nom du fichier change quand le contenu change. Sans versionnement, immutable devient une promesse trop forte.
Même une bonne configuration peut poser problème si elle est contradictoire ou mal testée. C’est là que les erreurs classiques apparaissent.
Les erreurs qui cassent le cache ou brouillent la sécurité
Je vois revenir les mêmes fautes, souvent parce qu’on cherche une “bonne pratique” générique au lieu de raisonner par type de donnée. Le cache n’est pas un interrupteur magique; c’est un compromis entre vitesse, fraîcheur et contrôle.- Confondre
no-cacheetno-store. Le premier autorise le stockage avec validation, le second interdit le stockage. - Mettre
publicpar réflexe sur une réponse qui dépend d’un cookie ou d’une session. - Oublier
Varyquand la réponse change selon l’identité, la langue ou d’autres paramètres de requête. - Donner un TTL très long à une page HTML qui change souvent, sans versionnement clair.
- Empiler plusieurs directives contradictoires dans l’espoir de “couvrir tous les cas”. En pratique, cela rend la politique illisible.
- Réserver
no-storeà des cas qui ne le nécessitent pas. Sur une page complète, cela peut aussi dégrader l’expérience de retour arrière. - Tester uniquement le cache du navigateur et oublier le comportement d’un CDN ou d’un proxy partagé.
Je préfère une règle simple: chaque directive doit répondre à un besoin précis. Si vous ne pouvez pas expliquer pourquoi elle est là, elle est probablement de trop. Et si la réponse est vraiment critique, je vérifie aussi que la logique de validation et de variation est cohérente, pas seulement le TTL.
Pour éviter de découvrir ces défauts en production, je termine toujours par une vérification simple mais méthodique.
Ce que je vérifierais avant de figer la politique de cache
Le plus efficace reste de tester trois scénarios: une requête froide, une requête répétée et une requête après expiration. J’observe alors si la réponse est servie depuis le cache, si elle est validée correctement et si les bons indicateurs apparaissent.
-
Inspecter les en-têtes avec les outils de développement ou un simple
curl -I. -
Rejouer la requête pour vérifier qu’un
304 Not Modifiedapparaît quand c’est attendu. - Contrôler la durée réelle en comparant le comportement avant et après expiration du TTL.
- Vérifier le cache partagé séparément du cache navigateur si vous passez par un CDN.
-
Surveiller
AgeetCache-Statusquand ils sont disponibles, pour voir ce que le cache a réellement fait.
Au fond, une bonne politique de cache n’est pas la plus agressive possible; c’est celle qui sert vite sans jamais réutiliser la mauvaise réponse. Sur un backend ou une API, je pars presque toujours de trois questions: qui a le droit de garder cette donnée, pendant combien de temps, et à quelles conditions doit-elle être revérifiée? Si vous répondez proprement à ces trois points, le reste devient simple.