Les images représentent en moyenne 50 à 70 % du poids total d'une page web. C'est la ressource la plus lourde, la plus mal optimisée, et paradoxalement celle sur laquelle les gains sont les plus rapides. Un projet Next.js bien configuré peut réduire ce poids de 60 à 80 % sans toucher une ligne de design, uniquement en appliquant les bonnes décisions sur les formats, les tailles et la stratégie de chargement.
Pourquoi les médias mal gérés coûtent cher
Un utilisateur sur mobile en 4G dispose d'environ 10 à 20 Mo/s de bande passante dans les meilleures conditions. En pratique, sur un réseau urbain chargé, on tombe à 3 à 5 Mo/s. Une page avec 8 images JPEG non compressées de 800 Ko chacune représente 6,4 Mo de données à télécharger. Sur une connexion à 4 Mo/s, ça prend 12 secondes. L'utilisateur est parti depuis longtemps.
Ce n'est pas un problème esthétique. C'est un problème de revenus.
| Contexte | Poids page non optimisée | Poids page optimisée | Temps de chargement gagné |
|---|---|---|---|
| Site vitrine (8 images) | 6,4 Mo | 1,1 Mo | ~8 s sur 4G |
| E-commerce (listing 24 produits) | 18 Mo | 2,8 Mo | ~20 s sur 4G |
| Article de blog (3 images) | 2,4 Mo | 320 Ko | ~5 s sur 4G |
| Landing page avec hero vidéo | 12 Mo | 3,2 Mo | ~14 s sur 4G |
Ces chiffres sont des ordres de grandeur réalistes, pas des cas extrêmes. Sur des projets en refonte, on retrouve régulièrement des assets non optimisés issus de sources diverses : photos stock exportées en pleine résolution, screenshots PNG de maquettes, vidéos de fond compressées une seule fois à la va-vite.
Les formats : choisir le bon outil selon le contenu
Le choix du format d'image est la décision la plus impactante. Chaque format a ses forces et ses cas d'usage. En utiliser un seul pour tout est la première source de gaspillage.
WebP : le nouveau standard
WebP offre une compression supérieure de 25 à 35 % par rapport au JPEG pour une qualité visuelle équivalente, et de 25 à 50 % par rapport au PNG pour les images avec transparence. Le support navigateur est universel depuis 2023 (Chrome, Firefox, Safari, Edge).
// next.config.ts — activer WebP et AVIF
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
images: {
formats: ["image/avif", "image/webp"],
deviceSizes: [640, 750, 828, 1080, 1200, 1920],
imageSizes: [16, 32, 48, 64, 96, 128, 256],
},
};
export default nextConfig;
Next.js sert automatiquement le format le plus léger supporté par le navigateur. Un navigateur qui supporte AVIF reçoit de l'AVIF. Les autres reçoivent du WebP. Aucun JPEG ne sort en production si cette configuration est en place.
AVIF : le gain supplémentaire
AVIF compresse 40 à 50 % mieux que JPEG et 20 % mieux que WebP. Le support est désormais présent dans Chrome, Firefox et Safari. Le seul inconvénient est le temps d'encodage côté serveur, plus long qu'en WebP. Pour les images statiques, ce coût est absorbé au build. Pour les images dynamiques, WebP reste souvent le meilleur compromis.
SVG : pour tout ce qui est vectoriel
Les icônes, les logos, les illustrations géométriques n'ont rien à faire en PNG ou JPEG. Un logo en SVG pèse quelques Ko, peut être mis à l'échelle à n'importe quelle résolution sans perte, et peut être stylisé en CSS. Un logo exporté en PNG à 2x pour les écrans Retina pèse facilement 40 à 80 Ko pour un résultat inférieur.
// À éviter absolument
<img src="/logo.png" width="120" height="40" alt="Logo Kreio" />
// La bonne approche
import Logo from "@/assets/logo.svg";
<Logo className="w-30 h-10" aria-label="Logo Kreio" />
Le tableau de décision
| Type de contenu | Format recommandé | Format à éviter |
|---|---|---|
| Photo, hero, illustration complexe | AVIF, WebP | PNG (trop lourd), GIF |
| Image avec transparence | WebP, AVIF | PNG (acceptable si nécessaire) |
| Logo, icône, illustration géométrique | SVG | Tout format raster |
| Animation simple | WebP animé, CSS | GIF (5 à 20x plus lourd) |
| Screenshot UI | WebP | PNG (acceptable), JPEG (artefacts) |
Les tailles : servir la bonne résolution selon l'écran
Servir une image de 1920 pixels sur un téléphone avec un écran de 390 pixels de large est une erreur aussi courante que coûteuse. Le navigateur télécharge l'image entière, puis la réduit à l'affichage. Les pixels supplémentaires sont téléchargés inutilement.
L'attribut srcset et les sizes
La solution native est l'attribut srcset, qui liste plusieurs versions d'une image à différentes résolutions. Le navigateur choisit la plus appropriée selon la taille de l'écran et la densité de pixels.
<!-- Implémentation manuelle avec srcset -->
<img
src="/hero-1200.webp"
srcset="
/hero-640.webp 640w,
/hero-828.webp 828w,
/hero-1200.webp 1200w,
/hero-1920.webp 1920w
"
sizes="
(max-width: 640px) 100vw,
(max-width: 1200px) 100vw,
1920px
"
width="1920"
height="1080"
alt="Hero Kreio"
fetchpriority="high"
/>
Le composant Image de Next.js
Next.js génère automatiquement toutes les variantes de taille et les sert via son CDN d'optimisation. Le composant Image abstrait toute cette complexité.
import Image from "next/image";
// Image hero above the fold
export function Hero() {
return (
<Image
src="/hero.jpg"
alt="Agence web Kreio — design et développement sur-mesure"
width={1920}
height={1080}
priority // précharge l'image, améliore le LCP
quality={85} // qualité WebP/AVIF (100 par défaut, 75-85 recommandé)
sizes="100vw" // l'image occupe toute la largeur
className="object-cover w-full h-full"
/>
);
}
// Image dans une grille (below the fold)
export function ServiceCard({ image, title }: { image: string; title: string }) {
return (
<div className="relative aspect-video">
<Image
src={image}
alt={title}
fill // remplit le conteneur, utilise position: absolute
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
className="object-cover"
// pas de priority ici : chargé en lazy par défaut
/>
</div>
);
}
L'attribut sizes est le paramètre le plus important à renseigner correctement. Il indique au navigateur quelle largeur l'image occupera à l'écran, ce qui lui permet de choisir la variante la plus légère. Sans sizes, Next.js suppose que l'image fait 100 % de la largeur de l'écran et sert des versions inutilement grandes.
La stratégie de chargement : quoi charger et quand
Télécharger toutes les images d'une page au chargement initial est inutile. Les images en bas de page ne seront peut-être jamais vues. La stratégie de chargement différencié permet de prioriser ce qui compte.
Priority vs Lazy loading
// Above the fold : priority obligatoire
// Pré-chargé dans le <head>, pas de lazy loading
<Image src="/hero.jpg" alt="..." priority />
// Below the fold : lazy par défaut dans Next.js
// Chargé uniquement quand l'image entre dans le viewport
<Image src="/section.jpg" alt="..." />
// équivalent à loading="lazy" en HTML natif
La règle est simple : une seule image par page doit avoir priority — l'image principale visible au-dessus de la ligne de flottaison. Mettre priority sur toutes les images annule l'effet et dégrade le LCP en saturant la bande passante initiale.
Le placeholder pendant le chargement
Next.js propose deux stratégies de placeholder pour éviter le CLS pendant le chargement des images :
// Placeholder flou (blur) — recommandé pour les images statiques
import heroImage from "@/public/hero.jpg"; // import statique nécessaire
<Image
src={heroImage}
alt="..."
placeholder="blur" // génère automatiquement une version 10x10px
priority
/>
// Placeholder de couleur personnalisé — pour les images dynamiques
<Image
src={url}
alt="..."
placeholder="blur"
blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRgAB..."
// générer avec : https://png-pixel.com/
/>
Le placeholder blur génère une version de l'image en 10 × 10 pixels encodée en base64, inlinée dans le HTML. Elle s'affiche instantanément, sans requête réseau, et disparaît progressivement quand l'image réelle est chargée. Le CLS est nul.
La bande passante : aller plus loin avec les optimisations réseau
Le preload pour les ressources critiques
Pour les images hébergées sur un CDN externe ou servies via un src dynamique, le preload <link> dans le <head> garantit que le navigateur commence le téléchargement le plus tôt possible.
// src/app/layout.tsx — preload de l'image hero
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="fr">
<head>
<link
rel="preload"
as="image"
href="/hero.webp"
imageSrcSet="/hero-640.webp 640w, /hero-1200.webp 1200w"
imageSizes="100vw"
/>
</head>
<body>{children}</body>
</html>
);
}
Les images hébergées sur un domaine externe
Next.js bloque par défaut les images provenant de domaines non déclarés. C'est une mesure de sécurité qui évite de servir des images arbitraires via l'optimiseur Next.js. Pour autoriser un domaine externe :
// next.config.ts
const nextConfig: NextConfig = {
images: {
remotePatterns: [
{
protocol: "https",
hostname: "images.prismic.io",
pathname: "/kreio/**",
},
{
protocol: "https",
hostname: "cdn.sanity.io",
},
],
},
};
La compression côté build pour les assets statiques
Les images placées dans le dossier public/ ne passent pas par l'optimiseur Next.js : elles sont servies telles quelles. Il faut les optimiser avant de les committer.
# Installer les outils de compression
pnpm add -D sharp @squoosh/cli
# Convertir et compresser en WebP via sharp (dans un script de build)
node -e "
const sharp = require('sharp');
sharp('./public/hero.jpg')
.webp({ quality: 85 })
.toFile('./public/hero.webp')
.then(info => console.log(info));
"
# Ou via squoosh-cli pour un traitement par lot
npx @squoosh/cli --webp '{"quality":85}' ./public/images/*.jpg
Pour les projets avec beaucoup d'assets statiques, un script de build dédié à la compression est plus maintenable qu'un traitement manuel image par image.
La vidéo : le cas particulier le plus coûteux
Les vidéos de fond sont le cas le plus coûteux à mal gérer. Une vidéo MP4 non compressée peut peser 20 à 50 Mo et bloquer complètement le rendu de la page sur mobile.
<!-- Configuration optimale pour une vidéo de fond -->
<video
autoplay
loop
muted
playsinline
preload="metadata"
poster="/hero-poster.webp"
>
<!-- WebM pour les navigateurs modernes (30-50% plus léger que MP4) -->
<source src="/hero.webm" type="video/webm" />
<!-- MP4 en fallback -->
<source src="/hero.mp4" type="video/mp4" />
</video>
Les paramètres critiques :
preload="metadata" : charge uniquement les métadonnées (durée, dimensions) sans télécharger la vidéo. Le navigateur attend que l'utilisateur soit prêt à voir la vidéo avant de la charger.
poster : affiche une image statique pendant le chargement. Sans poster, la zone vidéo est noire jusqu'au premier frame, ce qui génère un flash visible.
WebM : 30 à 50 % plus léger que MP4 à qualité équivalente. À servir en priorité aux navigateurs modernes.
Pour aller plus loin, Handbrake est l'outil de référence open source pour compresser des vidéos avant de les pousser en production. Une vidéo de 30 secondes destinée à un hero peut généralement descendre sous 3 Mo en WebM avec une qualité CRF de 28 à 33.
Checklist de mise en production
Avant tout déploiement, vérifier systématiquement :
- Toutes les images passent par
next/image(pas de balise<img>nue) - L'image hero a
priorityet une seule image par page l'a -
sizesest renseigné sur chaqueImageselon la mise en page réelle -
qualityest à 80-85 (pas à 100 par défaut) - Les logos et icônes sont en SVG, pas en PNG
- Aucun GIF en production (remplacé par WebP animé ou MP4)
- Les images statiques dans
public/sont compressées avant commit - Les vidéos ont un
poster,preload="metadata", et une source WebM - Les domaines externes sont dans
remotePatterns, pas dansdomains - PageSpeed Insights passe au vert sur « Properly size images » et « Serve images in next-gen formats »
Ce qu'on en retient
La gestion des médias est le levier de performance le plus accessible et le plus souvent négligé. Les gains sont immédiats, mesurables, et ne nécessitent aucune refonte architecturale. Passer de 6 Mo à 1 Mo de ressources images sur une page, c'est 5 secondes de chargement récupérées sur mobile, un LCP qui passe dans le vert, et des utilisateurs qui restent.
Le composant Image de Next.js fait 80 % du travail si on le configure correctement : formats modernes, variantes de taille, lazy loading. Les 20 % restants, c'est la discipline sur les assets statiques, les vidéos, et le bon usage de priority et sizes.