Optimiser les performances de votre application Next.js

mardi 25 mars 202515 min de lecturePar Damien Gilbrin
Optimiser les performances de votre application Next.js

Table des matières

🚀

Introduction

Next.js s'est imposé comme l'un des frameworks React les plus populaires pour le développement d'applications web modernes. Sa facilité d'utilisation, son approche hybride de rendu et ses nombreuses fonctionnalités intégrées en font un choix privilégié pour les développeurs. Cependant, à mesure que votre application grandit, les performances peuvent devenir un défi.

Imaginez votre application Next.js comme une voiture de course : elle peut avoir le moteur le plus puissant du monde, mais sans une carrosserie aérodynamique, des pneus adaptés et un pilote expérimenté, elle n'atteindra jamais son plein potentiel sur la piste. De même, votre application Next.js a besoin d'optimisations spécifiques pour vraiment exceller.

Dans cet article, nous explorerons des stratégies et techniques concrètes pour optimiser les performances de votre application Next.js, de l'optimisation des images au code splitting, en passant par les Server Components et les stratégies de mise en cache.

Ces optimisations permettront à votre application de se charger plus rapidement, d'utiliser moins de ressources et d'offrir une expérience utilisateur fluide, ce qui peut avoir un impact significatif sur l'engagement des utilisateurs et le classement SEO. Selon les études de Google, 53% des utilisateurs mobiles abandonnent un site si le chargement prend plus de 3 secondes - c'est donc un enjeu crucial!

📊

Comprendre les Core Web Vitals

Avant de plonger dans les optimisations spécifiques, prenons un moment pour comprendre les métriques que nous cherchons à améliorer. Google a introduit les Core Web Vitals comme un ensemble de métriques standardisées pour mesurer l'expérience utilisateur sur le web.

Largest Contentful Paint (LCP)

Le LCP mesure le temps nécessaire pour que le plus grand élément visible (généralement une image ou un bloc de texte) soit affiché à l'écran. Un bon LCP devrait être inférieur à 2,5 secondes.

Le saviez-vous?

Chaque amélioration de 100ms du LCP peut augmenter les taux de conversion de 1%, selon les études de Deloitte.

First Input Delay (FID)

Le FID mesure le délai entre la première interaction d'un utilisateur avec votre page et le moment où le navigateur peut répondre à cette interaction. Un bon FID devrait être inférieur à 100 millisecondes. Next.js 13 a introduit l'Interaction to Next Paint (INP) qui remplacera progressivement le FID.

Cumulative Layout Shift (CLS)

Le CLS mesure la stabilité visuelle en quantifiant le déplacement inattendu des éléments de la page. Un bon CLS devrait être inférieur à 0,1.

En gardant ces métriques à l'esprit, examinons maintenant les techniques spécifiques pour les optimiser dans votre application Next.js.

🖼️

Optimisation des images

Les images représentent souvent plus de 50% du poids total d'une page web. Next.js propose un composant Image qui optimise automatiquement les images pour vos utilisateurs, une fonctionnalité qui peut faire une différence spectaculaire sur vos performances.

Utilisation du composant Image

Le composant Image de Next.js est comme un chef cuisinier pour vos images : il les prépare exactement comme il faut pour chaque client (navigateur). Il apporte plusieurs avantages :

  • Chargement différé automatique (lazy loading) - les images ne sont chargées que lorsqu'elles entrent dans le viewport

  • Redimensionnement adaptatif selon le dispositif - envoi de la taille optimale pour chaque appareil

  • Prévention du décalage de mise en page (layout shift) - réserve l'espace nécessaire avant le chargement

  • Service d'optimisation d'images à la demande - conversion vers des formats modernes comme WebP et AVIF

  • Mise en cache automatique dans le CDN - réduit la charge serveur et accélère les chargements ultérieurs

Exemple d'utilisation :

1import Image from 'next/image';
2
3export default function ProductPage() {
4  return (
5    <div>
6      <Image
7        src="/images/product.jpg"
8        alt="Description du produit"
9        width={700}
10        height={475}
11        priority={true} // Pour les images above the fold
12        sizes="(max-width: 768px) 100vw, 700px"
13        placeholder="blur" // Affiche une version floue pendant le chargement
14        blurDataURL="data:image/jpeg;base64,/9j..." // Base64 de l'image placeholder
15      />
16    </div>
17  );
18}

Astuce de pro: utilisez l'attribut "priority" pour les images situées above the fold (visibles sans faire défiler) afin qu'elles soient préchargées en priorité, ce qui améliore considérablement le LCP!

Les formats d'image modernes

