Core Web Vitals : actions concrètes pour améliorer LCP, INP et CLS

Actions concrètes pour améliorer LCP, INP et CLS sur boutiques PrestaShop : RUM, profiling, optimisation images/JS, cache et priorisation par valeur business.

Tableau de bord numérique détaillé affichant des graphiques et des statistiques pour l'optimisation web.

Table des matières :

  1. Core Web Vitals en 2026 : ce que Google mesure réellement (et comment ne pas se tromper)
  2. Mettre en place une mesure exploitable : RUM + profiling, pas seulement des scores
  3. INP : supprimer les “long tasks” et rendre les interactions réellement réactives
  4. CLS : éliminer les sauts de mise en page (dimensions, polices, contenus injectés)
  5. Plan d’attaque pragmatique : prioriser, industrialiser, éviter les fausses bonnes idées

Core Web Vitals en 2026 : ce que Google mesure réellement (et comment ne pas se tromper)

Depuis mars 2024, INP (Interaction to Next Paint) a officiellement remplacé FID dans les Core Web Vitals. En 2026, le trio à piloter côté e-commerce reste donc LCP / INP / CLS. Google rappelle des seuils simples mais non négociables : « To provide a good user experience, sites should strive to have LCP occur within the first 2.5 seconds of the page starting to load » (Google, web.dev, “Largest Contentful Paint”). Pour INP : « An INP of 200 milliseconds or less is considered “good” » (Google, web.dev, “Interaction to Next Paint”). Et pour CLS : « A CLS score of 0.1 or less is considered “good” » (Google, web.dev, “Cumulative Layout Shift”).

Pour éviter les contresens, gardez en tête la grille “bon / à améliorer / mauvais” (ce ne sont pas des objectifs “SEO”, ce sont des seuils UX utilisés pour classer votre expérience) :

Métrique Bon À améliorer Mauvais
LCP ≤ 2,5 s 2,5–4,0 s > 4,0 s
INP ≤ 200 ms 200–500 ms > 500 ms
CLS ≤ 0,1 0,1–0,25 > 0,25

Le point qui fait perdre du temps aux équipes PrestaShop : confondre mesure de laboratoire (Lighthouse / PageSpeed Insights) et données terrain (CrUX, données Chrome réelles). Lighthouse est utile pour reproduire et profiler (CPU throttling, réseau simulé), mais la vérité SEO est dans les distributions terrain (p75). Autrement dit : optimiser “pour Lighthouse” sans vérifier CrUX revient souvent à déplacer le problème (scripts tiers, devices bas de gamme, cache froid).

Concrètement, une boutique peut “passer” Lighthouse avec un cache chaud, puis échouer en terrain parce que :

  • une part significative de votre audience navigue en 4G instable (transports, zones périurbaines) et déclenche plus de cache froid ;
  • des scripts tiers (chat, tag manager, A/B test) se déclenchent différemment selon consentement, pays, campagnes ;
  • les pages les plus vues (catégorie / produit) ont des templates et modules très différents de la home.

Sur PrestaShop (8.x / 9.x), vous devez découper le diagnostic par types de pages (home, catégorie, produit, panier/checkout) et par templates (thème + overrides + modules). Les Core Web Vitals pénalisent fréquemment les pages catégorie et produit, parce que le LCP y est souvent une image héro (produit) ou un bloc de listing rendu tard (catégorie), et l’INP souffre du JS “utility” ajouté par modules (tracking, widgets, comparateurs, chat). Avant de toucher au code, instrumentez et attribuez.

Astuce opérationnelle (souvent négligée) : priorisez par business, pas par ego technique. Dans la plupart des e-commerces PrestaShop, une dégradation INP sur le checkout coûte plus cher qu’un LCP moyen sur la home. Faites donc votre tri par :

  • pages à fort trafic (SEO + campagnes),
  • pages à fort taux de conversion (produit / panier / paiement),
  • pages à forte valeur (catégories “money keywords”).

Références utiles :

Mettre en place une mesure exploitable : RUM + profiling, pas seulement des scores

Si vous n’avez pas de RUM (Real User Monitoring) minimal, vous optimisez à l’aveugle. Pour une boutique PrestaShop, le compromis propre consiste à collecter LCP/INP/CLS via la librairie officielle web-vitals (Google) et à pousser des événements vers votre stack (Matomo, GA4, endpoint interne). L’intérêt : segmenter par device, réseau, template, module activé, et repérer des régressions lors d’un déploiement.

Exemple (front) :

