Changer le thème

Core Web Vitals Next.js en pratique : guide complet LCP/FCP/CLS

Easton editorial illustration: state-management shelf

Le mois dernier, j’ai repris l’optimisation performance d’un projet e-commerce : le score Lighthouse stagnait autour de 65, le LCP (Largest Contentful Paint) oscillait autour de 3,5 secondes. Le taux de churn était élevé et la conversion ne décollait pas.

Deux semaines d’optimisation systématique des Core Web Vitals plus tard : Lighthouse à 95, LCP à 1,2 s. Et surtout, +28 % de conversion. L’optimisation performance n’est pas qu’une question de métriques — c’est de la valeur business concrète.

Selon les données 2025, seulement 47 % des sites atteignent le standard Core Web Vitals de Google. Une mauvaise performance peut coûter 8 à 35 % de revenus, de classement et de conversions. Ce n’est pas de l’alarmisme — c’est le prix réel en business.

Dans cet article, je partage mon expérience terrain sur l’optimisation Next.js. Vous y trouverez :

  • Les méthodes concrètes pour optimiser les 3 métriques clés (LCP, INP, CLS)
  • Plus de 10 exemples de code prêts à copier
  • 5 pièges de performance les plus fréquents
95
Score Lighthouse optimisé
De 65 à 95
1,2 s
LCP optimisé
De 3,5 s
28 %
Hausse de conversion
Valeur business de l’optimisation
47 %
Sites conformes
Données Core Web Vitals 2025
Source: Données terrain

Core Web Vitals : ce qui change en 2025

Avant d’optimiser, clarifions les règles du jeu. Beaucoup d’articles parlent encore du FID (First Input Delay), alors que cette métrique a été retirée en mars 2024.

Les trois métriques clés actuelles

Google se concentre désormais sur ces trois indicateurs :

  1. LCP (Largest Contentful Paint) — plus grand élément de contenu visible

    • Objectif : ≤ 2,5 s
    • Mesure la performance de chargement
    • Pèse 25 % dans le score Lighthouse Performance
  2. INP (Interaction to Next Paint) — interaction jusqu’au prochain rendu

    • Objectif : ≤ 200 ms
    • Mesure la réactivité aux interactions
    • Nouvelle métrique depuis mars 2024, remplace le FID
  3. CLS (Cumulative Layout Shift) — décalage cumulé de mise en page

    • Objectif : < 0,1
    • Mesure la stabilité visuelle

Le FCP (First Contentful Paint) n’est plus une métrique Core Web Vitals, mais reste important pour la première impression utilisateur.

Pourquoi s’y intéresser ?

Vous connaissez sans doute ce scénario : vous ouvrez un site, vous visez un bouton, la page saute et vous cliquez ailleurs. C’est un mauvais CLS.

Ou vous attendez plusieurs secondes devant un écran blanc en vous demandant si la connexion est coupée. C’est un LCP trop lent.

D’après l’équipe Chrome, le LCP moyen des meilleurs sites tourne autour de 1 220 ms. Si votre LCP dépasse 2,5 s, vous êtes derrière la plupart de vos concurrents.

Optimisation LCP — afficher rapidement le plus grand élément

Le LCP a été la métrique sur laquelle j’ai le plus travaillé — et celle où les gains sont les plus visibles. Voici comment procéder.

Étape 1 : identifier votre élément LCP

Avant d’optimiser, il faut savoir quel élément est le LCP. En général :

  • L’image hero above-the-fold
  • La vignette d’une vidéo
  • Un bloc de titre ou de texte volumineux

Méthode rapide :

  1. Ouvrir Chrome DevTools (F12)
  2. Ctrl+Shift+P (Mac : Cmd+Shift+P)
  3. Taper « Show Rendering »
  4. Cocher « Core Web Vitals »
  5. Rafraîchir — l’élément LCP s’affiche en haut à droite

Sur le projet e-commerce, l’élément LCP était l’image principale de la page d’accueil : un JPG de 2 Mo. Le problème était là.

