Accueil >Web >Performance web >Mesurer la vitesse d’un site : Core Web Vitals

Mesurer la vitesse d’un site : Core Web Vitals

La vitesse d’un site ne se mesure pas avec un seul chiffre. Google l’évalue avec trois métriques Core Web Vitals – LCP, INP et CLS. Ce qu’elles signifient, quelles valeurs sont bonnes, comment les mesurer et comment les collecter auprès de vrais visiteurs dans votre propre base de données.

Pourquoi la vitesse compte

Un site lent fait fuir les visiteurs – ils attendent le contenu, appuient sur des boutons qui ne réagissent pas et le texte se dérobe sous leur doigt. De plus, Google compte les métriques Core Web Vitals parmi les signaux d’après lesquels il classe les pages dans les résultats de recherche. Le contenu reste toutefois le plus important pour lui ; la vitesse n’est qu’un signal parmi beaucoup d’autres.

Les trois métriques Core Web Vitals

  • LCP (Largest Contentful Paint) – le temps qu’il faut pour que la plus grande image ou le plus grand bloc de texte apparaisse dans la partie visible de la page. Il indique quand le visiteur voit le contenu principal. Bon : jusqu’à 2,5 s, mauvais : plus de 4 s.
  • INP (Interaction to Next Paint) – la rapidité avec laquelle la page réagit à un clic, à un toucher ou à une touche du clavier, c’est-à-dire le délai entre l’action et l’affichage du changement par le navigateur. C’est la réaction la plus lente de toute la visite qui compte (sur les pages avec beaucoup d’interactions, on écarte la pire de chaque tranche de 50). Bon : jusqu’à 200 ms, mauvais : plus de 500 ms. En mars 2024, l’INP a remplacé l’ancienne métrique FID, qui ne mesurait que la première interaction.
  • CLS (Cumulative Layout Shift) – à quel point le contenu saute, par exemple quand une image sans dimensions se charge et pousse le texte vers le bas. C’est un nombre sans unité. Bon : jusqu’à 0,1, mauvais : plus de 0,25. Les décalages dans la demi-seconde qui suit un clic ou une touche ne comptent pas – le visiteur s’y attend.

Une page satisfait une métrique quand au moins 75 % des visites ont une bonne valeur (75e centile), séparément pour les téléphones et les ordinateurs. Entre la bonne et la mauvaise valeur se trouve la zone « à améliorer ». Les seuils n’ont pas changé depuis 2024 (état en octobre 2026).

Des métriques complémentaires, qui ne font pas partie des Core Web Vitals, expliquent pourquoi le LCP est lent :

  • TTFB (Time to First Byte) – quand arrive le premier octet du HTML. Bon jusqu’à 0,8 s. Il comprend aussi bien le trajet jusqu’au serveur que le travail de PHP et de la base de données, voir Vitesse du serveur.
  • FCP (First Contentful Paint) – quand apparaît le premier texte ou la première image. Bon jusqu’à 1,8 s.

Données de laboratoire et données de terrain

Une mesure de laboratoire se fait une seule fois, sur un seul ordinateur et dans des conditions simulées (processeur et connexion plus lents). Elle est utile pour l’optimisation, car elle donne tout de suite un résultat et montre ce qu’on peut améliorer :

  • Lighthouse – dans Chrome, dans les outils de développement (F12), onglet Lighthouse ;
  • le panneau Performance des outils de développement – il affiche le LCP, le CLS et l’INP en direct pendant que vous utilisez la page ;
  • PageSpeed Insights (pagespeed.web.dev) – la partie inférieure du résultat est une mesure Lighthouse.

Un test de laboratoire ne clique nulle part, il ne peut donc pas mesurer l’INP. À la place, il affiche le TBT (Total Blocking Time) – combien de temps le navigateur a été occupé par JavaScript pendant le chargement.

Les données de terrain proviennent de vrais visiteurs, sur leurs appareils. Google les collecte auprès des utilisateurs de Chrome dans le rapport CrUX (Chrome User Experience Report) sur les 28 derniers jours. Elles figurent dans la partie supérieure du résultat de PageSpeed Insights et dans le rapport Signaux Web essentiels de la Google Search Console. C’est d’après elles que Google classe les pages – mais elles n’existent que pour les sites qui ont suffisamment de visiteurs. Un site plus petit peut collecter lui-même ses données de terrain, comme le montre la suite de l’article.

Mesurer dans le navigateur : Performance API

Le navigateur enregistre lui-même les temps et JavaScript les lit avec PerformanceObserver. Cet exemple affiche les candidats au LCP (lancez-le dans la console des outils de développement) :

new PerformanceObserver((list) => {
  const last = list.getEntries().at(-1);
  console.log('LCP:', Math.round(last.startTime), 'ms', last.element);
}).observe({ type: 'largest-contentful-paint', buffered: true });

L’option buffered: true ajoute aussi les entrées antérieures au lancement du script. Il peut y avoir plusieurs entrées – à mesure que la page se charge, des éléments plus grands apparaissent. C’est la dernière entrée avant la première action du visiteur (clic, touche, défilement) qui compte ; ensuite, le navigateur ne signale plus de candidats. startTime se mesure depuis le début du chargement de la page.

Les temps de chargement de la page elle-même sont donnés par l’entrée de type navigation :

