Pourquoi votre application Next.js est lente : 7 astuces pour améliorer ses performances qui fonctionnent vraiment
Votre application Next.js met plus de 4 secondes à se charger. Son score Lighthouse est de 60. Les utilisateurs quittent rapidement la page. Voici 7 solutions concrètes accompagnées de mesures avant/après — pas de conseils vagues, juste du code.
1. Vous envoyez 500 Ko de code JavaScript au navigateur
Exécutez la commande `npx @next/bundle-analyzer` et vérifiez le contenu de votre bundle client. Les éléments les plus courants à alléger : moment.js (300 Ko → à remplacer par date-fns), lodash (70 Ko → importer uniquement les fonctions spécifiques), les bibliothèques d'icônes (importer toutes les icônes au lieu de celles qui sont nécessaires).
// MAUVAIS : importe toute la bibliothèque (70 Ko)
import _ from 'lodash';
const result = _.debounce(fn, 300);
// BON : n'importe que ce dont vous avez besoin (2 Ko)
import debounce from 'lodash/debounce';
const result = debounce(fn, 300);
// MAUVAIS : importe toutes les icônes (plus de 1 000) (200 Ko)
import { ArrowRight } from 'lucide-react';
// BON : importation
optimisableimport ArrowRight from 'lucide-react/dist/esm/icons/arrow-right';2. Chaque composant est de type « use client »
Si vous ajoutez « use client » à tous vos composants, vous envoyez tout ce JavaScript au navigateur. Les composants serveur s'affichent côté serveur et renvoient du code HTML — sans aucune ligne de JavaScript. N'utilisez « use client » que pour les composants qui nécessitent une interactivité (onClick, useState, useEffect).
// Composant serveur (par défaut) - envoie du code HTML, pas de
JSasync function BlogList() {
const posts = await db.posts.findMany();
return (
<div>
{posts.map(post => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
{/* Seul le bouton est côté client */}
<LikeButton postId={post.id} />
</article>
))}
</div>
);
}
// Composant client - uniquement la partie
interactive'use client';
function LikeButton({ postId }: { postId: string }) {
const [liked, setLiked] = useState(false);
return <button onClick={() => setLiked(!liked)}>Like</button>;
}3. Images sans « next/image »
Les balises <img> brutes chargent l'image en taille réelle. La balise next/image redimensionne automatiquement l'image, la convertit au format WebP/AVIF et la charge de manière différée. À elle seule, cette technique permet de réduire le LCP de 40 à 60 %.
// MAUVAIS : charge l'image
complète de 4 Mo<img src="/hero.jpg" />
// BON : Next.js optimise, charge les images à la demande et les sert au format
WebPimport Image from 'next/image';
<Image
src="/hero.jpg"
width={1200}
height={600}
priority // pour les images situées au-dessus de la
ligne de flottaison alt="Image d'en-tête"
/>4. Récupération des données côté client alors que le SSR serait préférable
Si vous utilisez `useEffect` + `fetch` pour charger des données à chaque visite de la page, vous obligez l'utilisateur à attendre : le téléchargement du code HTML → l'analyse du JavaScript → l'initialisation de React → la requête `fetch` → le rendu. Avec les composants serveur, la récupération des données s'effectue sur le serveur et l'utilisateur obtient immédiatement le code HTML complet.
5. Absence d'en-têtes de mise en cache pour les ressources statiques
Les fichiers statiques Next.js situés dans le répertoire /_next/static/ sont hachés et immuables. Votre serveur Nginx devrait les mettre en cache de manière intensive :
# Nginx : mise en cache des fichiers statiques Next.js pendant 1 an
location /_next/static {
proxy_pass http://frontend:3000;
add_header Cache-Control "public, max-age=31536000, immutable";
}6. Ne pas utiliser React.lazy pour les composants lourds
Les composants tels que les éditeurs de texte enrichi, les graphiques et les cartes sont très volumineux. Utilisez le chargement différé pour qu'ils ne bloquent pas le rendu initial :
import dynamic from 'next/dynamic';
// Ne se charge que lorsque le composant est affiché
const Chart = dynamic(() => import('@/components/Chart'), {
loading: () => <div className="h-64 animate-pulse bg-gray-800" />,
ssr: false, // La bibliothèque de graphiques nécessite la fenêtre
});7. Pas d'optimisation des polices
Les polices personnalisées provoquent un décalage de mise en page (CLS) lorsqu'elles se chargent tardivement. Utilisez next/font pour héberger vous-même et précharger les polices :
import { Inter } from 'next/font/google';
const inter = Inter({
subsets: ['latin'],
display: 'swap', // afficher la police de secours jusqu'à ce que la police soit chargée
});
export default function Layout({ children }) {
return <body className={inter.className}>{children}</body>;
}Résultats attendus
| Système métrique | Avant | Une fois toutes les corrections apportées |
|---|---|---|
| Lighthouse Performance | 58 | 94 |
| Premier affichage de contenu | 2,8 s | 0,8 s |
| Premier élément visible | 4,2 s | 1,4 s |
| Ensemble complet JS | 520 Ko | 180 Ko |
| Déplacement cumulatif de la mise en page | 0,25 | 0,02 |
