Jour 51 Day 51 · mercredi 21 octobre 2026 Wednesday 21 October 2026 Web Intermédiaire
Web performance & caching Web performance & caching
Core Web Vitals, images, JS budget, fonts et surtout le caching HTTP (Cache-Control, ETag, CDN) : ce qui rend un site rapide — et les questions front les plus concrètes en entretien. Core Web Vitals, images, JS budget, fonts and above all HTTP caching (Cache-Control, ETag, CDN): what makes a site fast — and the most concrete front-end interview questions.
L’essentiel
La performance web n’est pas une affaire de micro-optimisations : c’est ce que l’utilisateur perçoit. Google la mesure avec les Core Web Vitals, trois métriques qui comptent aussi pour le SEO :
- LCP (Largest Contentful Paint, cible < 2,5 s) : le temps d’affichage du plus gros élément visible — en général l’image ou le titre principal. Se dégrade avec des images lourdes, un serveur lent, des ressources bloquantes.
- INP (Interaction to Next Paint, cible < 200 ms) : la réactivité aux interactions — le successeur de FID. Se dégrade quand le main thread est occupé par du JavaScript.
- CLS (Cumulative Layout Shift, cible < 0,1) : la stabilité visuelle — le bouton qui se décale au moment où vous cliquez. Causé par des images sans dimensions, des fonts qui changent la mise en page, des bannières injectées.
Et la règle d’or avant tout le reste : mesurer avant d’optimiser. Lighthouse pour l’audit, l’onglet Network/Performance des DevTools pour le détail, les données terrain (CrUX) pour la réalité des utilisateurs. Optimiser sans mesurer, c’est déplacer des octets au hasard.
La plus grosse optimisation, et de loin, reste la plus simple : ne pas refaire le travail — c’est tout le rôle du caching HTTP.
Comment ça marche
Le chemin d’une requête, avec ses trois niveaux de cache :
Navigateur CDN (edge) Origin
────────── ────────── ──────
cache local ──miss──▶ cache edge ──miss──▶ serveur
(mémoire/disque) (PoP proche génère la
│ hit de l'utilisateur) réponse
▼ │ hit │
réponse en ~0 ms ▼ ▼
réponse rapide réponse + headers
de cache (remonte
et se fait stocker)
Tout est piloté par les headers de cache que renvoie l’origin :
| Directive | Effet |
|---|---|
max-age=3600 | Frais pendant 1 h : le navigateur ne redemande rien |
no-cache | Stocker, mais revalider à chaque fois (ETag/304) |
no-store | Ne jamais stocker (données sensibles) |
public / private | Cachable par les intermédiaires (CDN) / seulement le navigateur |
immutable | Ne jamais revalider pendant max-age (fichiers hashés) |
s-maxage=600 | max-age spécifique aux caches partagés (CDN) |
stale-while-revalidate=60 | Servir le périmé immédiatement, rafraîchir en fond |
La revalidation : quand une ressource est périmée (ou en no-cache), le navigateur envoie l’ETag reçu précédemment (If-None-Match: "abc123"). Si le contenu n’a pas changé, le serveur répond 304 Not Modified — pas de corps, juste « garde ta copie ». On paie un aller-retour, pas le transfert.
La stratégie deux vitesses, celle qu’utilisent tous les sites modernes :
# HTML : toujours revalidé — c'est lui qui pointe vers le reste
GET /index.html
Cache-Control: no-cache
ETag: "v42"
# → 304 si inchangé : un aller-retour léger, jamais de HTML périmé
# Assets buildés : hash du contenu DANS le nom de fichier
GET /assets/app.9f3ab2.js
Cache-Control: public, max-age=31536000, immutable
# → 1 an de cache, zéro requête. Un nouveau déploiement change
# le hash → nouvelle URL → le HTML frais pointe dessus.
# L'« invalidation » n'existe plus : on change d'URL.
💡 La stratégie deux vitesses — HTML en
no-cache+ assets hashés enimmutable, c’est la réponse modèle à « comment gères-tu le cache d’une SPA ? ». L’HTML léger se revalide à chaque visite (304), et comme lui seul référence les assets, ceux-ci peuvent être cachés un an sans aucun risque de servir du périmé. Le vieux problème de l’invalidation disparaît : on n’invalide pas, on change d’URL.
Le CDN ajoute l’étage intermédiaire : des serveurs edge répartis dans le monde qui servent les réponses cachées près de l’utilisateur (latence ÷ 5 à 10 sur les assets). s-maxage contrôle leur durée de cache, et l’invalidation explicite (purge API) reste possible — mais avec des assets hashés, on n’en a presque jamais besoin.
Concepts clés à maîtriser
- Images, le poids lourd (souvent 50 %+ de la page) : formats modernes (WebP, AVIF : 30-50 % plus légers que JPEG),
loading="lazy"sur tout ce qui est sous la ligne de flottaison (natif, sans JS),srcset/sizespour servir la bonne résolution à chaque écran, et toujourswidth/height(ouaspect-ratio) pour réserver l’espace — c’est ça qui tue le CLS. Attention : jamais de lazy loading sur l’image LCP, au contraire (fetchpriority="high"). - JS budget : le JavaScript coûte deux fois — téléchargement, puis parsing/exécution sur le main thread (là où INP se joue). Armes : code splitting (un bundle par route, import dynamique pour le lourd), tree shaking (éliminer le code non importé — d’où l’importance des imports nommés),
defersur les scripts (téléchargement parallèle, exécution après le parsing HTML), et auditer ses dépendances (Bundlephobia : la lib de 200 Ko pour formater une date). - Fonts : une webfont bloque le texte par défaut.
font-display: swapaffiche d’abord la police système (quitte à un léger swap visuel),<link rel="preload">charge la font critique tôt, et le self-host évite l’aller-retour vers Google Fonts (DNS + connexion tierce). Limiter les variantes (chaque graisse = un fichier). - Mesurer : Lighthouse (labo, reproductible), onglet Performance des DevTools (flame chart du main thread), onglet Network (waterfall, tailles, cache hits), et les données terrain CrUX/RUM — le labo sur votre machine de dev fibrée ne dit rien du mobile 4G de vos utilisateurs.
🎤 En entretien — « Le site est lent, tu fais quoi ? » Ne partez PAS en liste de recettes. Réponse structurée : 1) mesurer (Lighthouse + Network) pour identifier le goulot, 2) traiter le goulot dominant — image LCP trop lourde ? bundle JS obèse ? pas de cache ? — 3) re-mesurer. Un candidat qui commence par « je regarde le waterfall » vaut dix candidats qui récitent « minifier le CSS ».
En entretien
« C’est quoi les Core Web Vitals ? » — LCP (chargement perçu, < 2,5 s), INP (réactivité aux interactions, < 200 ms), CLS (stabilité visuelle, < 0,1). Donner un levier chacun : LCP → optimiser l’image principale et le TTFB ; INP → réduire le JS sur le main thread ; CLS → dimensions explicites sur images et embeds.
« Explique Cache-Control: no-cache. » — Piège classique : ça ne veut PAS dire « ne pas cacher ». Ça signifie « stocke, mais revalide à chaque utilisation » (requête conditionnelle ETag → 304 si inchangé). « Ne jamais stocker », c’est no-store.
« Comment fonctionne un 304 ? » — Le serveur a envoyé un ETag avec la ressource. À la revalidation, le navigateur envoie If-None-Match: <etag> ; si le contenu correspond toujours, le serveur répond 304 sans corps et le navigateur réutilise sa copie. Coût : un aller-retour, pas un transfert.
« Comment invalides-tu le cache d’un asset ? » — La vraie réponse : on ne l’invalide pas — on met un hash du contenu dans le nom de fichier (app.9f3ab2.js), caché un an en immutable ; un déploiement produit un nouveau hash donc une nouvelle URL, référencée par un HTML toujours revalidé. La purge CDN existe, mais c’est le plan B.
« À quoi sert un CDN ? » — Rapprocher le contenu de l’utilisateur : des serveurs edge cachent les réponses (selon s-maxage/Cache-Control) et coupent la latence géographique. Bonus : absorption des pics, TLS terminé à l’edge, et protection de l’origin.
Pièges & idées reçues
⚠️ Le cache qui sert du périmé — mettre
max-age=31536000sur un fichier non hashé (app.js) : vos utilisateurs garderont l’ancienne version un an, et aucun déploiement ne les touchera. L’immutablelongue durée est réservé aux URLs qui changent quand le contenu change. À l’inverse, du HTML caché longtemps = des utilisateurs qui pointent vers des assets qui n’existent plus (erreurs après chaque déploiement).
- « no-cache = pas de cache » — non : stocker mais revalider. C’est
no-storequi interdit le stockage. Question piège très fréquente. - Lazy-loader l’image LCP —
loading="lazy"sur l’image principale retarde exactement ce qu’on mesure : LCP dégradé. Lazy loading = sous la ligne de flottaison uniquement. - Optimiser sans mesurer — minifier un CSS de 10 Ko pendant qu’une image de 4 Mo plombe le LCP. Le waterfall d’abord.
- Le score Lighthouse comme fin en soi — c’est une mesure labo sur une machine ; les données terrain (CrUX, RUM) peuvent raconter autre chose. Viser l’expérience réelle, pas le 100 vert.
Pour aller plus loin
- web.dev — Core Web Vitals : définitions, seuils et guides d’optimisation par métrique
- MDN — HTTP caching : la référence sur Cache-Control, ETag et la revalidation
- web.dev — Learn Performance : le cours complet (images, fonts, JS, ressources critiques)
- Chrome DevTools — Performance : analyser le main thread et le waterfall
- Exercice : ouvrir l’onglet Network sur un site connu, observer les
Cache-Controldes assets vs du HTML — la stratégie deux vitesses est partout
The essentials
Web performance is not about micro-optimizations: it’s what the user perceives. Google measures it with the Core Web Vitals, three metrics that also count for SEO:
- LCP (Largest Contentful Paint, target < 2.5 s): the render time of the largest visible element — usually the hero image or main heading. Degraded by heavy images, a slow server, blocking resources.
- INP (Interaction to Next Paint, target < 200 ms): responsiveness to interactions — the successor of FID. Degraded when the main thread is busy running JavaScript.
- CLS (Cumulative Layout Shift, target < 0.1): visual stability — the button that shifts right as you click it. Caused by images without dimensions, fonts that reflow the layout, injected banners.
And the golden rule before anything else: measure before optimizing. Lighthouse for the audit, the DevTools Network/Performance tabs for the details, field data (CrUX) for what users actually experience. Optimizing without measuring is shuffling bytes at random.
The biggest optimization, by far, is also the simplest: don’t redo the work — that is the entire job of HTTP caching.
How it works
The path of a request, with its three cache levels:
Browser CDN (edge) Origin
─────── ────────── ──────
local cache ──miss──▶ edge cache ──miss──▶ server
(memory/disk) (PoP close to generates
│ hit the user) response
▼ │ hit │
response in ~0 ms ▼ ▼
fast response response + cache
headers (goes back
up and gets stored)
Everything is driven by the cache headers the origin returns:
| Directive | Effect |
|---|---|
max-age=3600 | Fresh for 1 h: the browser asks for nothing |
no-cache | Store, but revalidate every time (ETag/304) |
no-store | Never store (sensitive data) |
public / private | Cacheable by intermediaries (CDN) / browser only |
immutable | Never revalidate during max-age (hashed files) |
s-maxage=600 | max-age specific to shared caches (CDN) |
stale-while-revalidate=60 | Serve stale immediately, refresh in the background |
Revalidation: when a resource is stale (or under no-cache), the browser sends the previously received ETag (If-None-Match: "abc123"). If the content hasn’t changed, the server answers 304 Not Modified — no body, just “keep your copy”. You pay a round trip, not a transfer.
The two-speed strategy, the one every modern site uses:
# HTML: always revalidated — it's what points to everything else
GET /index.html
Cache-Control: no-cache
ETag: "v42"
# → 304 if unchanged: one light round trip, never stale HTML
# Built assets: content hash IN the file name
GET /assets/app.9f3ab2.js
Cache-Control: public, max-age=31536000, immutable
# → 1 year of cache, zero requests. A new deploy changes
# the hash → new URL → the fresh HTML points to it.
# "Invalidation" no longer exists: you change the URL.
💡 The two-speed strategy — HTML on
no-cache+ hashed assets onimmutableis the model answer to “how do you handle caching for a SPA?”. The lightweight HTML revalidates on every visit (304), and since it alone references the assets, those can be cached for a year with zero risk of serving stale content. The old invalidation problem vanishes: you don’t invalidate, you change the URL.
The CDN adds the middle tier: edge servers spread around the world that serve cached responses close to the user (latency ÷ 5 to 10 on assets). s-maxage controls their cache duration, and explicit invalidation (purge API) remains possible — but with hashed assets you almost never need it.
Key concepts to master
- Images, the heavyweight (often 50%+ of the page): modern formats (WebP, AVIF: 30-50% lighter than JPEG),
loading="lazy"on everything below the fold (native, no JS),srcset/sizesto serve the right resolution to each screen, and alwayswidth/height(oraspect-ratio) to reserve the space — that’s what kills CLS. Careful: never lazy-load the LCP image; do the opposite (fetchpriority="high"). - JS budget: JavaScript costs twice — download, then parsing/execution on the main thread (where INP is decided). Weapons: code splitting (one bundle per route, dynamic import for heavy parts), tree shaking (dropping unimported code — hence the importance of named imports),
deferon scripts (parallel download, execution after HTML parsing), and auditing dependencies (Bundlephobia: the 200 KB lib to format a date). - Fonts: a webfont blocks text by default.
font-display: swapshows the system font first (at the cost of a slight visual swap),<link rel="preload">loads the critical font early, and self-hosting avoids the round trip to Google Fonts (DNS + third-party connection). Limit variants (every weight = one file). - Measuring: Lighthouse (lab, reproducible), the DevTools Performance tab (main-thread flame chart), the Network tab (waterfall, sizes, cache hits), and CrUX/RUM field data — the lab on your fiber-connected dev machine says nothing about your users’ 4G phones.
🎤 In an interview — “The site is slow, what do you do?” Do NOT launch into a recipe list. Structured answer: 1) measure (Lighthouse + Network) to identify the bottleneck, 2) fix the dominant bottleneck — oversized LCP image? bloated JS bundle? no caching? — 3) re-measure. A candidate who starts with “I look at the waterfall” is worth ten candidates reciting “minify the CSS”.
In an interview
“What are the Core Web Vitals?” — LCP (perceived loading, < 2.5 s), INP (interaction responsiveness, < 200 ms), CLS (visual stability, < 0.1). Give one lever each: LCP → optimize the hero image and TTFB; INP → reduce JS on the main thread; CLS → explicit dimensions on images and embeds.
“Explain Cache-Control: no-cache.” — Classic trap: it does NOT mean “don’t cache”. It means “store, but revalidate on every use” (conditional request with ETag → 304 if unchanged). “Never store” is no-store.
“How does a 304 work?” — The server sent an ETag with the resource. On revalidation, the browser sends If-None-Match: <etag>; if the content still matches, the server answers 304 with no body and the browser reuses its copy. Cost: a round trip, not a transfer.
“How do you invalidate a cached asset?” — The real answer: you don’t — you put a content hash in the file name (app.9f3ab2.js), cached for a year with immutable; a deploy produces a new hash and thus a new URL, referenced by an always-revalidated HTML. CDN purging exists, but it’s plan B.
“What is a CDN for?” — Bringing content closer to the user: edge servers cache responses (per s-maxage/Cache-Control) and cut geographic latency. Bonus: absorbing spikes, TLS terminated at the edge, and shielding the origin.
Pitfalls & misconceptions
⚠️ The cache that serves stale content — putting
max-age=31536000on a non-hashed file (app.js): your users will keep the old version for a year, and no deploy will reach them. Long-livedimmutableis reserved for URLs that change when the content changes. Conversely, HTML cached for long = users pointing at assets that no longer exist (errors after every deploy).
- “no-cache = no caching” — no: store but revalidate. It’s
no-storethat forbids storage. A very common trick question. - Lazy-loading the LCP image —
loading="lazy"on the hero image delays exactly what is being measured: degraded LCP. Lazy loading = below the fold only. - Optimizing without measuring — minifying a 10 KB CSS file while a 4 MB image sinks the LCP. Waterfall first.
- The Lighthouse score as an end in itself — it’s a lab measurement on one machine; field data (CrUX, RUM) can tell a different story. Aim for the real experience, not the green 100.
Going further
- web.dev — Core Web Vitals: definitions, thresholds and per-metric optimization guides
- MDN — HTTP caching: the reference on Cache-Control, ETag and revalidation
- web.dev — Learn Performance: the full course (images, fonts, JS, critical resources)
- Chrome DevTools — Performance: analyzing the main thread and the waterfall
- Exercise: open the Network tab on a well-known site and compare the
Cache-Controlof assets vs HTML — the two-speed strategy is everywhere