const nav = performance.getEntriesByType('navigation')[0];
console.log('TTFB:', Math.round(nav.responseStart), 'ms');
console.log('HTML downloaded:', Math.round(nav.responseEnd), 'ms');
console.log('DOMContentLoaded:', Math.round(nav.domContentLoadedEventEnd), 'ms');
console.log('Everything loaded:', Math.round(nav.loadEventEnd), 'ms');

La valeur loadEventEnd vaut 0 tant que la page n’est pas entièrement chargée – lancez l’exemple seulement après le chargement. Les navigateurs prennent en charge les différentes mesures de façon inégale (état en octobre 2026) : le LCP et la mesure des réactions pour l’INP fonctionnent dans Chrome, Edge, Firefox et, depuis la version 26.2, Safari ; les décalages de mise en page pour le CLS seulement dans les navigateurs basés sur Chromium (Chrome, Edge, Opera). La liste des types pris en charge par le navigateur est donnée par PerformanceObserver.supportedEntryTypes.

Collecter auprès des visiteurs : la bibliothèque web-vitals

Calculer correctement l’INP et le CLS demande du travail – le CLS, par exemple, se calcule par fenêtres de décalages. Google fournit pour cela une petite bibliothèque, web-vitals, qui mesure de la même façon que Chrome pour CrUX. Le mieux est de télécharger le fichier de la bibliothèque sur votre propre site, par exemple depuis https://unpkg.com/web-vitals@6/dist/web-vitals.js (version 6, état en octobre 2026) dans le dossier js. Vous ne dépendez alors d’aucun autre serveur et les visiteurs ne se connectent nulle part ailleurs :

<script type="module">
  import { onCLS, onINP, onLCP } from '/js/web-vitals.js';
  function send(metric) {
    const data = JSON.stringify({ name: metric.name, value: metric.value, rating: metric.rating, page: location.pathname });
    navigator.sendBeacon('/vitals.php', data);
  }
  onCLS(send);
  onINP(send);
  onLCP(send);
</script>

La bibliothèque appelle la fonction send quand la valeur est définitive – le LCP après la première action du visiteur, le CLS et l’INP généralement seulement quand le visiteur ferme la page ou la quitte. C’est pourquoi les données partent avec navigator.sendBeacon() : le navigateur les envoie même pendant la fermeture de la page. La chaîne part en POST avec le type text/plain. La propriété rating contient good, needs-improvement ou poor selon les seuils ci-dessus. Une métrique que le navigateur ne sait pas mesurer (le CLS dans Firefox et Safari), la bibliothèque ne la signale tout simplement pas.

Côté serveur, un court script qui vérifie et enregistre les données suffit. La table dans MySQL ou MariaDB :

CREATE TABLE vitals (
  id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(10) NOT NULL,
  value DOUBLE NOT NULL,
  rating VARCHAR(20) NOT NULL,
  page VARCHAR(255) NOT NULL,
  time DATETIME NOT NULL,
  INDEX (name, time)
);

Le script vitals.php n’accepte que des valeurs connues et les enregistre avec une requête préparée – les données sont envoyées par le navigateur du visiteur, on ne peut donc pas leur faire confiance :

<?php
$data = json_decode(file_get_contents('php://input'), true);
$metrics = array('CLS', 'INP', 'LCP');
$ratings = array('good', 'needs-improvement', 'poor');
if(!is_array($data) || !in_array($data['name'] ?? null, $metrics, true) || !in_array($data['rating'] ?? null, $ratings, true)
  || !is_numeric($data['value'] ?? null) || !is_string($data['page'] ?? null))
  {
    http_response_code(400);
    exit;
  }
$pdo = require __DIR__.'/../private/db.php';   // connexion à la base de données, fichier en dehors de la racine du site
$query = $pdo->prepare('INSERT INTO vitals (name, value, rating, page, time) VALUES (?, ?, ?, ?, NOW())');
$query->execute(array($data['name'], (float) $data['value'], $data['rating'], mb_substr($data['page'], 0, 255)));
http_response_code(204);

La réponse 204 signifie « reçu, sans contenu ». La connexion à la base de données se trouve dans un fichier en dehors de la racine du site, comme le décrit Mots de passe et clés dans un fichier .env. Le bilan des 28 derniers jours – quel pourcentage des mesures est bon :

SELECT name, COUNT(*) AS measurements, ROUND(100 * AVG(rating = 'good')) AS good_percent
FROM vitals
WHERE time >= NOW() - INTERVAL 28 DAY
GROUP BY name
ORDER BY name;
+------+--------------+--------------+
| name | measurements | good_percent |
+------+--------------+--------------+
| CLS  |            4 |           75 |
| INP  |            3 |           67 |
| LCP  |            5 |           80 |
+------+--------------+--------------+

L’expression rating = 'good' donne 1 ou 0, sa moyenne est donc la part des bonnes mesures. Une métrique est satisfaite quand au moins 75 % des mesures sont bonnes – c’est la même chose que la condition du 75e centile. Dans l’exemple, le CLS et le LCP sont donc satisfaits, et l’INP est à améliorer. Il vaut aussi la peine de répartir les résultats par page (GROUP BY page, name) – souvent, seule une partie du site est lente.

Articles connexes :
Images : formats, tailles et lazy loading
Vitesse du serveur : TTFB, OPcache et base de données
Requêtes préparées avec PDO