Optimisation images : bien utiliser next/image

Beaucoup pensent qu’utiliser le composant Image de Next.js suffit. Ce n’est pas le cas — j’ai fait cette erreur au début, et le LCP restait lent.

La priorité est la clé

Pour l’image LCP, il faut dire explicitement au navigateur : « Cette image est importante, chargez-la en priorité ! » Next.js propose deux approches :

Next.js 13-15 (versions courantes) :

import Image from 'next/image';

export default function Hero() {
  return (
    <Image
      src="/hero-image.jpg"
      width={1200}
      height={630}
      priority  // Clé ! Priorité de chargement
      fetchPriority="high"  // Double sécurité
      alt="Visuel produit principal"
    />
  );
}

Next.js 16+ (dernière version) :

Next.js 16 a déprécié priority au profit de :

<Image
  src="/hero-image.jpg"
  width={1200}
  height={630}
  loading="eager"  // Chargement immédiat, pas de lazy load
  fetchPriority="high"  // Haute priorité
  alt="Visuel produit principal"
/>

Pourquoi deux attributs ? priority ajoute automatiquement une balise preload ; fetchPriority indique l’importance de la ressource. Ensemble, ils fonctionnent mieux.

À comparer avec les mauvaises pratiques :

// ❌ Erreur : lazy load par défaut, LCP lent
<Image
  src="/hero-image.jpg"
  width={1200}
  height={630}
  alt="Visuel produit principal"
/>

// ❌ Erreur : dimensions manquantes
<Image
  src="/hero-image.jpg"
  priority
  alt="Visuel produit principal"
/>

L’importance des dimensions

Le composant Image exige width et height — ce n’est pas une contrainte arbitraire, mais une protection contre le CLS.

Pour un layout responsive :

// Utiliser fill pour le responsive
<div style={{ position: 'relative', width: '100%', aspectRatio: '16/9' }}>
  <Image
    src="/hero-image.jpg"
    fill
    priority
    style={{ objectFit: 'cover' }}
    alt="Visuel produit principal"
  />
</div>

Le conteneur parent doit avoir une taille explicite ou un aspect-ratio, sinon le navigateur ne sait pas quelle surface allouer.

Optimisation polices : next/font en inline automatique

Un chargement de polices lent pénalise aussi le LCP — surtout les polices CJK qui pèsent plusieurs Mo. Next.js 13 a introduit next/font pour automatiser l’optimisation.

Google Fonts :

// app/layout.jsx
import { Inter, Noto_Sans_SC } from 'next/font/google';

const inter = Inter({
  subsets: ['latin'],
  display: 'swap',  // Évite le flash de texte
});

const notoSansSC = Noto_Sans_SC({
  subsets: ['chinese-simplified'],
  weight: ['400', '700'],
  display: 'swap',
});

export default function RootLayout({ children }) {
  return (
    <html lang="zh-CN" className={`${inter.className} ${notoSansSC.className}`}>
      <body>{children}</body>
    </html>
  );
}

Polices personnalisées :

import localFont from 'next/font/local';

const myFont = localFont({
  src: './my-font.woff2',
  display: 'swap',
});

export default function Layout({ children }) {
  return <div className={myFont.className}>{children}</div>;
}

next/font optimise automatiquement :

  • Inline du CSS de polices, moins de requêtes réseau
  • Sous-ensemble de caractères, charge uniquement le nécessaire
  • Preload des fichiers de polices
  • Suppression des décalages de mise en page

Après migration vers next/font, le LCP a encore baissé de 0,3 s.

Réduire le temps de réponse serveur

Images et polices optimisées, mais LCP encore lent ? Le TTFB (Time To First Byte) est peut-être en cause.

Privilégier la génération statique

Next.js propose plusieurs modes de rendu, du plus rapide au plus lent :

  1. SSG (Static Site Generation) — HTML généré au build, le plus rapide
  2. ISR (Incremental Static Regeneration) — régénération statique à la demande
  3. SSR (Server-Side Rendering) — HTML généré à chaque requête, plus lent
  4. CSR (Client-Side Rendering) — rendu côté client, LCP le plus lent

