Pour améliorer les Core Web Vitals dans Next.js, livrez tôt le contenu important, limitez le JavaScript exécuté dans le navigateur et réservez l’espace des éléments qui arrivent ensuite. Next.js fournit des outils utiles, mais la stratégie de rendu et les frontières entre composants déterminent toujours le travail demandé à l’appareil du visiteur. Identifiez la métrique lente avant de modifier la configuration du framework.
Les exemples ciblent Next.js 16 avec App Router et ont été vérifiés dans la documentation affichant v16.4.2. Ils illustrent des méthodes d’implémentation, pas des résultats de benchmark. Les applications Pages Router et les applications React sans Next.js nécessitent d’autres points d’intégration, même si les problèmes du navigateur restent identiques.
Commencez par la route et la métrique
Choisissez une route réelle présentant un problème mesurable, plutôt que la page la plus facile à optimiser. Notez la catégorie d’appareil, le type de navigation, le déploiement et l’état de l’utilisateur. Un article public en cache, un tableau de bord authentifié et une fiche produit avec recommandations personnalisées n’ont pas le même chemin critique.
LCP décrit le moment où le plus grand contenu visible admissible est peint. Une réponse serveur tardive, une requête d’image retardée ou du travail sur le thread principal peuvent le repousser. INP concerne la réactivité pendant toute la visite. L’hydratation, le gestionnaire d’un filtre et le rendu après une mise à jour d’état peuvent contribuer à des moments différents. CLS décrit les déplacements inattendus, y compris après l’apparition du premier écran.
Utilisez les données terrain pour identifier les visiteurs concernés, puis un enregistrement de performances pour expliquer le délai. Un audit Lighthouse de navigation standard ne permet pas d’établir l’INP en production. Inversement, un clic rapide sur l’ordinateur du développeur ne démontre pas une bonne réactivité pour les utilisateurs réels. Le guide de diagnostic de l’INP distingue les délais d’entrée, de traitement et de présentation.
Conservez une référence avant modification : route, élément LCP observé, principales tâches longues, sources de déplacements et scripts chargés sur cette route. Vous transformez ainsi une optimisation générale en hypothèse vérifiable. Si l’image principale démarre tard, la première modification doit viser sa découverte ou sa priorité, pas un nettoyage de dépendances sans rapport.
Adaptez le rendu aux besoins réels du contenu
La génération statique, le rendu à la requête, le streaming et le rendu client décrivent différentes parties de la livraison. Les Server Components indiquent où leur code s’exécute. Un Server Component peut participer à une route statique ou à un traitement par requête ; son nom ne définit pas sa politique de cache.
Voici les compromis pratiques :
| Approche | Avantage possible | Risque à vérifier |
|---|---|---|
| HTML statique ou en cache | Contenu précoce sans répéter le travail à l’origine | Contenu périmé si l’invalidation est incorrecte |
| Rendu serveur à la requête | HTML récent ou personnalisé | Dépendances de données qui retardent la réponse |
| Streaming | Sections utiles disponibles avant les plus lentes | Contenu LCP toujours bloqué derrière une frontière |
| Contenu rendu côté client | Vues interactives alimentées à la demande | Cascades de scripts et de données avant le contenu utile |
Ne choisissez pas un calcul par requête simplement parce que l’application utilise React. Une documentation publique modifiée seulement lors de sa publication peut souvent être préparée en amont. La personnalisation peut exiger un traitement dynamique, mais la navigation et les explications autour n’ont pas à attendre toutes ses dépendances.
Le streaming est particulièrement facile à mal interpréter. Envoyer un squelette plus tôt peut améliorer la première impression sans avancer l’élément LCP final. Examinez l’arrivée du vrai contenu, pas seulement le premier octet du document. Donnez au contenu provisoire et à son remplacement des dimensions comparables pour éviter un saut pendant la livraison progressive.
Le cache dépend de la version de Next.js et des fonctionnalités activées. Examinez la route générée et le comportement déployé au lieu de reprendre les hypothèses d’une ancienne version. Une session de développement déjà chaude ne prouve pas le comportement d’une requête froide en production. Testez les chargements directs et les navigations client, qui peuvent demander des ressources différentes.
Gardez des frontières client suffisamment limitées
Dans App Router, pages et layouts sont des Server Components par défaut. Utilisez des Client Components pour l’état, les événements et les API du navigateur. Placez 'use client' à la plus petite frontière interactive utile, par exemple un filtre ou un contrôle dépliable, plutôt que de rendre tout un layout riche en contenu client pour un seul bouton.
Cette frontière affecte le graphe des modules importés. Une bibliothèque lourde importée par le module client peut entrer dans le bundle du navigateur même si presque toute la page paraît statique. Vérifiez les imports du composant interactif et transmettez uniquement les données nécessaires. Sérialiser un objet volumineux dans les propriétés entraîne aussi des coûts de transfert et de traitement, même si sa transformation a eu lieu sur le serveur.
Un Client Component n’implique pas un HTML produit exclusivement côté client. Next.js peut prérendre sa sortie initiale puis l’hydrater dans le navigateur. Cette distinction compte lorsqu’une page semble complète avant que ses contrôles répondent. Le HTML est arrivé, mais le JavaScript et la gestion des événements peuvent encore se préparer.
Analysez le bundle de production pour comprendre pourquoi une dépendance atteint une route. Consultez ensuite une trace pour savoir si son exécution pèse réellement. Taille compressée et temps CPU répondent à des questions différentes. Le guide sur l’exécution JavaScript permet de poursuivre lorsque de meilleurs téléchargements ne réduisent pas les tâches longues.
Donnez un plan de chargement à la véritable image LCP
Utilisez next/image pour fournir des dimensions et des variantes adaptatives, puis décrivez précisément la largeur affichée. Les valeurs intrinsèques width et height réservent le ratio ; CSS contrôle les dimensions rendues. Avec fill, le parent doit être positionné et disposer de dimensions ou d’un ratio définis. Une géométrie manquante peut transformer une amélioration d’image en problème de mise en page.
Cet exemple app/page.jsx suppose que public/images/storefront.webp existe et constitue l’image importante du premier écran :
// app/page.jsx
import Image from 'next/image';
export default function Page() {
return (
<main style={{ maxWidth: 960, margin: '0 auto' }}>
<h1>A guide to faster storefronts</h1>
<Image
src="/images/storefront.webp"
alt="A storefront with a wide display window"
width={1600}
height={900}
sizes="(max-width: 960px) 100vw, 960px"
loading="eager"
fetchPriority="high"
style={{ width: '100%', height: 'auto' }}
/>
</main>
);
}La valeur sizes correspond au conteneur de l’exemple, qui occupe la largeur disponible jusqu’à 960 pixels CSS. Modifiez-la quand le layout change. Une carte dans une grille multicolonne ne doit pas annoncer toute la largeur de la fenêtre par simple commodité. Vérifiez la variante retenue dans le panneau réseau à des largeurs et densités de pixels représentatives.
Dans Next.js 16, priority est déprécié au profit de preload. Cela ne signifie pas que chaque ancien priority doit devenir un préchargement systématique. La référence du composant Image privilégie généralement un chargement immédiat ou une priorité élevée et déconseille d’associer preload à loading ou fetchPriority.
L’exemple emploie donc un chargement immédiat et une indication de priorité, sans preload. Le préchargement reste une autre option lorsqu’une image critique connue doit être découverte plus tôt. Ne préchargez pas plusieurs images principales concurrentes pour mobile et ordinateur sans comprendre laquelle sera utilisée. Gardez le chargement différé pour les images secondaires hors écran, puis vérifiez à nouveau l’élément LCP réel.
Contrôlez également le service de livraison des images. Les sources distantes nécessitent une configuration adaptée de remotePatterns, et l’optimiseur par défaut exige un environnement compatible. Un export statique demande une stratégie explicite, comme un loader personnalisé ou des fichiers déjà optimisés. Le guide d’optimisation des images explique les choix de format, dimensions et qualité qui restent à effectuer.
Pensez les polices et la stabilité ensemble
next/font peut servir les fichiers de police depuis le déploiement de l’application. Avec next/font/google, le navigateur du visiteur n’a pas à demander la police à Google. Avec next/font/local, les fichiers viennent du projet. Dans les deux cas, il faut encore considérer les caractères couverts, les graisses, les métriques de remplacement et le volume transféré.
Ce layout racine suppose une police latine sous licence dans app/fonts/body-latin.woff2 et le composant de mesure présenté plus loin :
// app/layout.jsx
import localFont from 'next/font/local';
import { WebVitals } from './web-vitals';
const bodyFont = localFont({
src: './fonts/body-latin.woff2',
display: 'swap',
});
export default function RootLayout({ children }) {
return (
<html lang="en" className={bodyFont.className}>
<body>
<WebVitals />
{children}
</body>
</html>
);
}Il s’agit d’un layout anglais, pas d’une recette multilingue universelle. Les pages coréennes et hébraïques nécessitent les caractères, paramètres de langue et, si nécessaire, le sens d’écriture appropriés. Un fichier purement latin ne peut pas afficher toutes les langues. Évitez de télécharger plusieurs familles complètes sur chaque route si le visiteur n’utilise qu’une langue.
display: 'swap' affiche une police de remplacement pendant le chargement. Vérifiez si l’échange modifie les retours à la ligne, la largeur de navigation ou la hauteur des cartes. Les ajustements du framework aident, mais ne prouvent pas que toute la page a un CLS nul. Publicités, intégrations, bannières insérées et sections transmises ont toujours besoin d’espace réservé.
Comparez la page sur réseau lent avant et après l’arrivée de la police. Examinez le titre qui détermine LCP, pas seulement un paragraphe témoin. Si le texte se redistribue, ajustez la police de remplacement et le layout ou réduisez cette dépendance. Le guide d’optimisation des polices détaille cette démarche.
Planifiez les scripts sans confondre report et suppression
next/script contrôle le moment du chargement, mais ne rend pas l’exécution gratuite. Choisissez son périmètre aussi soigneusement que sa stratégie : un widget utile sur une seule route ne devrait pas automatiquement s’exécuter depuis le layout racine sur toutes les pages.
Les stratégies répondent à des besoins différents :
| Stratégie | Moment prévu | Point de vigilance |
|---|---|---|
beforeInteractive |
Scripts critiques précoces dans le layout racine | Réserver au code réellement nécessaire tôt dans toute l’application |
afterInteractive |
Après une partie de l’hydratation ; valeur par défaut | Peut concurrencer les premières interactions |
lazyOnload |
Pendant une période d’inactivité après le chargement | Une exécution tardive peut encore interrompre le visiteur |
worker |
Chargement expérimental dans un worker | Non pris en charge avec App Router dans la documentation consultée |
Décaler le chargement peut protéger le chemin critique initial tout en dégradant une interaction ultérieure. Reproduisez l’action qui coïncide avec l’initialisation du widget. Lorsque c’est pertinent, chargez une fonction facultative seulement à la demande du visiteur et affichez un retour visible pendant l’attente. Le consentement reste distinct : retarder un script analytique ne constitue pas une intégration conditionnée au consentement.
Examinez l’hydratation et le travail qui suit
L’hydratation ajoute le comportement React à une sortie prérendue. Gardez cohérentes la sortie serveur et la première sortie client. Un état propre au navigateur, des valeurs non déterministes ou des branches de contenu différentes peuvent provoquer des divergences. Masquer tous les avertissements ne répare pas le modèle de livraison.
Une hydratation importante peut retarder l’entrée, mais tous les problèmes d’INP ne viennent pas d’elle. Des mises à jour ultérieures peuvent recalculer de grands sous-arbres, déclencher un layout coûteux ou des calculs synchrones. Enregistrez l’action lente après stabilisation de la page comme pendant son chargement. Examinez l’intervalle entre l’entrée de l’utilisateur et la prochaine image affichée.
Les outils de planification de React peuvent prioriser certaines mises à jour, mais ne déplacent pas arbitrairement le JavaScript synchrone hors du thread principal. Supprimez d’abord les calculs inutiles, réduisez le sous-arbre mis à jour et évitez les recalculs de layout répétés. Pour un travail CPU important, envisagez une séparation adaptée avec un worker. Vérifiez toujours la justesse : un filtre rapide qui affiche brièvement une réponse fausse n’est pas une réussite.
Mesurez une version de production avec un callback stable
App Router utilise useReportWebVitals de next/web-vitals dans un petit Client Component. Une référence de callback stable évite les doublons provoqués par son remplacement lors d’un nouveau rendu. Cet exemple écrit les observations dans la console du navigateur et ne transmet aucune donnée utilisateur :
// app/web-vitals.jsx
'use client';
import { useReportWebVitals } from 'next/web-vitals';
function reportMetric({ id, name, value, rating }) {
console.info('web-vital', { id, name, value, rating });
}
export function WebVitals() {
useReportWebVitals(reportMetric);
return null;
}Le layout précédent monte le composant une fois. Observez une version de production, interagissez avec la page et changez sa visibilité pour examiner les métriques signalées plus tard dans le cycle de vie. N’attendez pas toutes les valeurs immédiatement après le chargement. Une visite sans interaction admissible peut n’avoir aucun INP ; ne remplacez pas les valeurs absentes par zéro.
Pour une collecte auprès des utilisateurs, remplacez la console par une chaîne de télémétrie conçue explicitement. Définissez échantillonnage, consentement applicable, minimisation et déduplication. Conservez l’identifiant et le nom de la métrique, puis traitez les mises à jour selon la version de la bibliothèque. LCP et INP sont des durées ; CLS est sans unité. Ne mélangez pas les unités et ne comptez pas plusieurs mises à jour comme plusieurs visites.
Les changements de route demandent une attention supplémentaire. La mesure des navigations douces dépend du navigateur et de la bibliothèque. Ne supposez pas qu’un changement d’URL client réinitialise toutes les Core Web Vitals standard. N’attribuez pas non plus une métrique tardive à l’URL simplement visible lors de sa réception. Documentez les règles de navigation de la version réellement livrée.
Livrez une modification avec un critère d’acceptation clair
Choisissez le goulot d’étranglement, appliquez la plus petite modification pertinente et répétez le même parcours dans des conditions comparables. Pour une image, vérifiez le début de requête, la variante et LCP. Pour une frontière client, vérifiez le bundle, la réactivité et la fonction. Pour le layout, examinez le chargement et les mises à jour ultérieures.
Utilisez le vérificateur de Core Web Vitals pour interpréter les valeurs mesurées, puis surveillez les routes et groupes d’appareils concernés en production. Gardez les guides d’optimisation par plateforme près de la liste de publication. L’étape utile suivante est une amélioration enregistrée, reliée à une cause, puis un contrôle des régressions sur le reste de l’expérience.