import {onLCP, onINP, onCLS} from 'web-vitals';

function sendToAnalytics(metric) {
  navigator.sendBeacon('/metrics/cwv', JSON.stringify({
    name: metric.name,
    value: metric.value,
    id: metric.id,
    url: location.href,
    navType: performance.getEntriesByType('navigation')[0]?.type,
  }));
}

onLCP(sendToAnalytics);
onINP(sendToAnalytics);
onCLS(sendToAnalytics);

Deux enrichissements simples qui rendent cette collecte vraiment exploitable sur PrestaShop :

  • Ajouter le contexte PrestaShop (controller, idproduct, idcategory, thème) pour éviter des métriques “globales” inutilisables.
  • Collecter l’attribution quand possible (selon version/config de web-vitals) : pour LCP, connaître l’élément, la phase (TTFB / load delay / load time / render delay) aide à trancher “serveur vs front”.

Côté backend, n’enregistrez pas “tout” : stockez un échantillon (1–5%), et agrégerez en p75/p90. Et surtout, liez vos métriques aux identifiants de pages PrestaShop (controller, idproduct, idcategory) via data-* ou variables Smarty. Cela permet de dire “la page produit lente = image 1 en 2400px + module X qui bloque le main thread”.

Checklist “RUM utile” (sans sur-ingénierie) :

  • [ ] 1 endpoint dédié (/metrics/cwv) avec logs séparés (sinon vous polluez vos logs applicatifs).
  • [ ] suppression/arrondi des URL (éviter d’enregistrer des paramètres sensibles, et réduire la cardinalité).
  • [ ] échantillonnage côté client (plus simple) ou côté serveur (plus contrôlable).
  • [ ] stockage d’un identifiant de build/déploiement (commit hash, version thème) pour corréler une régression.
  • [ ] conformité : si vous exploitez ces données à des fins d’analyse, appliquez vos règles RGPD (minimisation, durée de conservation, finalité).

Pour le profiling, passez sur Chrome DevTools Performance + “Performance Insights”. Sur INP en particulier, vous cherchez des Long Tasks (> 50 ms) et des handlers d’événements qui font du DOM/JS lourd. Sur LCP, vous cherchez la chaîne complète : TTFB, CSS bloquante, JS bloquant, découverte tardive de la ressource LCP, décodage image.

Prérequis / risques : tout changement de cache, compression, règles HTTP, ou concat/minify doit être validé sur un environnement de staging. Si vous migrez vers PrestaShop 9.1 et PHP récents, vérifiez la compatibilité (notamment modules).

Sur PrestaShop, un classique : modules qui ajoutent des requêtes N+1 sur les listings (catégories). Si vous surchargez des hooks de grille, auditez les accès DB et utilisez le cache (ou pré-chargement) au lieu de requêter par produit. Exemple de point d’entrée côté BO/Grille : Hook actionProductGridDataModifier PrestaShop : emplacement, appel et usage développeur — le même principe (ne pas enrichir “à la volée” sans stratégie) s’applique aux pages FO si vos modules injectent des calculs lourds.

Actions concrètes “TTFB” à valider (sans tout refondre) :

  • activer/contrôler OPcache et la mémoire allouée (éviter les resets fréquents) ;
  • réduire les appels externes côté backend (API livraison/paiement appelées trop tôt, géoloc, etc.) ;
  • vérifier l’usage de cache Symfony en prod (warmup après déploiement) ;
  • sur les pages très cacheables (home, catégories), envisager un reverse-proxy/CDN en respectant les cookies (sinon vous “cassez” le cache et vous ne gagnez rien).

2) Chemin critique : CSS/JS bloquants, rendu initial

Le LCP souffre quand le navigateur ne peut pas peindre parce que : (a) CSS principale trop lourde et non découpée, (b) JS synchrones en tête, (c) polices web bloquantes. Sur un thème PrestaShop, la “solution” rapide est souvent un module de minification/concat. En pratique, ça casse facilement (ordre des dépendances, inline scripts) et peut augmenter l’INP. Une approche plus robuste : extraire du CSS critique (au moins pour above-the-fold) et différer le reste. Des outils comme critical (Node) peuvent générer du critical CSS par template (home/cat/product).

Deux leviers réseau qui aident souvent sur les pages produit (sans “magie”) :

  • preconnect vers le domaine qui sert l’image LCP (CDN / sous-domaine image), si différent du HTML ;
  • preload de la ressource LCP quand elle est parfaitement déterministe (attention aux variantes produit qui changent l’image).