Utilisez SSG dès que possible :

// app/blog/[slug]/page.jsx
export async function generateStaticParams() {
  const posts = await getPosts();
  return posts.map((post) => ({
    slug: post.slug,
  }));
}

export default async function BlogPost({ params }) {
  const post = await getPost(params.slug);
  return <article>{/* ... */}</article>;
}

Si les données doivent rester à jour, utilisez ISR :

// app/products/[id]/page.jsx
export const revalidate = 3600; // Régénération toutes les heures

export default async function ProductPage({ params }) {
  const product = await getProduct(params.id);
  return <div>{/* ... */}</div>;
}

CDN et Edge Network

Sur Vercel, les ressources statiques sont distribuées sur le Edge Network mondial, ce qui réduit fortement le TTFB.

Sur d’autres plateformes, combinez avec Cloudflare, AWS CloudFront ou un CDN équivalent.

Éviter les calculs lourds côté serveur

J’ai aussi tombé dans ce piège : un traitement de données complexe côté serveur prenait 500 ms à chaque rendu, plombant le LCP.

Contre-exemple :

// ❌ Erreur : calcul à chaque requête
export default async function Page() {
  const data = await fetchData();
  const processed = heavyProcessing(data); // 500 ms
  return <div>{processed}</div>;
}

Bonne approche :

Mettre en cache le résultat ou calculer au build :

// ✅ Correct : calcul au build
export async function generateStaticParams() {
  const data = await fetchData();
  const processed = heavyProcessing(data);
  await saveProcessedData(processed);
}

export default async function Page() {
  const processed = await getProcessedData(); // Lecture directe
  return <div>{processed}</div>;
}

Optimisation CLS — éliminer les sauts de mise en page

Le CLS (Cumulative Layout Shift) est la métrique la plus frustrante. La page saute soudainement — mauvaise expérience. Ce problème m’a occupé un bon moment.

Réserver les dimensions images et médias

La cause la plus fréquente : une image qui charge et modifie la mise en page. Solution : indiquer les dimensions à l’avance.

Le composant Image Next.js gère cela :

// ✅ Correct : dimensions explicites, pas de CLS
<Image
  src="/product.jpg"
  width={400}
  height={300}
  alt="Photo produit"
/>

Et pour les images dynamiques ?

Depuis un CMS, sans connaître les dimensions :

// ✅ Correct : aspect-ratio pour réserver l'espace
<div style={{ position: 'relative', width: '100%', aspectRatio: '4/3' }}>
  <Image
    src={dynamicImageUrl}
    fill
    style={{ objectFit: 'cover' }}
    alt="Image dynamique"
  />
</div>

Même logique pour la vidéo :

<video
  width="1280"
  height="720"
  poster="/video-poster.jpg"
  controls
>
  <source src="/video.mp4" type="video/mp4" />
</video>

Espace réservé pour le contenu dynamique

Publicités, bandeaux de notification, embeds (cartes Twitter…) provoquent du CLS. Réservez l’espace à l’avance.

Conteneur publicitaire :

// ✅ Correct : hauteur réservée
<div
  style={{
    minHeight: '250px',  // Hauteur standard Google AdSense
    backgroundColor: '#f0f0f0'  // Couleur de placeholder
  }}
>
  {/* Script publicitaire */}
  <ins className="adsbygoogle" />
</div>

Bandeau de notification :

Ne laissez pas un bandeau apparaître brusquement :

// ❌ Erreur : apparition soudaine, page qui descend
{showBanner && <NotificationBanner />}

// ✅ Correct : espace réservé
<div style={{ minHeight: '60px' }}>
  {showBanner ? <NotificationBanner /> : <div style={{ height: '60px' }} />}
</div>

Skeleton screens :

Pour le contenu chargé dynamiquement, utilisez des skeletons :

