Leçon 7/8 10 min

Le cache HTTP

Cache-Control, ETag et le 304 : comment le navigateur évite de re-télécharger ce qu'il possède déjà, les trois niveaux de cache.

Le navigateur, lui, a de la mémoire

Souviens-toi de la leçon précédente : le serveur n'a pas de mémoire. Chaque requête arrive vierge, il a oublié la précédente, et c'est le cookie qui rattrape le coup. Mais il y a une mémoire dont on n'a pas encore parlé, et elle est de ton côté : ton navigateur, lui, retient des choses. Et ici, ce n'est pas pour te reconnaître. C'est une arme de vitesse.

Reprends le fil rouge de la leçon 4 : chaque aller-retour coûte. Une requête part, traverse le réseau, le serveur répond, ça revient. Du temps, à chaque fois. Le cache, c'est l'outil qui supprime des allers-retours entiers. Le meilleur aller-retour, c'est celui qu'on ne fait pas.

Pose-toi la question : tu reviens sur un site que tu connais. Le logo, le fichier CSS, les polices... tout ça n'a pas changé depuis hier. Pourquoi ton navigateur irait-il les re-télécharger ? Ce serait du pur gâchis. Cette leçon explique comment il évite ce gâchis, et comment toi, développeur, tu le pilotes.

Trois niveaux de réponse, du plus rapide au plus lent

Quand ton navigateur a besoin d'un fichier qu'il a déjà vu, il a trois façons de répondre. Du plus rapide au plus lent :

  • Cache frais : zéro requête. Le navigateur a une copie, et il sait qu'elle est encore valable. Il la ressert directement depuis son disque. Aucun aller-retour. C'est instantané.
  • Revalidation : un aller-retour minuscule. Le navigateur a une copie, mais il n'est plus sûr qu'elle soit à jour. Il demande au serveur « ma version est-elle toujours bonne ? ». Si oui, le serveur répond 304, sans renvoyer le fichier. Un aller-retour, mais quasi vide.
  • Cache raté : tout re-télécharger. Pas de copie utilisable. Le navigateur refait une vraie requête, le serveur renvoie le fichier complet en 200. L'aller-retour entier, body compris.

Tout l'art du cache, c'est de rester le plus possible dans le premier cas, de tomber dans le deuxième quand on n'est pas sûr, et d'éviter le troisième quand rien n'a changé. La suite explique les en-têtes qui pilotent ça.

Cache-Control, raconté simplement

C'est le serveur qui décide. Quand il envoie un fichier, il ajoute un en-tête Cache-Control qui dit au navigateur comment le garder. Quelques directives suffisent à tout comprendre :

  • max-age=3600 : « garde cette copie 3600 secondes (1 heure) sans rien me redemander ». Pendant cette heure, c'est le cas le plus rapide : zéro requête. La copie est dite fraîche.
  • no-cache : « garde la copie, mais revalide à chaque fois avant de la servir ». Le navigateur garde bien le fichier, mais il redemande systématiquement au serveur « toujours bon ? ». C'est le cas de l'aller-retour minuscule.
  • no-store : « ne garde rien du tout ». Le vrai « pas de cache ». Le navigateur ne stocke même pas une copie, il re-télécharge à chaque fois. On le réserve aux données sensibles (une page de compte bancaire).
  • public / private : public autorise les caches partagés (comme un CDN) à stocker la réponse ; private la réserve au navigateur de l'utilisateur, parce qu'elle lui est personnelle.

Le piège du nom : no-cache ne veut PAS dire « pas de cache ». C'est le contraire de ce que le nom laisse croire. no-cache autorise le navigateur à garder la copie, il l'oblige juste à la revalider avant chaque usage. Le vrai « ne stocke rien » s'appelle no-store. Confondre les deux est l'erreur la plus courante sur ce sujet : on croit avoir désactivé le cache avec no-cache alors qu'on a juste forcé une revalidation.

La revalidation : ETag et le 304

Regardons le cas du milieu de plus près, parce que c'est le plus malin. Comment le navigateur demande-t-il « ma version est-elle toujours bonne ? » sans re-télécharger le fichier ? Avec une empreinte.