Côté images LCP : utilisez AVIF/WebP avec fallback, et surtout servez une taille cohérente (srcset/sizes). Un cover produit en 2000–3000 px affiché en 600 px est une faute de base : vous payez le téléchargement + décodage. Sur LCP, le décodage image peut compter (surtout sur mobile). Ajoutez fetchpriority="high" sur l’image LCP et évitez de la lazy-loader (la lazy-load sur l’élément LCP est un anti-pattern).

Exemple (template produit) :

<img
  src="/img/p/1/2/12-home_default.webp"
  srcset="/img/p/1/2/12-large_default.webp 800w, /img/p/1/2/12-home_default.webp 600w"
  sizes="(max-width: 768px) 92vw, 600px"
  width="600" height="600"
  fetchpriority="high"
  decoding="async"
  alt="...">

3) Cache HTTP et assets statiques : banal mais déterminant

Votre LCP sur mobile se joue souvent sur la répétabilité : cache navigateur, cache CDN, cache reverse proxy. Activez un cache long sur les assets versionnés (CSS/JS/images) et invalidez par hash (nom de fichier) plutôt que par purge brutale. Sur Nginx, ça ressemble à :

location ~* \.(css|js|png|jpg|jpeg|gif|webp|avif|svg|woff2)$ {
  expires 30d;
  add_header Cache-Control "public, max-age=2592000, immutable";
}

Sur PrestaShop, évitez les URLs d’assets non versionnées servies depuis des modules qui changent “sans cache-busting” : vous serez obligé de réduire le TTL, donc vous dégradez LCP. Point d’attention e-commerce : les cookies (panier, session, consentement) peuvent forcer un Vary: Cookie ou empêcher le cache sur le HTML. D’où l’intérêt de distinguer :

  • HTML (souvent personnalisé → cache délicat),
  • assets statiques (cache agressif quasi toujours gagnant).

Si vous migrez de 1.7/8 vers 9 pour nettoyer la dette (thème, overrides, modules), faites-le proprement (staging, tests de régression). Référence interne : PrestaShop 9 : guide de migration depuis 1.7 et 8 avec 1-Click Upgrade.

INP : supprimer les “long tasks” et rendre les interactions réellement réactives

INP mesure la latence entre une interaction utilisateur (clic, tap, touche) et la prochaine peinture. Sur les boutiques PrestaShop, les pires INP viennent rarement du cœur : ils viennent des scripts tiers, des bundles JS gonflés (thème + modules), et des handlers qui font des recalculs DOM/JS synchrones (ex. : recalcul panier, variations produit, validations de formulaire). Google le résume sans détour : « INP is a metric that assesses a page’s overall responsiveness to user interactions » (Google, web.dev, “Interaction to Next Paint”).

Pour rester pragmatique, pensez “moments critiques” : ouvrir le menu, filtrer une catégorie, changer une déclinaison, ajouter au panier, appliquer un code promo, passer une étape checkout. Ce sont ces interactions qui concentrent les pires INP et les impacts business (abandon, rage-click).

1) Audit JS : “moins”, “plus tard”, “par page”

Action concrète : faire un inventaire des scripts chargés sur chaque controller (home/category/product/cart/checkout). Beaucoup de thèmes/modules chargent des scripts globalement “par simplicité”. C’est une dette directe sur INP : parse/compile + exécution + contention main thread. Visez un chargement conditionnel par page (ex. ne pas charger un slider sur checkout), et découpez les bundles si vous buildiez (Webpack/Vite). Même sans build moderne, vous pouvez déjà retarder le non-critique : defer pour la majorité des scripts, et type="module" si vous migrez vers un pipeline moderne.

Sur PrestaShop, la tentation est d’empiler des modules de tracking/chat/AB test. Côté INP, c’est souvent du “death by a thousand cuts” : plusieurs petits scripts créent des tâches longues (listeners multiples, mutations DOM, timers). Mesurez via DevTools → Performance : si vous voyez du “Scripting” lourd au moment d’un clic “Ajouter au panier”, commencez par désactiver les scripts tiers un par un (en staging) et comparez l’INP RUM.

Indicateur simple à suivre en parallèle d’INP (très parlant en e-commerce) :

  • Poids JS total exécuté sur page produit (et pas seulement téléchargé). Un script minifié “léger” peut coûter cher à l’exécution sur mobile.

2) Rendre les handlers non bloquants : micro-optimisations qui comptent