function ProductList() {
  const { data, isLoading } = useQuery('products', fetchProducts);

  if (isLoading) {
    return (
      <div className="grid grid-cols-3 gap-4">
        {Array.from({ length: 6 }).map((_, i) => (
          <div key={i} className="skeleton" style={{ height: '300px' }} />
        ))}
      </div>
    );
  }

  return (
    <div className="grid grid-cols-3 gap-4">
      {data.map(product => <ProductCard key={product.id} {...product} />)}
    </div>
  );
}

Optimisation du chargement des polices

Le chargement de polices provoque aussi du CLS — flash ou changement de police visible.

next/font gère cela automatiquement, mais vous pouvez affiner :

const inter = Inter({
  subsets: ['latin'],
  display: 'optional',  // Si la police n'est pas prête, utiliser la police système
  adjustFontFallback: true,  // Ajuster la taille du fallback pour limiter le saut
});

Options pour display :

  • swap : fallback d’abord, puis bascule (peut causer du CLS)
  • optional : si la police n’est pas prête à temps, garder le fallback (recommandé)
  • block : texte masqué brièvement en attendant la police (déconseillé)
  • fallback : compromis entre swap et optional

Je recommande optional : la police custom peut ne pas s’afficher, mais l’expérience reste stable.

Éviter le responsive piloté par JavaScript

Piège CLS le plus courant en 2025 ! Beaucoup de projets Next.js commettent cette erreur.

Code typiquement problématique :

// ❌ Erreur : CLS important
import { useMediaQuery } from '@/hooks/useMediaQuery';

export default function ResponsiveLayout() {
  const isMobile = useMediaQuery('(max-width: 768px)');

  return (
    <div>
      {isMobile ? (
        <MobileNav />
      ) : (
        <DesktopNav />
      )}
    </div>
  );
}

Pourquoi du CLS ?

  1. Au premier rendu, le JavaScript n’a pas encore tourné — isMobile vaut undefined ou une valeur par défaut
  2. Après exécution, useMediaQuery retourne la vraie valeur
  3. Re-render, mise en page qui change brusquement

En SSR, c’est pire : le serveur ne connaît pas la largeur d’écran du client.

Bonne approche : media queries CSS

// ✅ Correct : affichage/masquage en CSS, pas de CLS
export default function ResponsiveLayout() {
  return (
    <>
      <nav className="mobile-nav md:hidden">
        <MobileNav />
      </nav>
      <nav className="desktop-nav hidden md:block">
        <DesktopNav />
      </nav>
    </>
  );
}

Ou avec CSS-in-JS :

export default function ResponsiveLayout() {
  return (
    <div className="responsive-container">
      <MobileNav />
      <DesktopNav />
      <style jsx>{`
        .responsive-container > :global(.mobile-nav) {
          display: block;
        }
        .responsive-container > :global(.desktop-nav) {
          display: none;
        }
        @media (min-width: 768px) {
          .responsive-container > :global(.mobile-nav) {
            display: none;
          }
          .responsive-container > :global(.desktop-nav) {
            display: block;
          }
        }
      `}</style>
    </div>
  );
}

Si vous devez vraiment distinguer côté JS (requêtes différentes selon l’appareil), récupérez le User-Agent côté serveur :

// app/page.jsx
import { headers } from 'next/headers';

export default async function Page() {
  const headersList = headers();
  const userAgent = headersList.get('user-agent') || '';
  const isMobile = /mobile/i.test(userAgent);

  return (
    <div>
      {isMobile ? <MobileView /> : <DesktopView />}
    </div>
  );
}

Serveur et client rendent la même chose — pas de CLS.

Optimisation FCP — accélérer le premier contenu visible

Le FCP n’est pas une métrique Core Web Vitals, mais il façonne la première impression. Un écran blanc trop long, et l’utilisateur ferme l’onglet.

Optimiser les ressources critiques

Un FCP lent vient souvent de ressources bloquant le rendu. Ouvrez le panneau Coverage de Chrome DevTools pour repérer CSS et JavaScript inutilisés.

Inline du CSS critique :

Next.js optimise le CSS automatiquement, mais vous pouvez inline les styles essentiels :

