frontend / nextjs
Optimisation images et fonts
Explication
Ce que vous allez apprendre
- Utiliser
<Image>denext/imagepour un redimensionnement et un format automatiques - Comprendre le lazy loading par défaut et savoir quand le désactiver avec
priority - Autoriser une image distante via
remotePatternsdansnext.config.js - Charger des polices avec
next/fontsans requête réseau vers Google Fonts - Comprendre le lien entre chargement des images/fonts et le Cumulative Layout Shift (CLS)
Dans quel contexte ?
La page d'accueil d'un site vitrine affiche une grande bannière (hero image) tout en haut de page et une galerie de photos plus bas. Un audit Lighthouse récent a pointé un score de performance faible : la bannière met du temps à s'afficher (mauvais LCP) et le texte "saute" légèrement une fois la police personnalisée chargée (CLS élevé). Cette leçon montre comment next/image et next/font corrigent ces deux problèmes précis, avec très peu de configuration.
Le problème, d'abord
Sur le web, les images sont souvent la ressource la plus lourde d'une page. Elles peuvent, à elles seules, faire doubler le temps de chargement si elles sont mal gérées.
Les polices posent un problème différent, mais tout aussi visible. Une police mal chargée peut faire "sauter" le texte à l'écran pendant le chargement, un phénomène appelé CLS (Cumulative Layout Shift), qui pénalise l'expérience utilisateur ET le référencement Google.
Gérer tout ça manuellement demande énormément d'efforts. Bon format, bonne taille selon l'écran, chargement différé... c'est là que next/image intervient.
Voyons d'abord ce que fait le composant <Image>. Il redimensionne l'image pour l'écran du visiteur et choisit un format moderne plus léger (AVIF, WebP) quand le navigateur le supporte.
Il fait aussi autre chose d'important : il retarde le chargement des images hors écran. C'est le lazy loading, activé par défaut sur toutes les images.
Mais il y a une exception essentielle à connaître. L'image priority, typiquement celle visible immédiatement en haut de page (le LCP, "Largest Contentful Paint"), ne doit surtout PAS être en lazy loading, sinon on ralentit artificiellement l'affichage perçu du site.
Une contrainte de sécurité s'ajoute pour les images distantes. Pour une image hébergée sur un autre domaine (CDN, API externe), Next.js exige de déclarer explicitement ce domaine dans remotePatterns — ça empêche que le service d'optimisation soit détourné pour transformer des images venant de n'importe où.
Prérequis
Aucune connaissance avancée n'est requise, mais il est utile de savoir ce qu'est le LCP (Largest Contentful Paint) et le CLS (Cumulative Layout Shift), deux métriques de performance web mesurées par des outils comme Lighthouse.
Maintenant, passons aux polices avec next/font. Il télécharge et auto-héberge les polices Google Fonts DIRECTEMENT dans le build, au lieu de faire une requête vers les serveurs de Google à chaque visite.
Il réserve aussi l'espace nécessaire dès le départ. Résultat : pas de "flash" de texte avec la mauvaise police (FOUT), ni de décalage de mise en page une fois la bonne police chargée.
| Prop / option | Effet | À utiliser pour |
|---|---|---|
priority | Désactive le lazy loading | L'image visible immédiatement (LCP) |
placeholder="blur" | Affiche un aperçu flou pendant le chargement | Avatars, vignettes |
fill + sizes | L'image s'adapte à son conteneur | Grilles responsives |
next/font (display: "swap") | Réserve l'espace, évite le FOUT/CLS | Toute police personnalisée |
Le piège fréquent à éviter : mettre priority sur toutes les images "pour être sûr" annule l'intérêt du lazy loading. Seule l'image réellement visible sans scroll devrait l'avoir, sinon tout se télécharge en même temps et le gain de performance disparaît.
Piège fréquent
Ajouter priority à toutes les images d'une galerie "pour être sûr qu'elles s'affichent vite" force le navigateur à toutes les télécharger dès le chargement de la page, y compris celles hors écran — l'effet inverse de l'optimisation recherchée.
Commandes & code
Optimisation images et fonts
// next/image — redimensionnement, lazy loading et formats modernes automatiques
import Image from "next/image";
export default function Hero() {
return (
<Image
src="/hero.jpg"
alt="Bannière d'accueil"
width={1200}
height={600}
priority // désactive le lazy loading pour le LCP (above the fold)
quality={85}
/>
);
}// Image distante — l'hôte doit être autorisé dans next.config.js
import Image from "next/image";
export default function Avatar({ url }: { url: string }) {
return (
<Image
src={url}
alt="Avatar utilisateur"
width={48}
height={48}
className="rounded-full"
placeholder="blur"
blurDataURL="data:image/png;base64,iVBORw0KGgo..." // aperçu flou pendant le chargement
/>
);
}// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
remotePatterns: [
{ protocol: "https", hostname: "cdn.example.com" },
{ protocol: "https", hostname: "**.githubusercontent.com" },
],
formats: ["image/avif", "image/webp"],
deviceSizes: [640, 750, 828, 1080, 1200, 1920],
},
};
module.exports = nextConfig;// fill : l'image occupe son conteneur (utile dans une grid responsive)
import Image from "next/image";
export default function ProductCard({ product }: { product: { image: string; name: string } }) {
return (
<div style={{ position: "relative", aspectRatio: "4 / 3" }}>
<Image
src={product.image}
alt={product.name}
fill
style={{ objectFit: "cover" }}
sizes="(max-width: 768px) 100vw, 33vw" // aide le navigateur à choisir la bonne résolution
/>
</div>
);
}// next/font — auto-héberge les fonts Google, zéro layout shift (pas de FOUT/CLS)
// app/layout.tsx
import { Inter, JetBrains_Mono } from "next/font/google";
const inter = Inter({
subsets: ["latin"],
variable: "--font-inter",
display: "swap",
});
const mono = JetBrains_Mono({
subsets: ["latin"],
variable: "--font-mono",
});
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="fr" className={`${inter.variable} ${mono.variable}`}>
<body>{children}</body>
</html>
);
}// Font locale (self-hosted, ex. font custom de marque)
import localFont from "next/font/local";
const brandFont = localFont({
src: [
{ path: "./fonts/Brand-Regular.woff2", weight: "400", style: "normal" },
{ path: "./fonts/Brand-Bold.woff2", weight: "700", style: "normal" },
],
variable: "--font-brand",
});Résumé
next/imagegère automatiquement le redimensionnement, le lazy loading et le format (AVIF/WebP).prioritysur l'image LCP (au-dessus de la ligne de flottaison) évite un lazy loading pénalisant.remotePatternsdoit lister explicitement les domaines d'images distantes.next/fontélimine les décalages de mise en page liés au chargement des polices (CLS).
Exercices pratiques
Mission : le score Lighthouse en chute libre
Objectif : Corriger une bannière mal optimisée et une galerie qui télécharge tout d'un coup, puis autoriser une image distante bloquée.
Contexte
Un audit Lighthouse pointe deux problèmes sur la page d'accueil : la bannière hero.jpg en haut de page (visible immédiatement, sans scroll) n'a pas la prop priority et met du temps à s'afficher (mauvais LCP). Plus bas, une galerie de 40 photos a été codée par un collègue qui a ajouté priority à CHAQUE <Image> "pour être sûr qu'elles s'affichent vite", ce qui a en réalité aggravé le score. Une image d'avatar hébergée sur cdn.example.com provoque en plus une erreur au build car ce domaine n'est pas autorisé.