L'essentiel
En 2026, les Core Web Vitals reposent sur trois métriques : LCP (Largest Contentful Paint), INP (Interaction to Next Paint) qui a définitivement remplacé FID, et CLS (Cumulative Layout Shift) — chacune avec des seuils mis à jour par Google.
Sur Next.js, le LCP se traite en priorité avec le composant Image en mode `priority`, le Partial Prerendering pour le shell statique, et un CDN edge servi depuis Vercel ou Cloudflare — trois leviers qui réduisent le LCP de 30 à 50 % en production.
L'INP se travaille avec le code splitting par route, les Web Workers pour les tâches lourdes et les React 19 transitions qui permettent de marquer les mises à jour non urgentes et d'éviter le blocage du thread principal.
Automatiser la surveillance via Lighthouse CI dans GitHub Actions et Real User Monitoring avec web-vitals.js envoyé à GA4 est la seule façon de garantir que les scores ne régressent pas à chaque déploiement.
Core Web Vitals 2026 : optimiser un site Next.js pour les scores parfaits
INP remplace FID, les seuils ont évolué — voici comment diagnostiquer et corriger LCP, INP et CLS sur un projet Next.js avec des méthodes validées en production B2B.
Adapté à toute taille de structure
#Core Web Vitals en 2026 : ce qui a changé
Depuis mai 2023, Google a officiellement remplacé le First Input Delay (FID) par l'Interaction to Next Paint (INP) dans les signaux de ranking. En 2026, ce changement est pleinement intégré dans Google Search Console, PageSpeed Insights et le rapport CWV du Chrome User Experience Report (CrUX).
Les trois métriques officielles et leurs seuils 2026 :
| Métrique | Bon | Amélioration nécessaire | Mauvais |
|---|---|---|---|
| LCP (Largest Contentful Paint) | ≤ 2,5 s | 2,5 – 4,0 s | > 4,0 s |
| INP (Interaction to Next Paint) | ≤ 200 ms | 200 – 500 ms | > 500 ms |
| CLS (Cumulative Layout Shift) | ≤ 0,1 | 0,1 – 0,25 | > 0,25 |
Google ne publie pas de formule exacte sur le poids de chaque métrique dans le ranking, mais la documentation officielle de Search Central confirme qu'un site dans la zone « Bon » sur les trois métriques bénéficie d'un signal positif dans le classement mobile et desktop.
Le HTTP Archive Almanac 2025 indique que seulement 43 % des sites web dans le monde passent les seuils « Bon » sur les trois métriques simultanément. Sur les sites Next.js bien configurés, ce taux monte à 71 %. L'écart est entièrement expliqué par des choix d'architecture et non par la taille des projets.
#LCP — Largest Contentful Paint : diagnostic et 8 optimisations Next.js
#Comprendre ce que mesure le LCP
Le LCP mesure le temps nécessaire pour que l'élément de plus grande surface visible dans le viewport soit rendu. Sur la majorité des pages, cet élément est soit une image hero, soit un bloc de texte H1 large. Dans Google Search Console, le rapport « Signaux web essentiels » indique quel élément constitue le LCP de vos URLs les plus visitées.
#Diagnostic LCP en pratique
Avant de corriger, identifiez l'élément LCP réel :
// Dans Chrome DevTools > Performance > filmstrip
// ou en JavaScript pour le debugging
new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
console.log('LCP element:', entry.element)
console.log('LCP time:', entry.startTime)
}
}).observe({ type: 'largest-contentful-paint', buffered: true })
#Optimisation 1 — Composant Image avec priority
Le composant <Image> de Next.js ajoute automatiquement loading="lazy" sur toutes les images. Pour l'image LCP, cette valeur doit absolument être contournée :
import Image from 'next/image'
// Image hero = candidate LCP → priority obligatoire
<Image
src="/hero-produit.webp"
alt="Interface de gestion B2B — dashboard Nehos"
width={1200}
height={630}
priority // ← génère fetchpriority="high" + preload dans le <head>
quality={85}
/>
Sans priority, l'image est chargée en lazy — elle n'est même pas demandée au réseau avant que le navigateur ait fini de parser le HTML. C'est la cause n°1 de LCP dégradé sur les projets Next.js.
#Optimisation 2 — Preload explicite dans le <head>
Pour les images servies depuis un CDN externe (Cloudinary, Imagekit, etc.), Next.js ne génère pas automatiquement le preload. Ajoutez-le manuellement dans votre layout :
// app/layout.tsx
export default function RootLayout({ children }) {
return (
<html>
<head>
<link
rel="preload"
as="image"
href="https://cdn.nehos-groupe.com/hero-home.webp"
type="image/webp"
/>
</head>
<body>{children}</body>
</html>
)
}
#Optimisation 3 — CDN edge avec cache-control long
Le TTFB (Time to First Byte) impacte directement le LCP. Sur Vercel, les routes statiques et PPR sont automatiquement servies depuis le CDN edge. Sur une infrastructure personnalisée, configurez des headers de cache longs sur les assets statiques :
// next.config.ts
const config: NextConfig = {
async headers() {
return [
{
source: '/_next/static/(.*)',
headers: [
{ key: 'Cache-Control', value: 'public, max-age=31536000, immutable' }
]
}
]
}
}
#Optimisation 4 — Fonts : display: swap et préchargement
Une police web non préchargée bloque le rendu du texte — si le texte est l'élément LCP, le score s'effondre. Avec next/font :
import { Inter } from 'next/font/google'
const inter = Inter({
subsets: ['latin'],
display: 'swap', // évite le FOIT (Flash of Invisible Text)
preload: true, // génère le lien preload dans <head>
variable: '--font-inter'
})
#Optimisation 5 — Partial Prerendering pour le shell statique
Le PPR permet de servir le shell de la page (header, hero, navigation) depuis le CDN en ~20 ms et de streamer les parties dynamiques ensuite. L'élément LCP fait partie du shell statique et est donc visible très tôt :
// app/produits/[slug]/page.tsx
import { Suspense } from 'react'
export default function ProductPage() {
return (
<main>
{/* Servi depuis CDN — l'image hero est dans ce shell */}
<ProductHero />
{/* Streamé depuis le serveur — n'impacte pas le LCP */}
<Suspense fallback={<ReviewsSkeleton />}>
<ProductReviews />
</Suspense>
</main>
)
}
#Optimisation 6 — Formats AVIF/WebP automatiques
Next.js 16 négocie automatiquement le format optimal. L'AVIF offre 30 à 50 % de compression supplémentaire par rapport au WebP. Sur une image hero de 180 Ko en JPEG, le passage en AVIF donne typiquement 65 à 80 Ko — soit 400 ms de téléchargement économisés sur une connexion 4G standard.
#Optimisation 7 — Éliminer les chaînes de redirections
Chaque redirection ajoute un aller-retour réseau complet. Sur les sites qui empilent des redirections (HTTP → HTTPS, www → non-www, trailing slash), le TTFB effectif peut doubler. Vérifiez avec :
curl -I -L https://monsite.fr/page | grep -E '^(HTTP|Location)'
#Optimisation 8 — SSR sélectif sur les composants critiques
Les composants au-dessus de la ligne de flottaison doivent être rendus côté serveur. Évitez le dynamic(() => import(...), { ssr: false }) sur tout composant qui fait partie du LCP — cela retarde le rendu initial au moment où JavaScript s'exécute côté client.
#INP — Interaction to Next Paint : diagnostic et optimisations
#Ce que mesure l'INP
L'INP mesure la latence entre une interaction utilisateur (clic, frappe clavier, tap) et le moment où le navigateur peint la réponse visuelle. Contrairement au FID qui mesurait uniquement la première interaction, l'INP mesure le pire centile des interactions sur toute la durée de session.
Les causes principales d'INP élevé sur les apps React/Next.js :
- Long Tasks JavaScript sur le thread principal (> 50 ms)
- Rendu de listes longues sans virtualisation
- Calculs synchrones déclenchés par un event handler
- Hydratation complète de pages lourdes au premier clic
#Diagnostic INP
import { onINP } from 'web-vitals'
onINP(({ value, rating, attribution }) => {
console.log('INP:', value, 'ms — rating:', rating)
console.log('Element:', attribution.interactionTarget)
console.log('Long Task duration:', attribution.inputDelay)
})
#Code splitting par route
Next.js 16 split automatiquement par route avec l'App Router. Assurez-vous de ne pas importer de librairies lourdes dans le layout racine — elles seraient chargées sur chaque page :
// app/dashboard/chart/page.tsx
// ✅ Import dynamique : Chart.js ne charge que sur cette route
import dynamic from 'next/dynamic'
const BarChart = dynamic(() => import('@/components/BarChart'), { ssr: false })
#Web Workers pour les calculs lourds
Déplacer les calculs coûteux hors du thread principal est la seule façon d'éviter les Long Tasks qui bloquent les interactions :
// lib/heavy-calc.worker.ts
self.onmessage = (event) => {
const result = expensiveDataTransformation(event.data)
self.postMessage(result)
}
// Dans le composant React
const worker = new Worker(new URL('../lib/heavy-calc.worker', import.meta.url))
worker.postMessage(rawData)
worker.onmessage = (e) => setProcessedData(e.data)
#React 19 Transitions pour les mises à jour non urgentes
React 19 stabilise le pattern useTransition pour différer les mises à jour d'état coûteuses. Le navigateur reste réactif aux autres interactions pendant que React recalcule :
import { useTransition } from 'react'
function SearchInput() {
const [isPending, startTransition] = useTransition()
const [query, setQuery] = useState('')
const [results, setResults] = useState([])
function handleChange(e) {
const val = e.target.value
setQuery(val) // mise à jour urgente — input reste réactif
startTransition(() => {
setResults(filterProducts(val)) // non urgent — peut être interrompu
})
}
return (
<>
<input value={query} onChange={handleChange} />
{isPending && <Spinner />}
<ResultsList data={results} />
</>
)
}
#View Transitions API pour les navigations fluides
Next.js 16 expose un hook useViewTransition qui s'appuie sur la View Transitions API du navigateur. Les navigations entre pages produisent une animation native sans JavaScript côté client — ce qui maintient l'INP bas pendant les transitions.
#CLS — Cumulative Layout Shift : causes et corrections
#Les 4 causes principales de CLS sur Next.js
1. Images sans dimensions déclarées — Sans width et height sur le composant Image, le navigateur ne réserve pas l'espace avant le chargement. L'image s'insère et décale tout le contenu en dessous.
2. Fonts Web avec display: auto ou block — Le chargement asynchrone de la police provoque un flash de mise en page (FOUT / FOIT) qui décale les lignes de texte.
3. Publicités, embeds et iframes sans conteneur de taille fixe — Un bloc publicitaire qui apparaît au-dessus du contenu est la source de CLS la plus fréquente sur les sites éditoriaux.
4. Contenu injecté dynamiquement au-dessus de la ligne de flottaison — Les bannières cookies, les notifications push et les bandeaux promotionnels déclenchent systématiquement du CLS s'ils ne sont pas réservés en amont.
#Correction 1 — aspect-ratio CSS pour toutes les images
/* globals.css */
img, video {
max-width: 100%;
height: auto; /* maintient le ratio et évite le CLS */
}
.card-thumbnail {
aspect-ratio: 16 / 9; /* réserve l'espace avant le chargement */
overflow: hidden;
}
#Correction 2 — Skeleton Loaders pour les blocs dynamiques
Plutôt qu'un simple spinner, un skeleton loader réserve l'espace exact que le contenu final occupera :
// components/ArticleCardSkeleton.tsx
export function ArticleCardSkeleton() {
return (
<div className="animate-pulse">
<div className="aspect-video bg-gray-200 rounded-lg" />
<div className="mt-3 h-5 bg-gray-200 rounded w-3/4" />
<div className="mt-2 h-4 bg-gray-200 rounded w-1/2" />
</div>
)
}
// Utilisation avec Suspense
<Suspense fallback={<ArticleCardSkeleton />}>
<ArticleCard slug={slug} />
</Suspense>
#Correction 3 — font-display: optional pour les fonts critiques
Avec optional, si la police n'est pas disponible dans les 100 ms, le navigateur utilise la police de fallback pour ce premier rendu — sans décalage. La police custom sera utilisée à partir du rechargement suivant (depuis le cache) :
const inter = Inter({
subsets: ['latin'],
display: 'optional', // Pas de swap, pas de FOUT → CLS = 0
})
#Correction 4 — Réserver l'espace pour les bandeaux consentement
/* Réserve l'espace du bandeau en haut de page dès le premier rendu */
body {
padding-top: var(--consent-banner-height, 0);
}
/* Défini côté serveur selon l'état du consentement */
:root {
--consent-banner-height: 72px;
}
#Outils de mesure Core Web Vitals en 2026
#1. Google Search Console — rapport Signaux Web Essentiels
L'outil de référence pour les données de terrain (CrUX). Il agrège les mesures réelles de vos visiteurs Chrome sur 28 jours glissants et ventile les URLs par statut (Bon / Amélioration nécessaire / Mauvais). C'est la seule source de données qui impacte directement le ranking.
#2. PageSpeed Insights
PageSpeed Insights combine données de terrain CrUX et données de laboratoire Lighthouse. Utilisez l'API PSI pour l'automatisation :
curl "https://www.googleapis.com/pagespeedonline/v5/runPagespeed\
?url=https://monsite.fr&strategy=mobile&key=API_KEY" \
| jq '.loadingExperience.metrics'
#3. Chrome DevTools — panneau Performance
Le panneau Performance de DevTools permet d'identifier précisément les Long Tasks, le moment où chaque ressource se charge et quel élément constitue le LCP. L'onglet « Insights » introduit en 2025 résume les problèmes détectés en langage naturel.
#4. Lighthouse CI
Lighthouse CI automatise les audits à chaque déploiement. Configuration minimale dans GitHub Actions :
# .github/workflows/lighthouse.yml
name: Lighthouse CI
on: [push, pull_request]
jobs:
lighthouse:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- name: Install dependencies
run: npm ci
- name: Build Next.js
run: npm run build
- name: Start server
run: npm run start &
- name: Run Lighthouse CI
uses: treosh/lighthouse-ci-action@v12
with:
urls: |
http://localhost:3000/
http://localhost:3000/blog
http://localhost:3000/services/refonte-web
budgetPath: ./budget.json
uploadArtifacts: true
temporaryPublicStorage: true
// budget.json — seuils par défaut Nehos
[
{
"path": "/*",
"resourceSizes": [
{ "resourceType": "total", "budget": 400 },
{ "resourceType": "script", "budget": 150 },
{ "resourceType": "image", "budget": 200 }
],
"timings": [
{ "metric": "interactive", "budget": 3000 },
{ "metric": "first-contentful-paint", "budget": 1500 },
{ "metric": "largest-contentful-paint", "budget": 2500 }
]
}
]
#5. web-vitals.js — Real User Monitoring
La librairie officielle Google pour capturer les métriques CWV en production, directement depuis les navigateurs de vos vrais utilisateurs :
// app/providers/web-vitals.ts
'use client'
import { onCLS, onINP, onLCP, onFCP, onTTFB } from 'web-vitals/attribution'
function sendToGA4(metric) {
window.gtag('event', metric.name, {
event_category: 'Web Vitals',
event_label: metric.id,
value: Math.round(metric.name === 'CLS' ? metric.value * 1000 : metric.value),
non_interaction: true,
metric_rating: metric.rating, // 'good' | 'needs-improvement' | 'poor'
metric_delta: metric.delta,
metric_element: metric.attribution?.largestShiftTarget // Pour le CLS
})
}
onCLS(sendToGA4)
onINP(sendToGA4)
onLCP(sendToGA4)
onFCP(sendToGA4)
onTTFB(sendToGA4)
Dans GA4, créez une exploration personnalisée avec les dimensions event_label et metric_rating pour visualiser la distribution de vos métriques CWV par page, par device et par segment d'utilisateurs.
#Budgets de performance : définir et faire respecter les seuils
Un budget de performance est un contrat d'équipe : toute régression qui dépasse le seuil doit être corrigée avant merge. Sans budget, les scores LCP et CLS dégradent progressivement à chaque sprint.
#Approche Nehos sur les projets B2B
Nous définissons trois niveaux de seuils dans budget.json :
- Seuil cible : la valeur que le projet doit atteindre en production
- Seuil warning : dépassé → flag dans la PR, correction recommandée
- Seuil bloquant : dépassé → la PR ne peut pas merger
// budget.json — niveau 3 : bloquant
{
"timings": [
{ "metric": "largest-contentful-paint", "budget": 2500 },
{ "metric": "total-blocking-time", "budget": 200 }
],
"resourceSizes": [
{ "resourceType": "script", "budget": 175 }
]
}
La règle que nous appliquons systématiquement : le budget est défini dès la phase de découverte, avant la première ligne de code. Le rattraper après coup coûte 3 à 5 fois plus cher.
#Cas Nehos : 3 audits CWV B2B — avant/après avec scores réels
#Cas 1 — SaaS de gestion documentaire (B2B, ~8 000 pages)
Situation initiale : LCP 3,8 s, INP 340 ms, CLS 0,19. Le LCP était causé par une image hero chargée sans priority depuis un CDN externe non préchargé.
Corrections appliquées : ajout de priority sur l'image hero, preload explicite dans le <head>, migration vers l'Image Component avec AVIF automatique, activation du PPR sur les pages produit.
Résultat après 30 jours : LCP 1,6 s (-58 %), INP 110 ms (-68 %), CLS 0,04 (-79 %). Passage de la zone « Mauvais » à « Bon » sur les trois métriques. Progression de 4 positions en moyenne sur les requêtes cibles dans Search Console.
#Cas 2 — E-commerce headless B2B (Shopify + Next.js, ~12 000 SKUs)
Situation initiale : LCP 4,2 s, INP 520 ms, CLS 0,22. L'INP élevé venait d'un filtre de catalogue qui recalculait 12 000 produits sur le thread principal à chaque frappe.
Corrections appliquées : migration du filtrage vers un Web Worker, virtualisation de la liste avec react-window, useTransition sur la mise à jour des résultats, skeleton loaders sur les tuiles produit.
Résultat après 45 jours : LCP 2,1 s (-50 %), INP 145 ms (-72 %), CLS 0,07 (-68 %). Le taux de conversion mobile a augmenté de 12 % — mesurable directement dans GA4 après le passage dans la zone « Bon ».
#Cas 3 — Site corporate B2B international (8 langues, Next.js App Router)
Situation initiale : LCP 2,9 s, INP 210 ms, CLS 0,13. Le CLS était entièrement dû aux fonts Google Fonts chargées en display: swap sans dimensionnement des fallback fonts.
Corrections appliquées : migration vers next/font avec display: optional sur les fonts critiques et size-adjust CSS sur les fonts de fallback pour aligner les métriques typographiques, réservation d'espace pour le bandeau consentement.
Résultat après 21 jours : LCP 1,8 s (-38 %), INP 170 ms (-19 %), CLS 0,02 (-85 %). Score Lighthouse mobile passé de 64 à 91.
#Impact sur le ranking SEO : ce que Google confirme en 2026
Google a publié en mars 2026 une mise à jour de sa documentation Search Central sur les signaux de page. Trois éléments sont confirmés officiellement :
1. Les Core Web Vitals sont un facteur de ranking actif, mais pas déterminant à eux seuls. Google précise que deux pages de « qualité similaire » verront la page CWV meilleure être favorisée — la qualité éditoriale reste prépondérante.
2. Le seuil de discrimination est le passage de « Amélioration nécessaire » à « Bon ». Passer de « Mauvais » à « Amélioration nécessaire » produit moins d'effet sur le ranking qu'atteindre « Bon ».
3. L'expérience mobile est évaluée indépendamment de l'expérience desktop. Depuis l'index mobile-first, ce sont les scores mobiles qui déterminent le ranking — y compris pour les requêtes desktop.
Sur les 3 projets B2B présentés ci-dessus, nous avons mesuré une amélioration moyenne de la position dans les SERP de +3,2 positions sur les pages optimisées, dans les 60 jours suivant le passage en « Bon » sur les trois métriques. Ces mesures ne permettent pas d'isoler l'effet CWV des autres variables, mais la corrélation est nette et cohérente avec les déclarations officielles de Google.