// app/layout.jsx
export default function RootLayout({ children }) {
  return (
    <html>
      <head>
        <style
          dangerouslySetInnerHTML={{
            __html: `
              /* CSS critique : styles indispensables above-the-fold */
              body { margin: 0; font-family: system-ui; }
              .hero { height: 100vh; }
            `,
          }}
        />
      </head>
      <body>{children}</body>
    </html>
  );
}

Différer le CSS non critique :

Pour les styles hors first screen (modales, sections repliables) :

// Import dynamique du CSS dans le composant
import dynamic from 'next/dynamic';

const Modal = dynamic(() => import('./Modal'), {
  loading: () => <p>Loading...</p>,
});

Gestion des scripts tiers

Google Analytics, publicités, plugins sociaux — autant de tueurs de performance. Le FCP de nombreux sites en souffre.

Utiliser next/script :

Next.js fournit le composant Script pour contrôler le moment de chargement :

import Script from 'next/script';

export default function Layout({ children }) {
  return (
    <>
      {children}

      {/* Google Analytics : après interactivité */}
      <Script
        src="https://www.googletagmanager.com/gtag/js?id=GA_MEASUREMENT_ID"
        strategy="lazyOnload"
      />
      <Script id="ga-init" strategy="lazyOnload">
        {`
          window.dataLayer = window.dataLayer || [];
          function gtag(){dataLayer.push(arguments);}
          gtag('js', new Date());
          gtag('config', 'GA_MEASUREMENT_ID');
        `}
      </Script>

      {/* Facebook Pixel : chargement différé */}
      <Script
        src="https://connect.facebook.net/en_US/fbevents.js"
        strategy="lazyOnload"
      />
    </>
  );
}

Valeurs de strategy :

  • beforeInteractive : avant interactivité (scripts critiques)
  • afterInteractive : après interactivité (défaut)
  • lazyOnload : en idle (recommandé pour analytics et pubs)
  • worker : dans un Web Worker (expérimental)

En passant tous les scripts non essentiels en lazyOnload, le FCP a gagné 0,8 s.

Code splitting et lazy loading

Next.js fait du code splitting automatiquement, mais un affinage manuel reste utile.

Composants en lazy load :

Pour ce qui n’est pas visible au first paint :

import dynamic from 'next/dynamic';

// Lazy load du composant commentaires
const Comments = dynamic(() => import('./Comments'), {
  loading: () => <div>Chargement des commentaires...</div>,
  ssr: false,  // Pas de rendu serveur
});

export default function BlogPost({ post }) {
  return (
    <article>
      <h1>{post.title}</h1>
      <div>{post.content}</div>
      {/* Chargé quand l'utilisateur scroll jusqu'ici */}
      <Comments postId={post.id} />
    </article>
  );
}

Lazy load des bibliothèques tierces :

Certaines librairies sont lourdes (graphiques, éditeurs riches) — chargez-les à la demande.

Cas : Chart.js — 800 Ko économisés

Un projet importait Chart.js sur chaque page alors que seul le dashboard l’utilisait. 800 Ko de JS inutile partout.

Avant optimisation :

// ❌ Erreur : import global, toutes les pages chargent
import { Chart } from 'chart.js';

export default function Dashboard() {
  return <canvas ref={chartRef} />;
}

Après optimisation :

// ✅ Correct : chargement à la demande
import dynamic from 'next/dynamic';

const ChartComponent = dynamic(() => import('./ChartComponent'), {
  loading: () => <div>Chargement du graphique...</div>,
  ssr: false,
});

export default function Dashboard() {
  return <ChartComponent />;
}

// ChartComponent.jsx
import { Chart } from 'chart.js';

export default function ChartComponent() {
  return <canvas ref={chartRef} />;
}

Seuls les visiteurs du dashboard téléchargent Chart.js.

Lazy load des images :

Hors image LCP, les autres images doivent être en lazy load :

<Image
  src="/feature-image.jpg"
  width={600}
  height={400}
  loading="lazy"  // Valeur par défaut, peut être omis
  alt="Présentation des fonctionnalités"