Adopter des formats d'image modernes comme WebP ou AVIF peut réduire considérablement la taille des fichiers tout en maintenant une qualité visuelle équivalente. Next.js convertit automatiquement vos images en WebP si le navigateur le supporte, mais vous pouvez également préparer vos images en amont avec des outils comme Sharp ou Squoosh.

Comparaison des formats

Une image JPEG typique de 200KB peut être réduite à environ 70KB en WebP et à seulement 30KB en AVIF, tout en conservant une qualité visuelle similaire. C'est comme compresser une valise sans sacrifier aucun vêtement!

📦

Code splitting et chargement à la demande

Le code splitting permet de diviser votre JavaScript en petits morceaux qui seront chargés uniquement lorsque nécessaire, réduisant ainsi le temps de chargement initial. C'est comme servir un repas en plusieurs services plutôt que de tout mettre sur la table en même temps - vos invités (utilisateurs) peuvent commencer à profiter du premier plat pendant que vous préparez le reste en cuisine.

Dynamic Imports

Next.js supporte nativement les imports dynamiques, ce qui permet de charger des composants ou des modules uniquement lorsqu'ils sont nécessaires :

1import dynamic from 'next/dynamic';
2
3// Le composant Chart sera chargé uniquement lorsqu'il sera rendu
4const Chart = dynamic(() => import('../components/Chart'), {
5  loading: () => <p>Chargement du graphique...</p>,
6  ssr: false // Désactive le rendu côté serveur si nécessaire
7});
8
9export default function DashboardPage() {
10  return (
11    <div>
12      <h1>Tableau de bord</h1>
13      {/* Le bundle JavaScript pour Chart ne sera chargé 
14          que lorsque ce composant sera rendu */}
15      <Chart data={chartData} />
16    </div>
17  );
18}

Les imports dynamiques sont particulièrement utiles pour les composants lourds comme les éditeurs de texte riches, les visualisations de données ou les bibliothèques de cartes qui ne sont pas nécessaires immédiatement.

Découpage basé sur les routes

Next.js effectue automatiquement un code splitting basé sur les routes. Chaque page devient son propre bundle JavaScript, qui n'est chargé que lorsque l'utilisateur navigue vers cette page. C'est l'un des avantages les plus significatifs du framework.

Pour aller plus loin, vous pouvez utiliser la propriété "suspense" de Next.js pour diviser votre page en morceaux plus petits qui peuvent être chargés en parallèle :

1import { Suspense } from 'react';
2import Loading from '../components/Loading';
3import ProfileData from '../components/ProfileData';
4import FriendsList from '../components/FriendsList';
5
6export default function ProfilePage() {
7  return (
8    <div>
9      <h1>Profil Utilisateur</h1>
10      
11      {/* Ces deux composants peuvent charger leurs données en parallèle */}
12      <Suspense fallback={<Loading section="profile" />}>
13        <ProfileData userId={123} />
14      </Suspense>
15      
16      <Suspense fallback={<Loading section="friends" />}>
17        <FriendsList userId={123} />
18      </Suspense>
19    </div>
20  );
21}

Exploiter les Server Components

Next.js 13 a introduit les React Server Components, une innovation majeure qui permet d'exécuter des composants entièrement sur le serveur, réduisant ainsi la quantité de JavaScript envoyée au client.

Server Components vs Client Components

Les Server Components offrent plusieurs avantages par rapport aux Client Components traditionnels :

  • Réduction drastique de la taille du bundle JavaScript côté client

  • Accès direct aux ressources serveur (bases de données, système de fichiers)

  • Pas de dévoilement des secrets ou des clés API sensibles

  • Meilleure sécurité globale

  • Meilleure performance initiale et amélioration du SEO

Voici comment différencier les Server Components et les Client Components dans Next.js 13+ :

1// server-component.js (composant serveur par défaut)
2export default async function ServerComponent() {
3  // Ce code s'exécute uniquement sur le serveur
4  const data = await fetch('https://api.example.com/data');
5  const result = await data.json();
6  
7  return (
8    <div>
9      <h1>Données du serveur</h1>
10      <pre>{JSON.stringify(result, null, 2)}</pre>
11    </div>
12  );
13}
14
15// client-component.js (composant client explicite)
16'use client'; // Cette directive est obligatoire pour les composants client
17
18import { useState } from 'react';
19
20export default function ClientComponent() {
21  // Ce code s'exécute dans le navigateur
22  const [count, setCount] = useState(0);
23  
24  return (
25    <div>
26      <p>Compteur: {count}</p>
27      <button onClick={() => setCount(count + 1)}>
28        Incrémenter
29      </button>
30    </div>
31  );
32}