Le mécanisme complet, étape par étape :

  • La première réponse porte un ETag. Quand le serveur envoie le fichier, il ajoute un en-tête ETag: "v42". C'est l'empreinte de cette version précise du fichier. Le navigateur range le fichier ET son empreinte.
  • Plus tard, le navigateur redemande avec If-None-Match. Au lieu d'une requête nue, il envoie If-None-Match: "v42" : « j'ai déjà la version v42, est-elle toujours d'actualité ? ».
  • Si rien n'a changé, le serveur répond 304 Not Modified. Rappelle-toi la leçon 5 : le 304 fait partie de la famille des 3xx. Il signifie « ta copie est toujours bonne, ressers-la ». Surtout, cette réponse n'a aucun body : le serveur ne renvoie pas le fichier, juste le feu vert.
  • Le navigateur ressert sa copie locale. Un aller-retour, oui, mais quasi zéro octet transféré. Au lieu de re-télécharger 80 Ko de logo, on a échangé quelques octets d'en-têtes.

C'est ça, la beauté de la revalidation : on paie le coût minimal (un aller-retour vide) pour avoir la certitude d'être à jour, au lieu de payer le coût maximal (re-télécharger tout) par précaution.

Prédis avant de lire

Tu charges une page : son logo, son CSS et ses polices arrivent tous en 200. Tu appuies sur F5 pour recharger la page. Que vont devenir ces 200 dans la colonne « statut » de l'onglet réseau ?

Voir la réponse

Ils changent presque tous de visage. Les fichiers encore frais (ceux avec un max-age non expiré) affichent 200 (from disk cache) : zéro requête, servis depuis le disque. Ceux dont la copie doit être revalidée affichent 304 Not Modified : un aller-retour minuscule, sans body. Et si un fichier a vraiment changé ou n'était pas cachable, lui seul repart en vrai 200 complet. Le tableau qui suit te montre les deux colonnes côte à côte.

L'onglet réseau, premier chargement contre rechargement

Ouvre les deux volets et compare. Le même site, deux visites. La colonne « taille » raconte toute l'histoire du cache.

📥 Premier chargement (tout est neuf)
Fichier Statut Taille
index.html20014 Ko
style.css20042 Ko
logo.svg2008 Ko
inter.woff220096 Ko

Total transféré : 160 Ko. Tout part en 200, body complet, rien n'est encore en cache.

🔄 Rechargement (le cache entre en jeu)
Fichier Statut Taille
index.html3040,3 Ko
style.css200 (cache disque)0 Ko
logo.svg200 (cache disque)0 Ko
inter.woff2200 (cache disque)0 Ko

Total transféré : 0,3 Ko au lieu de 160 Ko. Le HTML a été revalidé (304, juste les en-têtes), les assets frais sont servis depuis le disque (0 Ko). La page s'affiche presque instantanément.

Les trois scénarios en un coup d'oeil

Garde ce schéma en tête. Trois lignes, trois coûts : le cache frais ne touche jamais le réseau, la revalidation y va mais en revient presque vide, le cache raté paie le plein tarif.

Trois lignes horizontales montrent trois scénarios de cache, du navigateur à gauche vers le serveur à droite. Ligne 1, cache frais : une flèche fait demi-tour avant d'atteindre le réseau, zéro requête. Ligne 2, revalidation : un aller-retour fin part au serveur et revient avec un 304 sans body. Ligne 3, cache raté : un aller-retour épais part au serveur et revient avec un 200 complet. Navigateur Serveur Cache frais copie valable demi-tour avant le réseau 0 requête, instantané Revalidation pas sûr If-None-Match: "v42" 304 Not Modified · sans body Serveur Cache raté rien d'utilisable GET /logo.svg 200 · fichier complet (épais) Serveur
Du moins cher au plus cher : cache frais (zéro réseau), revalidation (aller-retour vide), cache raté (le plein body).

Et le HTML, on le cache ?

Tu remarques dans le tableau que le HTML, lui, repart en 304 à chaque fois, alors que le CSS et les polices restent muets sur le disque. Ce n'est pas un hasard. Le HTML change souvent (un nouveau titre, un prix mis à jour), donc on le cache peu, ou on le force à revalider. Les assets, eux, sont stables et on les garde longtemps.

Mais alors, comment changer un CSS qui est caché pour un an ? On change son nom. Au lieu de style.css, on sert style.css?v=12, ou mieux, un nom haché comme style.a1b2c3.css. Une nouvelle version a un nouveau nom, donc une nouvelle URL, donc le navigateur la voit comme un fichier neuf qu'il télécharge. C'est exactement pour ça que les outils de build (Vite, webpack) renomment automatiquement tes fichiers à chaque build : ils te donnent un cache long et agressif sans jamais te coincer avec une vieille version.

The browser, though, has a memory

Remember the previous lesson: the server has no memory. Every request arrives blank, it forgot the last one, and the cookie is what saves the day. But there's a memory we haven't talked about yet, and it's on your side: your browser does remember things. And here, it's not to recognise you. It's a speed weapon.

