Nehos Groupe

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

Artisan
Startup
PME / TPE
ETI
Grand Groupe
C
Chokri Siala
··next-headless

#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étriqueBonAmélioration nécessaireMauvais
LCP (Largest Contentful Paint)≤ 2,5 s2,5 – 4,0 s> 4,0 s
INP (Interaction to Next Paint)≤ 200 ms200 – 500 ms> 500 ms
CLS (Cumulative Layout Shift)≤ 0,10,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.

Questions & Réponses

Questions fréquentes sur les Core Web Vitals Next.js 2026

Le FID (First Input Delay) mesurait uniquement la latence de la toute première interaction utilisateur sur la page. L'INP (Interaction to Next Paint), qui l'a remplacé définitivement en mai 2023, mesure le pire centile de toutes les interactions tout au long de la session : clics, frappes clavier, taps. L'INP est beaucoup plus représentatif de l'expérience réelle car une page peut très bien répondre rapidement à la première interaction puis ralentir après hydratation.
Lighthouse mesure en conditions de laboratoire contrôlées (connexion simulée, machine sans extensions). Google Search Console agrège les données CrUX — mesures réelles des vrais utilisateurs Chrome sur vos pages. Les causes d'écart les plus fréquentes : des utilisateurs sur réseau mobile lent, des scripts tiers (chat, analytics) qui chargent en production mais pas en lab, ou une personnalisation serveur qui ajoute de la latence pour certains segments d'utilisateurs. Priorisez toujours les données CrUX pour le diagnostic SEO.
Le composant Image résout automatiquement le format (AVIF/WebP), le lazy loading et le srcset responsive. Mais il ne suffit pas seul : vous devez ajouter `priority` sur l'image candidate LCP, configurer un CDN edge pour réduire le TTFB, et éviter les redirections en chaîne avant la page. Sur nos projets, la combinaison Image priority + PPR + CDN edge permet systématiquement d'atteindre un LCP sous 2 secondes pour les utilisateurs européens.
Trois outils complémentaires : le panneau Performance de Chrome DevTools montre les Long Tasks (blocs rouges > 50 ms sur la flamechart), la librairie web-vitals.js avec le mode attribution retourne l'élément interactif et la durée du délai pour chaque mesure INP en production, et Lighthouse CI dans GitHub Actions signale les violations du Total Blocking Time (TBT) — une bonne approximation du risque INP en conditions de lab. Commencez par DevTools pour le diagnostic, puis confirmez avec les données RUM.
Oui — c'est beaucoup plus simple à mettre en place dès le début que de l'intégrer sur une codebase existante avec des dettes de performance. Sur les projets Nehos, Lighthouse CI est configuré lors du sprint 0 avec des seuils initialement larges (LCP < 4 s) puis progressivement resserrés à mesure que l'équipe optimise. Sans CI automatisé, les régressions de performance passent invariablement inaperçues entre les sprints.
Oui, c'est une cause fréquente et difficile à détecter. Quand un Server Component rend un contenu qui diffère du rendu client initial (par exemple, un composant qui lit localStorage ou un cookie pendant l'hydratation), React corrige le DOM après hydratation — ce qui provoque un décalage visuel. La solution est de différer le rendu client avec `useEffect` et d'afficher un skeleton dans l'état initial, ou d'utiliser `suppressHydrationWarning` sur les éléments dont la valeur peut légitimement différer.
Google confirme officiellement que les CWV sont un facteur de ranking actif depuis 2021. En 2026, le consensus mesuré par les études SEO indique un impact plus marqué sur les SERP compétitives où plusieurs pages ont une qualité de contenu similaire. Le passage de la zone 'Bon' sur les trois métriques corrèle avec une amélioration moyenne de 2 à 5 positions sur les requêtes transactionnelles, selon les données CrUX et Search Console analysées sur nos projets clients. L'impact mobile est plus fort que desktop.
Réserver un audit