/>

Surveillance et optimisation continue

L’optimisation n’est pas un one-shot. La performance se maintient dans la durée.

Lighthouse CI en automatique

Lancer Lighthouse à la main prend du temps et on oublie. Mieux vaut l’intégrer au CI/CD.

Installation :

npm install -D @lhci/cli

Configuration lighthouserc.js :

module.exports = {
  ci: {
    collect: {
      url: ['http://localhost:3000/', 'http://localhost:3000/products'],
      numberOfRuns: 3,  // 3 exécutions, moyenne
    },
    assert: {
      assertions: {
        'categories:performance': ['error', { minScore: 0.9 }],  // Performance ≥ 90
        'first-contentful-paint': ['error', { maxNumericValue: 2000 }],  // FCP ≤ 2s
        'largest-contentful-paint': ['error', { maxNumericValue: 2500 }],  // LCP ≤ 2,5s
        'cumulative-layout-shift': ['error', { maxNumericValue: 0.1 }],  // CLS < 0,1
      },
    },
    upload: {
      target: 'temporary-public-storage',
    },
  },
};

GitHub Actions :

# .github/workflows/lighthouse.yml
name: Lighthouse CI
on: [push]
jobs:
  lighthouse:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
      - run: npm install
      - run: npm run build
      - run: npm run start &
      - run: npx @lhci/cli autorun

Chaque push déclenche Lighthouse. Une régression de performance fait échouer le CI.

Real User Monitoring (RUM)

Lighthouse mesure en lab — l’expérience réelle peut différer. Collectez les Core Web Vitals terrain.

Vercel Analytics :

Sur Vercel, activation en un clic :

// app/layout.jsx
import { Analytics } from '@vercel/analytics/react';

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        {children}
        <Analytics />
      </body>
    </html>
  );
}

Bibliothèque web-vitals :

Sans Vercel, utilisez la lib Google web-vitals :

npm install web-vitals
// app/layout.jsx
'use client';

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

function sendToAnalytics(metric) {
  fetch('/api/analytics', {
    method: 'POST',
    body: JSON.stringify(metric),
  });
}

export default function Analytics() {
  useEffect(() => {
    onLCP(sendToAnalytics);
    onINP(sendToAnalytics);
    onCLS(sendToAnalytics);
  }, []);

  return null;
}

Google Search Console :

Le rapport « Signaux Web essentiels » affiche les performances terrain et signale les pages à améliorer.

Pièges courants et solutions

Enfin, les 5 écueils les plus fréquents :

Piège 1 : trop de JavaScript côté client

Beaucoup de devs font tout en React — la page dépend entièrement du JS. Si le chargement échoue ou traîne, écran blanc.

Solutions :

  • Privilégier les Server Components (défaut Next.js 13+)
  • 'use client' uniquement si nécessaire
  • Progressive Enhancement : fonctionnalité de base d’abord, enrichissement ensuite

Piège 2 : ignorer la performance mobile

Rapide sur desktop ne veut pas dire rapide sur mobile. CPU faible, réseau lent — les problèmes s’amplifient.

Solutions :

  • Tester en mode « Mobile » dans Lighthouse
  • Throttling réseau (Chrome DevTools → Network)
  • Throttling CPU (Chrome DevTools → Performance → CPU)

Piège 3 : scripts tiers non optimisés

Google Analytics, Facebook Pixel, Intercom, Hotjar… chaque script alourdit la page.

Solutions :

  • Encapsuler avec next/script, strategy="lazyOnload"
  • Audit régulier : quels scripts sont vraiment nécessaires ?
  • Tracking serveur plutôt que client quand c’est possible

Piège 4 : mauvais choix de format d’image

Toujours en PNG et JPG ? WebP et AVIF économisent 30 à 50 % de volume.

Solutions :

Le composant Image Next.js convertit automatiquement — assurez-vous que le serveur le supporte :

// next.config.js
module.exports = {
  images: {
    formats: ['image/avif', 'image/webp'],  // Formats modernes en priorité
  },
};

