Core Web Vitals en profondeur
Comprendre et corriger LCP, INP et CLS — avec un workflow de débogage pour développeurs.
Les Core Web Vitals sont la façon standardisée de Google de mesurer l’expérience de page — la vitesse de rendu d’une page, la rapidité avec laquelle elle répond aux interactions et sa stabilité pendant le chargement. Ils se regroupent dans le signal d’expérience de page plus large, qui sert de critère de départage dans le classement : il prend rarement le pas sur un excellent contenu, mais une page lente et saccadée perd face à une page tout aussi pertinente mais rapide.
Il existe aujourd’hui exactement trois Core Web Vitals, et l’un d’eux est nouveau. Depuis mars 2024, l’INP (Interaction to Next Paint) a remplacé le FID (First Input Delay) comme métrique de réactivité. Le FID ne mesurait que le délai avant le traitement de la première interaction ; l’INP mesure la latence complète des interactions sur toute la durée de vie de la page. Si votre modèle mental dit encore « FID », mettez-le à jour — le FID est obsolète et n’est plus rapporté.
Ce guide suppose que vous savez lire du code et déployer des changements. Nous allons procéder métrique par métrique, distinguer les sources de données qui comptent vraiment, puis dérouler une boucle de débogage concrète que vous pourrez appliquer à n’importe quelle page.
Les trois métriques
Chaque métrique possède trois seuils. Google évalue le 75e centile des utilisateurs réels — il vous faut donc que trois chargements de page sur quatre franchissent la barre « Bon », pas seulement votre expérience médiane sur la machine de dev.
| Métrique | Ce qu’elle mesure | Bon | À améliorer | Mauvais |
|---|---|---|---|---|
| LCP (Largest Contentful Paint) | Temps jusqu’au rendu du plus grand élément visible (image hero, titre, vignette vidéo) | ≤ 2,5 s | 2,5 – 4,0 s | > 4,0 s |
| INP (Interaction to Next Paint) | Latence dans le pire des cas entre une interaction utilisateur et le rendu de la frame suivante | ≤ 200 ms | 200 – 500 ms | > 500 ms |
| CLS (Cumulative Layout Shift) | Somme des scores de décalage de mise en page inattendus pendant la durée de vie de la page | ≤ 0,1 | 0,1 – 0,25 | > 0,25 |
Une bonne manière de les retenir : le LCP, c’est « est-ce affiché ? », l’INP, c’est « a-t-il réagi quand je l’ai sollicité ? », le CLS, c’est « ça a bougé pendant que je lisais ? » Chargement, réactivité, stabilité visuelle.
🧑💻 Point de vue développeur : le CLS est sans unité — c’est la somme de
fraction d'impact × fraction de distancesur les fenêtres de décalage, pas des millisecondes. Un seul gros décalage en haut du viewport peut faire exploser tout votre budget, tandis que de nombreux petits décalages sous la ligne de flottaison peuvent à peine compter.
Données terrain vs données labo
Cette distinction fait trébucher plus d’équipes que n’importe quelle optimisation isolée. Il existe deux types de mesure et ils répondent à des questions différentes.
Les données labo proviennent d’une exécution synthétique dans un environnement contrôlé — Lighthouse, WebPageTest ou le panneau Performance. Un appareil, un profil réseau, un chargement à froid. C’est reproductible et idéal pour le débogage, mais c’est un seul échantillon et cela ne peut pas mesurer correctement l’INP, car l’INP a besoin de vrais humains qui cliquent sur de vraies choses au cours d’une vraie session. Lighthouse substitue le Total Blocking Time (TBT) comme proxy labo de la réactivité.
Les données terrain sont ce sur quoi Google classe réellement. Elles proviennent du Chrome User Experience Report (CrUX) — des métriques anonymisées et agrégées issues de vrais utilisateurs Chrome ayant accepté le reporting, regroupées sur une fenêtre glissante de 28 jours. C’est du « RUM » (Real User Monitoring) à l’échelle de la planète.
| Labo (Lighthouse) | Terrain (CrUX / RUM) | |
|---|---|---|
| Source | Exécution synthétique unique | Utilisateurs réels, fenêtre de 28 jours |
| Prise en charge de l’INP | Non (utilise le proxy TBT) | Oui |
| Utilisé pour le classement | Non | Oui |
| Utile pour | Débogage, gates de CI | Vérité terrain, priorisation |
| Variabilité | Faible (contrôlée) | Élevée (appareils/réseaux réels) |
⚠️ Attention : un score Lighthouse vert ne signifie pas que vos Core Web Vitals sont « Bons ». Lighthouse s’exécute sur un appareil simulé bridé mais propre ; vos vrais utilisateurs incluent des téléphones Android milieu de gamme sur des réseaux instables. Confirmez toujours avec les données terrain avant de crier victoire.
La règle pratique : déboguez en labo, jugez par le terrain. Utilisez Lighthouse pour trouver et corriger les problèmes rapidement (c’est déterministe), mais considérez CrUX dans Search Console ou PageSpeed Insights comme la source de vérité pour savoir si le correctif a porté ses fruits.
Corriger le LCP
Le LCP est la métrique la plus décomposable. Décomposez le temps jusqu’au LCP en quatre sous-phases et vous saurez exactement où concentrer vos efforts :
| Sous-phase | Ce qui se passe | Part typique d’un LCP lent |
|---|---|---|
| TTFB | Le serveur répond avec le premier octet | souvent 40 %+ |
| Délai de chargement de la ressource | Temps entre le TTFB et le démarrage du chargement de la ressource LCP par le navigateur | le coupable caché le plus courant |
| Temps de chargement de la ressource | Téléchargement de l’image/police/vidéo du LCP | lié au réseau |
| Délai de rendu de l’élément | Temps entre l’arrivée de la ressource et son rendu | bloqué par le CSS/JS |
Un LCP optimal maintient le délai de chargement proche de zéro — le navigateur devrait découvrir et commencer à récupérer la ressource LCP presque immédiatement.
1. Réduisez le TTFB. C’est côté serveur : mise en cache, livraison en périphérie via CDN et élimination du travail d’origine bloquant le rendu. Pour un site statique servi depuis un CDN, le TTFB devrait se compter en dizaines de millisecondes. S’il atteint des centaines, vous avez un problème de mise en cache ou d’origine avant même de toucher à une seule image.
2. Préchargez la ressource LCP. Le bug classique de délai de chargement : l’image LCP est référencée en CSS ou injectée par JS, si bien que le navigateur ne la découvre que tardivement. Rendez-la découvrable dans le HTML initial et indiquez sa priorité :
<!-- Preload + high priority so the browser fetches it immediately -->
<link rel="preload" as="image" href="/hero.avif" fetchpriority="high" />
<!-- Or directly on the img — fetchpriority avoids a separate preload -->
<img src="/hero.avif" fetchpriority="high" alt="Product hero" width="1280" height="720" />
3. Optimisez l’image elle-même. Servez des formats modernes (AVIF/WebP), dimensionnez-la aux dimensions réellement rendues et utilisez un srcset responsive. Ne chargez jamais votre image LCP en lazy-load — loading="lazy" sur le hero est une blessure LCP que vous vous infligez vous-même, car cela diffère le rendu le plus important.
<img
src="/hero-800.avif"
srcset="/hero-400.avif 400w, /hero-800.avif 800w, /hero-1600.avif 1600w"
sizes="(max-width: 600px) 100vw, 800px"
fetchpriority="high"
alt="Product hero"
width="800" height="450" />
4. Gérez les polices. Si votre élément LCP est du texte (un grand titre), une police web bloquante retarde le rendu. Préchargez la police, utilisez font-display: swap (ou optional) et auto-hébergez-la pour éviter une connexion tierce.
@font-face {
font-family: "Inter";
src: url("/fonts/inter.woff2") format("woff2");
font-display: swap; /* render text immediately, swap when font loads */
}
💡 Astuce : avant d’optimiser, identifiez quel élément est le LCP. Dans les DevTools, le panneau Performance le marque explicitement, et PageSpeed Insights le nomme. Optimiser le mauvais élément est l’après-midi gaspillé le plus courant dans le travail de performance.
Corriger l’INP
L’INP est un problème de thread principal. Quand un utilisateur clique, le navigateur doit exécuter votre gestionnaire d’événement, recalculer les styles et la mise en page, puis rendre la frame suivante. Si le thread principal est occupé — à exécuter une tâche longue — rien de tout cela ne peut se produire, et l’interaction semble figée.
L’action la plus efficace est de découper les tâches longues (tout ce qui dépasse 50 ms bloque le thread principal). Trois techniques, de la plus grossière à la plus chirurgicale :
1. Cédez la main au thread principal. Laissez le navigateur traiter les entrées en attente entre les blocs de travail. La primitive moderne est scheduler.yield() ; setTimeout(0) est le repli.
async function processLargeList(items) {
for (const item of items) {
doExpensiveWork(item);
// Yield so a pending click can be processed mid-loop
if (navigator.scheduling?.isInputPending?.()) {
await scheduler.yield(); // falls back to setTimeout in older browsers
}
}
}
2. Découplez la réponse visuelle du travail lourd. Affichez d’abord le retour que l’utilisateur attend, puis différez le calcul coûteux pour qu’il ne bloque pas le rendu suivant :
button.addEventListener("click", () => {
// 1. Immediate visual feedback — runs before the next paint
button.classList.add("is-loading");
// 2. Defer the heavy work past the paint
requestAnimationFrame(() => {
setTimeout(() => runExpensiveUpdate(), 0);
});
});
3. Évitez les re-rendus inutiles. Dans les frameworks à composants, un seul clic qui déclenche une cascade de re-rendus est un tueur d’INP classique. Mémoïsez, faites du debounce sur les gestionnaires à haute fréquence et gardez les mises à jour d’état circonscrites :
// React: debounce a search-as-you-type handler so each keystroke
// doesn't trigger a full filter + re-render on the critical path
const onChange = useMemo(
() => debounce((q) => setQuery(q), 150),
[]
);
Autres gains fiables : déplacez le travail gourmand en CPU (parsing, traitement d’images) dans un Web Worker ; élaguez les scripts tiers qui monopolisent le thread principal ; et évitez les lectures de mise en page synchrones à l’intérieur des gestionnaires (lire offsetHeight puis écrire des styles force le « layout thrashing »).
🧑💻 Point de vue développeur : l’INP mesure la pire interaction, pas la moyenne. Une seule ouverture de modale lente sur une page par ailleurs réactive peut plomber votre score terrain. Profilez les interactions que les utilisateurs effectuent le plus — bascules de menu, recherche, ajout au panier — pas seulement le chargement de page.
Corriger le CLS
Le CLS est presque toujours causé par du contenu qui se charge après la mise en page et qui pousse le contenu existant. Les correctifs consistent à réserver l’espace à l’avance.
1. Définissez toujours les dimensions des images et des vidéos. Les attributs width/height (ou un aspect-ratio en CSS) permettent au navigateur de réserver la boîte avant l’arrivée de l’image :
<img src="/photo.avif" width="800" height="450" alt="..." />
.media { aspect-ratio: 16 / 9; width: 100%; }
2. Réservez de l’espace pour les publicités, embeds et iframes. Les emplacements publicitaires injectés dynamiquement sont la source n°1 de CLS sur les sites de contenu. Donnez au conteneur une min-height fixe correspondant à la taille d’emplacement la plus courante pour que la page ne saute pas quand la pub se charge.
.ad-slot { min-height: 280px; } /* reserve before the ad loads */
3. N’insérez jamais de contenu au-dessus du contenu existant. Les bannières, avis de cookies et barres « vous avez un nouveau message » qui poussent la page vers le bas sont les pires fautifs. Superposez-les (positionnement fixed/absolute) au lieu de les insérer dans le flux, ou réservez leur espace dès le départ.
4. Domptez les échanges de polices. Un échange de police modifie les métriques du texte et peut décaler tout ce qui se trouve en dessous. Associez font-display: swap aux descripteurs size-adjust, ascent-override et descent-override sur un @font-face de repli pour que la police de repli et la police web occupent le même espace :
@font-face {
font-family: "Inter-fallback";
src: local("Arial");
size-adjust: 107%;
ascent-override: 90%;
descent-override: 22%;
}
⚠️ Attention : le CLS ne compte que les décalages inattendus. Un décalage dans les 500 ms suivant une interaction utilisateur (par exemple le déploiement d’un accordéon) est exclu. Vous n’avez donc pas à craindre chaque animation — seulement les mouvements que l’utilisateur n’a pas demandés.
Un workflow de débogage
Voici une boucle reproductible. Allez du bon marché et large vers le profond et spécifique.
Étape 1 — Triez avec les données terrain. Ouvrez PageSpeed Insights (ou le rapport Core Web Vitals de Search Console) et lisez d’abord la section terrain. Elle vous indique quelle métrique échoue réellement au 75e centile et sur quelle classe d’appareil (le mobile perd généralement en premier). N’optimisez pas ce qui n’est pas cassé.
Étape 2 — Reproduisez en labo. Lancez Lighthouse (DevTools → Lighthouse, mobile + throttling activé) pour obtenir une exécution déterministe et débogable avec des diagnostics précis (« Élément Largest Contentful Paint », « Éviter les décalages de mise en page importants », « Réduire le JavaScript inutilisé »).
Étape 3 — Profilez la métrique spécifique dans les DevTools.
- Pour le LCP/CLS : le panneau Performance enregistre une trace. Il marque l’élément LCP et signale chaque décalage de mise en page avec une piste rouge « Layout Shift » — cliquez sur l’une d’elles pour voir exactement quel nœud a bougé.
- Pour l’INP : activez la piste interaction, cliquez sur la page et inspectez l’interaction la plus longue. Les DevTools la décomposent en délai d’entrée, temps de traitement et délai de présentation pour que vous sachiez si le problème vient d’un thread principal occupé (délai d’entrée) ou d’un gestionnaire lent (traitement).
Étape 4 — Instrumentez avec la bibliothèque web-vitals. Les données labo ne peuvent pas capturer le véritable INP. Intégrez la bibliothèque officielle pour collecter les métriques terrain auprès de vos propres utilisateurs et les transmettre à votre analytics :
import { onLCP, onINP, onCLS } from "web-vitals";
function send(metric) {
navigator.sendBeacon(
"/analytics",
JSON.stringify({ name: metric.name, value: metric.value, id: metric.id })
);
}
onLCP(send);
onINP(send);
onCLS(send);
Chaque objet de métrique embarque une build attribution (web-vitals/attribution) qui vous indique l’élément fautif ou la plus grande source de décalage — ainsi vos données RUM pointent directement vers le correctif, pas seulement vers un chiffre.
Étape 5 — Vérifiez sur le terrain. Après le déploiement, patientez. CrUX utilise une fenêtre glissante de 28 jours, donc les améliorations terrain mettent des semaines à se refléter pleinement. Vos propres beacons web-vitals montreront du mouvement en quelques jours — utilisez-les pour un retour rapide, et confirmez avec PageSpeed Insights une fois la fenêtre rattrapée.
Où cela se connecte
Les Core Web Vitals découlent des décisions que vous prenez en construisant le site — votre stratégie de rendu, votre pipeline d’assets, le chargement des polices et l’hébergement fixent tous le plafond de ce que ces chiffres peuvent atteindre. Les optimiser après coup est plus difficile que de les intégrer dès le départ.
Pour les fondations — comment structurer dès le début un site rapide et explorable — poursuivez vers la couche Construction du site. Cette couche couvre les choix d’architecture (rendu statique, pipelines d’images, livraison CDN) qui font de l’atteinte de ces seuils la valeur par défaut plutôt qu’un combat.
À retenir
- ✅ Trois métriques, trois questions : LCP (est-ce affiché ?), INP (a-t-il réagi ?), CLS (ça a bougé ?). Cibles : ≤ 2,5 s, ≤ 200 ms, ≤ 0,1 au 75e centile.
- ✅ L’INP a remplacé le FID en 2024 — mesurez la latence d’interaction complète sur toute la session, pas seulement la première entrée.
- ✅ Déboguez en labo, jugez par le terrain. Un score Lighthouse vert n’est pas un score CrUX réussi.
- ✅ Corrigez le LCP en le décomposant : réduisez le TTFB, éliminez le délai de chargement avec
preload+fetchpriority, et ne chargez jamais le hero en lazy-load. - ✅ Corrigez l’INP en découpant les tâches longues — cédez la main avec
scheduler.yield(), affichez d’abord le retour, et supprimez les re-rendus superflus. - ✅ Corrigez le CLS en réservant l’espace à l’avance : dimensions des images,
min-heightdes emplacements publicitaires, bannières en superposition (sans insertion), et une police de repli ajustée à la métrique.