Quand vous tenez un coupable (ex. handler click sur variante produit), l’objectif est de réduire le travail synchrone avant le prochain paint. Bonnes pratiques :

  • Marquer les listeners scroll/touch comme passive: true quand applicable.
  • Remplacer des boucles DOM coûteuses par une stratégie de cache (sélection une fois, réutilisation).
  • Découper une grosse opération en lots via requestAnimationFrame / setTimeout(0) / scheduler.postTask (si support) pour laisser respirer le rendu.
  • Déporter des calculs purs (sans DOM) dans un Web Worker quand c’est massif.

Exemple : au lieu de recalculer et re-render tout le DOM du mini-panier synchronement, mettez à jour un sous-ensemble minimal, et repoussez le reste à l’idle :

btn.addEventListener('click', async () => {
  // Feedback immédiat
  btn.disabled = true;
  btn.classList.add('is-loading');

  // Network
  const res = await fetch('/panier', {method: 'POST', body: formData});
  const data = await res.json();

  // Peinture rapide : juste le compteur
  cartCount.textContent = data.count;

  // Reste du rendu quand le thread est dispo
  requestIdleCallback(() => renderMiniCart(data), {timeout: 500});

  btn.disabled = false;
  btn.classList.remove('is-loading');
});

Deux détails qui font souvent la différence sur INP (surtout sur mobile milieu de gamme) :

  • donner un feedback UI immédiat (état loading, bouton désactivé) pour réduire les multi-clics et interactions concurrentes ;
  • éviter de déclencher une cascade de reflows (ex. recalculer toute la page alors que seul un compteur change).

3) DOM et layout thrashing : le piège silencieux

INP explose si votre code fait des allers-retours lecture/écriture de layout (ex. offsetHeight puis style.* puis offsetWidth dans une boucle). C’est du layout thrashing. Sur des thèmes PrestaShop avec beaucoup de composants (sticky header, modales, quickview), ce pattern est fréquent. Correctif : regrouper les lectures, puis les écritures ; utiliser transform plutôt que top/left ; limiter les getBoundingClientRect() dans des boucles.

Si vous faites du front sur PS 8/9, imposez un “budget” : aucune interaction critique (add to cart, change variant, open menu) ne doit générer de Long Task > 50–100 ms sur mobile mid-range. Ce n’est pas une règle Google officielle, mais c’est un seuil de réalité : au-delà, l’INP s’effondre.

CLS : éliminer les sauts de mise en page (dimensions, polices, contenus injectés)

Le CLS est rarement un “gros bug”, c’est plutôt un ensemble de petites instabilités : images sans dimensions, bannières injectées, cookies banners, barres promo, variations de hauteur sur les blocs, polices web qui changent la métrique. Google est explicite : « CLS measures the sum total of all individual layout shift scores for every unexpected layout shift that occurs during the entire lifespan of the page » (Google, web.dev, “Cumulative Layout Shift”). Sur une boutique, l’impact est direct : clics ratés, frustration, baisse conversion.

Point “terrain” fréquent en e-commerce : le CLS ne vient pas seulement de vos images, mais de contenus injectés selon conditions (livraison offerte au-delà d’un seuil, bandeau promo daté, message cookie, widget avis qui apparaît quand l’API répond). Si vous ne réservez pas d’espace, le CLS va varier… et vous aurez l’impression de “ne pas reproduire” le problème.

1) Réserver l’espace : images, iframes, carrousels, composants dynamiques

Action la plus rentable : toujours fournir width/height sur les images et/ou utiliser aspect-ratio en CSS. Sur PrestaShop, vérifiez le HTML généré dans les templates (product, category, home) et les modules qui injectent des images (badges, trust, paiements). Un composant “carousel” doit avoir une hauteur fixe ou un ratio fixe, sinon il pousse le contenu une fois les images chargées.

Pour les iframes (vidéo produit, widgets), même règle : placeholder avec ratio fixe. Et si un module ajoute une barre promo en haut après le chargement : c’est du CLS pur. La solution est de réserver dès le départ l’espace (un wrapper vide de hauteur connue), ou d’ancrer en overlay (position fixe) sans déplacer le layout.

Mini-check CLS “composants dynamiques” :

  • si un bloc dépend d’un appel réseau, prévoyez un placeholder de même hauteur ;
  • si un badge/étiquette produit apparaît au chargement, positionnez-le en overlay dans un conteneur dimensionné ;
  • évitez d’insérer des éléments “avant” le contenu déjà rendu (préférez les ajouter en fin, ou en overlay).