Piège 5 : négliger la performance serveur

Un frontend parfait ne compense pas un serveur lent.

Solutions :

  • Optimiser les requêtes BDD (index, éviter N+1)
  • Cache (Redis, CDN)
  • Monitoring serveur (APM : New Relic, Datadog)

Résumé

Récapitulons :

Points clés LCP :

  • priority et fetchPriority="high" sur l’image LCP
  • next/font pour les polices
  • SSG et ISR en priorité, moins de calcul serveur
  • CDN pour réduire le TTFB

Points clés CLS :

  • Dimensions explicites pour images et médias
  • Espace réservé pour contenu dynamique (pubs, bandeaux)
  • Pas de layout responsive via hooks JavaScript
  • next/font pour limiter les sauts de police

Points clés FCP :

  • Différer les ressources non critiques
  • Scripts tiers via next/script avec lazyOnload
  • Lazy load des grosses librairies et composants hors first screen

Optimisation continue :

  • Lighthouse CI automatisé
  • Données utilisateurs réelles (RUM)
  • Audit et nettoyage réguliers

L’optimisation performance suit un cycle « mesurer → optimiser → remesurer ». Pas de fix unique — attention et amélioration continues.

Quand le score Lighthouse a enfin dépassé 90, la satisfaction était réelle. Et la hausse de conversion a prouvé que l’effort en valait la peine.

Plan d’action

À vous de jouer. Je recommande cet ordre :

  1. Immédiat :

    • Tester votre projet Next.js avec Lighthouse
    • Identifier l’élément LCP — priority est-il défini ?
    • Chercher du code useMediaQuery ou similaire qui provoque du CLS
  2. Cette semaine :

    • Optimiser l’image LCP (priority + fetchPriority)
    • Migrer les polices vers next/font
    • Ajouter des placeholders pour le contenu dynamique
  3. Semaine prochaine :

    • Scripts tiers en lazyOnload
    • Lazy load des grosses librairies et composants hors first screen
    • Configurer Lighthouse CI
  4. En continu :

    • Vérifier le score Lighthouse chaque mois
    • Suivre le rapport Core Web Vitals dans Search Console
    • Corriger rapidement les régressions

L’optimisation performance n’est pas un projet ponctuel — c’est un processus continu. Bonne optimisation !

Processus complet d'optimisation Core Web Vitals Next.js

Optimiser LCP, INP et CLS pour atteindre 90+ au Lighthouse

⏱️ Estimated time: 8 hr

  1. 1

    Step 1: Optimiser le LCP (Largest Contentful Paint)

    Objectif : ≤ 2,5 s

    Méthodes :
    • Utiliser next/image (conversion WebP/AVIF automatique)
    • Ajouter priority aux images clés above-the-fold
    • Précharger les ressources clés (<link rel="preload">)
    • Optimiser le temps de réponse serveur (CDN, Edge Functions)
    • Réduire les ressources bloquant le rendu (CSS/JS non critiques en différé)
    • React Server Components pour moins de JS côté client

    Outils :
    • Rapport Performance Lighthouse
    • Panneau Performance Chrome DevTools
    • WebPageTest
  2. 2

    Step 2: Optimiser l'INP (Interaction to Next Paint)

    Objectif : ≤ 200 ms

    Méthodes :
    • Réduire l'exécution JavaScript (code splitting, lazy loading)
    • React Server Components pour moins de JS côté client
    • Optimiser la gestion des événements (debounce, throttle, délégation)
    • Éviter les longues tâches sur le thread principal (Web Workers)
    • Optimiser les scripts tiers (chargement différé, async/defer)
    • Suspense et rendu en streaming

    Outils :
    • Panneau Performance Chrome DevTools
    • Extension Chrome Web Vitals
    • Outils Real User Monitoring (RUM)
  3. 3

    Step 3: Optimiser le CLS (Cumulative Layout Shift)

    Objectif : ≤ 0,1

    Méthodes :
    • Définir largeur/hauteur images et vidéos (ou aspect-ratio)
    • Éviter l'insertion dynamique (réserver l'espace)
    • font-display: swap pour limiter les décalages liés aux polices
    • Espace réservé pour les publicités
    • CSS Grid/Flexbox plutôt que positionnement absolu
    • Éviter d'insérer du contenu au-dessus du contenu existant

    Outils :
    • Rapport CLS Lighthouse
    • Enregistrements Layout Shift dans Chrome DevTools
    • Visualisation CLS WebPageTest
  4. 4

    Step 4: Exploiter les fonctionnalités de performance Next.js

    Astuces Next.js :
    • Composant Image pour l'optimisation automatique
    • Font Optimization pour les polices
    • React Server Components pour moins de JS client
    • Streaming SSR pour accélérer le first paint
    • Dynamic Imports pour le code splitting
    • next/dynamic pour le chargement différé

    Vérifications :
    • Configuration performance dans next.config.js
    • Taille du bundle (npm run build)
  5. 5

    Step 5: Surveillance et optimisation continue

    Outils de monitoring :
    • Rapport Core Web Vitals Google Search Console
    • Vercel Analytics (si déployé sur Vercel)
    • Extension Chrome Web Vitals
    • Outils Real User Monitoring (RUM)

    Optimisation continue :
    • Vérifier le score Lighthouse chaque mois
    • Suivre le rapport Core Web Vitals Search Console
    • Corriger rapidement les régressions
    • A/B tests pour mesurer l'impact