Une bonne règle : utilisez des Server Components par défaut, et passez aux Client Components uniquement lorsque vous avez besoin d'interactivité ou d'hooks React (useState, useEffect, etc.).

💾

Stratégies de mise en cache

La mise en cache est l'une des techniques d'optimisation les plus puissantes disponibles. Next.js offre plusieurs niveaux de mise en cache pour maximiser les performances de votre application.

Mise en cache des données avec fetch()

Dans Next.js 13+, la fonction fetch() intégrée à JavaScript a été étendue pour prendre en charge des options de mise en cache :

1// Mise en cache par défaut (force-cache)
2async function getData() {
3  const data = await fetch('https://api.example.com/data');
4  return data.json();
5}
6
7// Pas de mise en cache (données toujours fraîches)
8async function getLatestData() {
9  const data = await fetch('https://api.example.com/latest', { cache: 'no-store' });
10  return data.json();
11}
12
13// Mise en cache avec revalidation périodique
14async function getRevalidatedData() {
15  const data = await fetch('https://api.example.com/data', { 
16    next: { revalidate: 60 } // Revalidation toutes les 60 secondes
17  });
18  return data.json();
19}

Rendu statique vs dynamique

Next.js vous permet de choisir entre rendu statique et dynamique au niveau de la page, ou même de segments individuels de la page :

1// page.js - Rendu statique avec revalidation
2export const revalidate = 3600; // Revalidation toutes les heures
3
4export default async function Page() {
5  return <div>Cette page est générée statiquement</div>;
6}
7
8// dynamic-page.js - Rendu dynamique à chaque requête
9export const dynamic = 'force-dynamic';
10
11export default async function DynamicPage() {
12  return <div>Cette page est générée à chaque requête</div>;
13}

Analogie du restaurant

Pensez au rendu statique comme à des plats préparés à l'avance et conservés au chaud - ils sont servis instantanément mais peuvent ne pas être aussi frais. Le rendu dynamique est comme cuisiner à la commande - cela prend plus de temps mais garantit la fraîcheur. La revalidation ISR est comme refaire un plat toutes les X minutes pour garantir qu'il reste assez frais.

🔍

Analyse des performances avec Lighthouse

Lighthouse est un outil open-source automatisé développé par Google pour améliorer la qualité des pages web. Il vous aide à diagnostiquer et à résoudre les problèmes de performance, d'accessibilité, de bonnes pratiques et de SEO.

Pour analyser votre application Next.js avec Lighthouse :

  • Ouvrez Chrome DevTools (F12 ou Cmd+Option+I)

  • Naviguez vers l'onglet "Lighthouse"

  • Sélectionnez les catégories que vous souhaitez analyser (Performance, Accessibilité, etc.)

  • Cliquez sur "Analyser la page"

Lighthouse vous fournira un score pour chaque catégorie, ainsi que des recommandations spécifiques pour améliorer ces scores. C'est comme avoir un expert en performance qui examine votre site et vous donne des conseils personnalisés.

Astuce : exécutez Lighthouse en mode Incognito pour éviter les interférences des extensions de navigateur. Aussi, testez votre application dans un environnement de production, car le mode développement de Next.js n'est pas optimisé pour les performances.

🔤

Optimisation des polices de caractères

Les polices de caractères peuvent avoir un impact significatif sur les performances et l'expérience utilisateur. Next.js 13 a introduit le composant next/font qui optimise automatiquement le chargement des polices.

1// app/layout.js
2import { Inter, Roboto_Mono } from 'next/font/google';
3
4// Charger la police Inter avec des sous-ensembles spécifiques
5const inter = Inter({
6  subsets: ['latin'],
7  display: 'swap',
8  variable: '--font-inter',
9});
10
11// Charger Roboto Mono pour le code
12const roboto_mono = Roboto_Mono({
13  subsets: ['latin'],
14  display: 'swap',
15  variable: '--font-roboto-mono',
16});
17
18export default function RootLayout({ children }) {
19  return (
20    <html lang="fr" className={`${inter.variable} ${roboto_mono.variable}`}>
21      <body>{children}</body>
22    </html>
23  );
24}

Les avantages de next/font sont nombreux :

  • Zéro requête réseau supplémentaire - les polices sont intégrées directement dans le bundle

  • Polices auto-hébergées - pas de connexion à Google, améliorant la confidentialité

  • Aucun Layout Shift - élimine le CLS lié aux polices

  • Optimisation automatique - tailles de fichier réduites avec des sous-ensembles

📊

Analyse et optimisation du bundle

Pour optimiser efficacement votre application, vous devez comprendre ce qui compose votre bundle JavaScript. L'analyseur de bundle intégré à Next.js peut vous aider à identifier les packages lourds et les opportunités d'optimisation.