Pick up the through-line from lesson 4: every round trip costs. A request goes out, crosses the network, the server answers, it comes back. Time, every single time. Caching is the tool that removes whole round trips. The best round trip is the one you never make.

Ask yourself: you come back to a site you know. The logo, the CSS file, the fonts... none of it has changed since yesterday. Why would your browser re-download them? That would be pure waste. This lesson explains how it avoids that waste, and how you, the developer, steer it.

Three levels of answer, from fastest to slowest

When your browser needs a file it has already seen, it has three ways to answer. From fastest to slowest:

  • Fresh cache: zero request. The browser has a copy, and it knows the copy is still valid. It serves it straight from its disk. No round trip. Instant.
  • Revalidation: a tiny round trip. The browser has a copy, but it's no longer sure it's up to date. It asks the server "is my version still good?". If yes, the server replies 304, without sending the file back. One round trip, but nearly empty.
  • Cache miss: re-download everything. No usable copy. The browser makes a real request, the server returns the full file with a 200. The whole round trip, body included.

The whole art of caching is to stay in the first case as much as possible, fall into the second when you're not sure, and avoid the third when nothing has changed. What follows explains the headers that steer this.

Cache-Control, told simply

The server decides. When it sends a file, it adds a Cache-Control header that tells the browser how to keep it. A handful of directives explain it all:

  • max-age=3600 — "keep this copy for 3600 seconds (1 hour) without asking me again". During that hour, it's the fastest case: zero request. The copy is said to be fresh.
  • no-cache — "keep the copy, but revalidate every time before serving it". The browser does keep the file, it just systematically re-asks the server "still good?". This is the tiny round-trip case.
  • no-store — "don't keep anything at all". The real "no cache". The browser doesn't even store a copy, it re-downloads every time. Reserve it for sensitive data (a bank account page).
  • public / privatepublic lets shared caches (like a CDN) store the response; private reserves it for the user's browser, because it's personal to them.

The naming trap: no-cache does NOT mean "no cache". It's the opposite of what the name suggests. no-cache lets the browser keep the copy, it just forces it to revalidate before every use. The real "store nothing" is called no-store. Confusing the two is the most common mistake on this topic: you think you disabled caching with no-cache when you just forced a revalidation.

Revalidation: ETag and the 304

Let's look at the middle case more closely, because it's the cleverest. How does the browser ask "is my version still good?" without re-downloading the file? With a fingerprint.

The full mechanism, step by step:

  • The first response carries an ETag. When the server sends the file, it adds an ETag: "v42" header. It's the fingerprint of that precise version of the file. The browser stores the file AND its fingerprint.
  • Later, the browser re-asks with If-None-Match. Instead of a bare request, it sends If-None-Match: "v42": "I already have version v42, is it still current?".
  • If nothing changed, the server replies 304 Not Modified. Remember lesson 5: the 304 belongs to the 3xx family. It means "your copy is still good, re-serve it". Crucially, this response has no body: the server doesn't send the file back, just the green light.
  • The browser re-serves its local copy. One round trip, yes, but nearly zero bytes transferred. Instead of re-downloading 80 KB of logo, you exchanged a few bytes of headers.

That's the beauty of revalidation: you pay the minimal cost (an empty round trip) to be certain you're up to date, instead of paying the maximal cost (re-download everything) just in case.

Predict before reading on

You load a page: its logo, CSS and fonts all arrive with a 200. You press F5 to reload. What will those 200s become in the "status" column of the network tab?

Show the answer

Almost all of them change face. The still-fresh files (those with a non-expired max-age) show 200 (from disk cache): zero request, served from disk. Those whose copy must be revalidated show 304 Not Modified: a tiny round trip, no body. And if a file truly changed or wasn't cacheable, that one alone goes back out as a real full 200. The table below shows both columns side by side.

The network tab: first load versus reload

Open both panels and compare. Same site, two visits. The "size" column tells the whole caching story.

📥 First load (everything is new)
File Status Size
index.html20014 KB
style.css20042 KB
logo.svg2008 KB
inter.woff220096 KB

Total transferred: 160 KB. Everything goes out as 200, full body, nothing is cached yet.

🔄 Reload (caching kicks in)
File Status Size
index.html3040.3 KB
style.css200 (disk cache)0 KB
logo.svg200 (disk cache)0 KB
inter.woff2200 (disk cache)0 KB

Total transferred: 0.3 KB instead of 160 KB. The HTML was revalidated (304, just the headers), the fresh assets are served from disk (0 KB). The page renders almost instantly.

The three scenarios at a glance

Keep this diagram in mind. Three lines, three costs: fresh cache never touches the network, revalidation goes out but comes back nearly empty, the cache miss pays full price.

