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).
// BAD: imports entire library (70KB)
import _ from 'lodash';
const result = _.debounce(fn, 300);
// GOOD: imports only what you need (2KB)
import debounce from 'lodash/debounce';
const result = debounce(fn, 300);
// BAD: imports all 1000+ icons (200KB)
import { ArrowRight } from 'lucide-react';
// GOOD: tree-shakeable import
import 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).
// Server Component (default) - sends HTML, zero JS
async 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>
{/* Only the button is client-side */}
<LikeButton postId={post.id} />
</article>
))}
</div>
);
}
// Client Component - only the interactive part
'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 %.
// BAD: loads full 4MB image
<img src="/hero.jpg" />
// GOOD: Next.js optimizes, lazy loads, serves WebP
import Image from 'next/image';
<Image
src="/hero.jpg"
width={1200}
height={600}
priority // for above-the-fold images
alt="Hero image"
/>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: cache Next.js static files for 1 year
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';
// Only loads when component is rendered
const Chart = dynamic(() => import('@/components/Chart'), {
loading: () => <div className="h-64 animate-pulse bg-gray-800" />,
ssr: false, // chart library needs window
});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', // show fallback font until loaded
});
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 |