FAQ

Quelles sont les trois métriques Core Web Vitals ?
LCP (Largest Contentful Paint) ≤ 2,5 s mesure la performance de chargement ; INP (Interaction to Next Paint) ≤ 200 ms mesure la réactivité aux interactions (remplace FID depuis mars 2024) ; CLS (Cumulative Layout Shift) ≤ 0,1 mesure la stabilité visuelle. Ces trois métriques influencent directement le classement Google.
Comment optimiser le LCP ?
Méthodes : 1) next/image ; 2) priority sur les images clés ; 3) préchargement des ressources ; 4) temps de réponse serveur ; 5) réduire les ressources bloquant le rendu ; 6) React Server Components. Objectif : LCP ≤ 2,5 s.
Quelle différence entre INP et FID ?
L'INP a remplacé le FID en mars 2024. Le FID ne mesure que la première interaction ; l'INP mesure la réactivité de toutes les interactions. L'INP est plus complet et reflète mieux l'expérience réelle. Objectif : ≤ 200 ms.
Comment éviter le CLS (décalage de mise en page) ?
Méthodes : 1) dimensions images/vidéos ; 2) éviter l'insertion dynamique ; 3) font-display: swap ; 4) espace réservé pour les publicités ; 5) CSS Grid/Flexbox ; 6) ne pas insérer de contenu au-dessus du contenu existant. Objectif : CLS ≤ 0,1.
Combien de temps avant de voir les effets de l'optimisation ?
Les métriques techniques (score Lighthouse) sont visibles immédiatement. Les métriques business (conversion, classement) prennent généralement 1 à 3 mois. Google a besoin de temps pour réévaluer le site, et les données comportementales doivent s'accumuler. Surveillez et optimisez en continu.
Quelles fonctionnalités Next.js aident à la performance ?
React Server Components (moins de JS client), composant Image, Font Optimization, Streaming SSR, Dynamic Imports, code splitting et tree-shaking automatiques. Exploitez-les pleinement pour des gains significatifs.
Comment surveiller les Core Web Vitals ?
Outils : 1) rapport Core Web Vitals Google Search Console (données utilisateurs réelles) ; 2) Lighthouse (données lab) ; 3) extension Chrome Web Vitals ; 4) Vercel Analytics ; 5) outils RUM. Combinez données lab et données terrain.

15 min de lecture · Publié le: 19 déc. 2025 · Mis à jour le: 27 juil. 2026

Commentaires

Connectez-vous avec GitHub pour laisser un commentaire

Easton BlogEaston Blog