Pour activer l'analyseur de bundle, installez @next/bundle-analyzer et mettez à jour votre fichier next.config.js :

1// next.config.js
2const withBundleAnalyzer = require('@next/bundle-analyzer')({
3  enabled: process.env.ANALYZE === 'true',
4});
5
6module.exports = withBundleAnalyzer({
7  // Config de Next.js
8});
9
10// Puis lancez la commande:
11// ANALYZE=true npm run build

Cherchez les grands rectangles dans le rapport de l'analyseur - ils représentent les modules volumineux qui pourraient bénéficier d'imports dynamiques ou être remplacés par des alternatives plus légères.

Tree Shaking et imports sélectifs

Le "tree shaking" est une technique qui élimine le code mort ou non utilisé de votre bundle final. Next.js l'applique automatiquement, mais vous pouvez l'améliorer en utilisant des imports sélectifs.

1// ❌ Mauvais: importe toute la bibliothèque
2import _ from 'lodash';
3
4// ✅ Bon: importe uniquement la fonction nécessaire
5import map from 'lodash/map';
6
7// ✅ Encore mieux: utilise une alternative moderne et légère
8import map from 'just-map'; // ~300 bytes vs ~24kb pour lodash
🔄

Gestion des scripts tiers

Les scripts tiers comme les outils d'analytics, les widgets de chat ou les pixels publicitaires peuvent ralentir considérablement votre site. Next.js fournit le composant Script pour mieux contrôler leur chargement.

1import Script from 'next/script';
2
3export default function Layout({ children }) {
4  return (
5    <>
6      {/* Charge uniquement après le chargement complet de la page */}
7      <Script
8        src="https://analytics.example.com/script.js"
9        strategy="afterInteractive"
10        onLoad={() => console.log('Script chargé')}
11      />
12      
13      {/* Charge seulement quand le composant entre dans la viewport */}
14      <Script
15        src="https://chat.example.com/widget.js"
16        strategy="lazyOnload"
17        onLoad={() => console.log('Chat chargé')}
18      />
19      
20      {children}
21    </>
22  );
23}

Stratégies de chargement des scripts

Next.js propose plusieurs stratégies pour contrôler quand les scripts sont chargés :

  • beforeInteractive: Pour les scripts critiques qui doivent se charger avant que la page devienne interactive

  • afterInteractive (défaut): La page est d'abord rendue interactive, puis le script est chargé

  • lazyOnload: Le script est chargé pendant les périodes d'inactivité du navigateur

  • worker (expérimental): Exécute le script dans un web worker

Analogie ferroviaire 🚂

Pensez à votre page comme à un train qui doit partir à l'heure. Les scripts beforeInteractive sont des passagers prioritaires qui montent avant le départ. Les scripts afterInteractive montent quand le train a déjà démarré sans ralentir le départ. Les scripts lazyOnload sont des marchandises non prioritaires chargées seulement quand il y a de la place disponible.

🏁

Conclusion

L'optimisation des performances d'une application Next.js est un processus continu qui implique plusieurs aspects, de l'optimisation des images à l'implémentation de stratégies de mise en cache efficaces.

Pour résumer les techniques principales que nous avons explorées :

  • Optimisez vos images avec le composant Image et les formats modernes

  • Utilisez le code splitting et les imports dynamiques pour réduire le bundle initial

  • Exploitez les Server Components pour minimiser le JavaScript côté client

  • Implémentez des stratégies de mise en cache intelligentes

  • Optimisez le chargement des polices avec next/font

  • Analysez et réduisez la taille de votre bundle

  • Gérez efficacement les scripts tiers

Rappelez-vous que la performance n'est pas une caractéristique à implémenter une fois, mais un processus d'amélioration continue. Mesurez régulièrement les performances de votre application avec des outils comme Lighthouse et PageSpeed Insights, et utilisez ces données pour guider vos efforts d'optimisation.

Avantages des optimisations

En appliquant ces techniques, vous améliorerez significativement les temps de chargement, l'expérience utilisateur et le classement SEO de votre application. Une application plus rapide se traduit par une meilleure rétention des utilisateurs, des taux de conversion plus élevés et une expérience globale plus satisfaisante.

Prêt à mettre ces optimisations en pratique? Commencez par analyser votre application avec Lighthouse, identifiez les opportunités d'amélioration les plus importantes, et abordez-les une par une. N'oubliez pas de mesurer les résultats après chaque optimisation pour quantifier l'impact de vos changements!

Damien Gilbrin

Damien Gilbrin

Développeur fullstack passionné, je crée des applications web performantes et modernes grâce à mon expertise en React, Next.js, PHP Symfony et les solutions AWS.