Retour au cours

frontend / nextjs

Optimisation images et fonts

Leçon 121 exercice

Explication

Ce que vous allez apprendre

  • Utiliser <Image> de next/image pour 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 remotePatterns dans next.config.js
  • Charger des polices avec next/font sans 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 / optionEffetÀ utiliser pour
priorityDésactive le lazy loadingL'image visible immédiatement (LCP)
placeholder="blur"Affiche un aperçu flou pendant le chargementAvatars, vignettes
fill + sizesL'image s'adapte à son conteneurGrilles responsives
next/font (display: "swap")Réserve l'espace, évite le FOUT/CLSToute 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

tsx
// 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}
    />
  );
}
tsx
// 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
    />
  );
}
js
// 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;
tsx
// 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>
  );
}
tsx
// 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>
  );
}
tsx
// 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/image gère automatiquement le redimensionnement, le lazy loading et le format (AVIF/WebP).
  • priority sur l'image LCP (au-dessus de la ligne de flottaison) évite un lazy loading pénalisant.
  • remotePatterns doit 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

1 disponible
1

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é.

Résoudre l’exercice →