Three horizontal lines show three caching scenarios, from the browser on the left to the server on the right. Line 1, fresh cache: an arrow turns back before reaching the network, zero request. Line 2, revalidation: a thin round trip goes to the server and returns with a bodyless 304. Line 3, cache miss: a thick round trip goes to the server and returns with a full 200. Browser Server Fresh cache copy still valid turns back before the network 0 request, instant Revalidation not sure If-None-Match: "v42" 304 Not Modified · no body Server Cache miss nothing usable GET /logo.svg 200 · full file (thick) Server
From cheapest to most expensive: fresh cache (zero network), revalidation (empty round trip), cache miss (the full body).

And the HTML itself, do we cache it?

You'll notice in the table that the HTML goes back out as a 304 every time, while the CSS and fonts stay silent on disk. That's no accident. HTML changes often (a new title, an updated price), so we cache it little, or force it to revalidate. The assets, on the other hand, are stable and we keep them a long time.

But then how do you change a CSS file that's cached for a year? You change its name. Instead of style.css, you serve style.css?v=12, or better, a hashed name like style.a1b2c3.css. A new version has a new name, so a new URL, so the browser sees it as a brand-new file to download. That's exactly why build tools (Vite, webpack) automatically rename your files on every build: they give you a long, aggressive cache without ever trapping you with an old version.

🎯 Pratique

S'entraîner (clique pour ouvrir) :

💬 Ré-explique sans regarder
Ré-explique sans regarder

Explique à un collègue le voyage d'un ETag : comment le serveur, plus tard le navigateur, puis un 304 évitent de re-télécharger un fichier inchangé.

Une bonne explication dit : la première réponse du serveur porte un ETag: "v42", l'empreinte de cette version. Le navigateur garde le fichier et l'empreinte. Plus tard, il redemande avec If-None-Match: "v42". Si rien n'a changé, le serveur répond 304 Not Modified, sans body, et le navigateur ressert sa copie locale. Bilan : un aller-retour, quasi zéro octet, au lieu de re-télécharger tout le fichier.
🧠 Rappel libre
Rappel libre

Sans remonter : quelle est la différence réelle entre no-cache et no-store ?

Malgré son nom, no-cache n'interdit PAS le cache : le navigateur garde la copie, mais il doit la revalider (ETag / 304) avant chaque usage. no-store est le vrai « pas de cache » : le navigateur ne stocke rien et re-télécharge à chaque fois. On réserve no-store aux données sensibles.
⚖️ Juge le code de l'IA
Accepter ou rejeter le code de l'IA

Un client te dit que ses utilisateurs voient parfois une vieille version d'une page. Tu demandes à l'IA de régler ça. Elle répond : « Réglé ! J'ai mis Cache-Control: no-cache sur toutes les réponses pour désactiver complètement le cache. » Tu acceptes, ou tu rejettes ?

À rejeter, sur deux points. D'abord l'explication est fausse : no-cache ne désactive PAS le cache, il force une revalidation à chaque fois (le navigateur garde la copie, le vrai « ne stocke rien » est no-store). Ensuite, désactiver le cache est rarement le bon fix : on perd toute la vitesse pour rien. Le vrai problème est ailleurs : les assets (CSS, JS) doivent garder un cache long ET être versionnés par leur nom (style.a1b2c3.css), pour qu'une nouvelle version ait une nouvelle URL. C'est ça qui sert toujours la dernière version sans sacrifier la performance.
Une réponse arrive avec Cache-Control: max-age=3600. Tu recharges la page 5 minutes plus tard. Que fait le navigateur pour ce fichier ?
Un collègue écrit Cache-Control: no-cache en pensant « ne mets jamais en cache ». A-t-il raison, et sinon que fait vraiment no-cache ?
Le navigateur renvoie If-None-Match: "v42" et reçoit un 304 Not Modified. Qu'a transféré le serveur, et que fait le navigateur ?
Tu modifies ton style.css, tu déploies, mais un client voit toujours l'ancien design. Quelles explications par le cache sont plausibles ?
Prochaine étape

Tu sais maintenant économiser des allers-retours entiers avec le cache. Leçon 8, le dernier tronçon du voyage : CORS et HTTP/2, HTTP/3. Pourquoi ton fetch vers une autre API se fait refuser par le navigateur, et ce que HTTP/2 puis HTTP/3 ont changé sous le capot pour rendre tout le reste plus rapide.

Leçon 8 : CORS et HTTP/2, HTTP/3 →
Besoin d'un développeur pour votre projet ?

Réponse sous 24h · Sans engagement