2) Polices : éviter FOIT/FOUT qui modifie les métriques

Les fonts web peuvent provoquer des shifts quand la font finale remplace la font fallback (différence de largeur/hauteur). Trois actions :

  • font-display: swap; (ou optional selon stratégie) sur vos @font-face.
  • preload des WOFF2 critiques (une seule famille/poids, pas tout).
  • Ajuster les fallbacks via size-adjust / ascent-override (CSS Fonts Level 4) si vous visez un CLS très bas.

Exemple minimal :

@font-face{
  font-family: "Inter";
  src: url("/fonts/inter.woff2") format("woff2");
  font-display: swap;
}

3) Comportements PrestaShop qui génèrent du CLS

Cas récurrent : le header qui change de hauteur après initialisation JS (sticky, ajout d’une ligne “livraison offerte”, insertion d’un bandeau cookie). Sur PS, beaucoup de thèmes ajoutent ces blocs via JS à DOMContentLoaded, donc le layout bouge. Corrigez en rendant ces éléments dans le HTML initial (Smarty) ou en réservant l’espace via CSS.

Autre classique : les messages d’erreur/validation dans les formulaires (adresse, checkout) qui s’insèrent au-dessus des champs, provoquant des shifts. Solution : réserver un espace pour les messages (min-height) et afficher le message sans déplacer les champs (ou en dessous avec un wrapper fixe).

Un cas très concret en France/UE : le bandeau cookies/consentement est quasi systématique. S’il s’affiche en poussant le contenu (au lieu d’être overlay), votre CLS peut passer de “bon” à “mauvais” sur une part significative des sessions. La règle d’or : ne jamais déplacer le contenu après le premier rendu pour afficher un bandeau légal — réservez ou superposez.

Plan d’attaque pragmatique : prioriser, industrialiser, éviter les fausses bonnes idées

Pour une boutique PrestaShop, vous n’avez pas besoin de 40 optimisations “micro”. Vous avez besoin de 10 actions ciblées, mesurées, et non régressives. Commencez par un tableau par template : LCP element, LCP (p75), INP (p75), CLS (p75), poids JS, requêtes, TTFB. Une fois la baseline posée, fixez un budget : ex. LCP p75 < 2.5 s sur pages produit, INP p75 < 200 ms sur panier/checkout, CLS p75 < 0.1 partout.

Modèle de tableau (simple, mais actionnable) :

Template URL exemple LCP p75 Élément LCP INP p75 CLS p75 Suspicion principale Prochaine action
Produit /produit-x
Catégorie /categorie-y
Panier /panier
Checkout /commande

Industrialisez la détection de régressions : un job CI qui lance Lighthouse (ou Lighthouse CI) sur 4 URLs clés (home/cat/product/checkout) à chaque merge, avec un seuil de tolérance. Ne bloquez pas sur le score global, bloquez sur les métriques (LCP/INP/CLS) et sur des garde-fous (JS total, nombre de requêtes). Ça évite qu’un module “anodin” ajoute 300 KB de JS et fasse exploser INP.

Enfin, méfiez-vous des “optimisations” qui masquent le problème : lazy-loader l’image LCP, injecter du skeleton qui shift, concaténer tout le JS en un seul fichier gigantesque, ou activer un cache agressif sans invalidation correcte. L’approche robuste reste : moins de JS, assets versionnés, priorités réseau correctes, rendu initial stable, TTFB maîtrisé. Et quand vous devez toucher à l’infra (reverse proxy, CDN, HTTP/3, Brotli), documentez et testez : les gains sont réels, mais les effets de bord (cache incohérent, cookies, variations) sont fréquents sur des shops avec beaucoup de personnalisation.

Pour valider proprement avant mise en production :

  • Vérifiez CrUX/field (ou RUM interne) sur au moins 7 jours glissants.
  • Refaites un profil DevTools sur un Android milieu de gamme (CPU limité).
  • Testez en cache froid (première visite) et cache chaud (repeat).
  • Contrôlez que le checkout n’a pas d’INP dégradé (c’est là que ça coûte).
  • Comparez au minimum 2 segments : mobile vs desktop (les régressions INP “n’existent” parfois que sur mobile).

Si vous avez besoin d’un point d’ancrage pour tester des gabarits et vérifier vos injections front, une page statique de référence peut servir à isoler le bruit des modules (ex. comparaison “page d’exemple” vs pages dynamiques) : https://www.expertise-prestashop.fr/page-d-exemple/.


À lire aussi