Cours Next.js · Module 8: Déploiement et production
Préparer l'application pour la production (build, analyze)
Dans cette leçon6
Préparer une application Next.js au déploiement en production est une étape cruciale du processus de développement, qui demande une attention particulière à l'optimisation, à l'analyse et à la configuration. Une application correctement préparée offrira une meilleure expérience utilisateur, de meilleures performances et une maintenance plus facile.
Le processus de build d'une application Next.js
Le processus de build dans Next.js transforme le code source en une version optimisée prête au déploiement. Pendant ce processus, Next.js :
- Compile le code React et TypeScript en JavaScript
- Minifie le code CSS et JavaScript
- Optimise les images
- Génère des pages statiques (pour getStaticProps)
- Prépare les composants serveur (pour getServerSideProps)
- Crée les fichiers nécessaires à la navigation côté client
Le processus de build de base
Pour builder une application Next.js, suis ces étapes :
1# D'abord, assure-toi que toutes les dépendances sont installées
2npm install
3
4# Ensuite, builde l'application
5npm run buildUne fois le processus de build terminé, Next.js affiche un récapitulatif des pages, de leurs types (statique, SSR, API) et de la taille de chaque page. Ce récapitulatif fournit des informations précieuses sur les performances de l'application.
Lancer l'application buildée
Après avoir buildé l'application avec succès, tu peux lancer la version de production en local avec :
1npm run startCette commande démarre un serveur Node.js qui sert ton application Next.js en mode production, sans hot-reloading et avec des assets optimisés.
Analyse du bundle (bundle analysis)
L'analyse de la taille du bundle est essentielle pour comprendre exactement ce qui influence la taille de ton application. Next.js propose un outil d'analyse de bundle intégré que tu peux activer à l'aide d'une variable d'environnement :
1ANALYZE=true npm run buildPour utiliser cette fonctionnalité, tu dois installer et configurer l'outil @next/bundle-analyzer :
1npm install --save-dev @next/bundle-analyzerPuis mets à jour le fichier next.config.js :
1const withBundleAnalyzer = require('@next/bundle-analyzer')({
2 enabled: process.env.ANALYZE === 'true',
3})
4
5module.exports = withBundleAnalyzer({
6 // Place ici les autres options de configuration de Next.js
7})Après avoir lancé le build avec l'analyzer activé, des rapports visuels seront générés, montrant une décomposition détaillée de la taille des bundles client et serveur.
Interpréter les résultats de l'analyse du bundle
L'analyse du bundle révèle :
- Grosses dépendances - les bibliothèques qui influencent fortement la taille du bundle
- Doublons - les mêmes paquets chargés plusieurs fois
- Code inutilisé - du code mort qui peut être supprimé
- Répartition des modules - quelles parties de l'application occupent le plus d'espace
À partir de ces informations, tu peux prendre des décisions d'optimisation éclairées :
- Remplacer les grosses bibliothèques par des alternatives plus légères
- Implémenter le lazy loading pour les gros composants
- Supprimer les dépendances inutilisées
- Découper le code en plus petits chunks
Optimisation des performances
Code splitting
Next.js implémente automatiquement le code splitting, mais tu peux optimiser davantage ce processus grâce aux imports dynamiques :
1import dynamic from 'next/dynamic';
2
3// Au lieu d'importer directement
4// import HeavyComponent from '../components/HeavyComponent';
5
6// Utilise un import dynamique
7const HeavyComponent = dynamic(() => import('../components/HeavyComponent'), {
8 loading: () => <p>Loading...</p>,
9 // Le SSR peut être désactivé pour les composants inutiles côté serveur
10 ssr: false,
11});
12
13export default function HomePage() {
14 return (
15 <div>
16 <h1>Home page</h1>
17 <HeavyComponent />
18 </div>
19 );
20}Optimisation des images
Next.js propose le composant Image qui optimise automatiquement les images, mais il vaut la peine d'en configurer en plus les options :
1import Image from 'next/image';
2
3export default function ProductCard({ product }) {
4 return (
5 <div>
6 <Image
7 src={product.imageUrl}
8 alt={product.name}
9 width={300}
10 height={200}
11 placeholder="blur"
12 blurDataURL={product.thumbnailUrl}
13 priority={product.featured} // Charge l'image en priorité haute si elle est mise en avant
14 />
15 <h2>{product.name}</h2>
16 </div>
17 );
18}Tree shaking
Le tree shaking est une technique d'élimination du code mort pendant le processus de build. Next.js utilise Webpack, qui effectue le tree shaking automatiquement. Pour tirer le meilleur parti du tree shaking :
- Utilise des imports ES Module plutôt que require
- Évite les imports à effet de bord (par ex.
import 'library') - Préfère les imports nommés aux imports par défaut quand tu n'as besoin que d'une partie d'une bibliothèque :
1// Mauvais : importe toute la bibliothèque
2import _ from 'lodash';
3const sortedItems = _.sortBy(items, 'name');
4
5// Bon : importe uniquement la fonction nécessaire
6import { sortBy } from 'lodash-es';
7const sortedItems = sortBy(items, 'name');Variables d'environnement
Next.js gère les variables d'environnement via les fichiers .env, .env.local, .env.development et .env.production. Elles sont essentielles pour configurer l'application dans différents environnements.
1# .env.production
2NEXT_PUBLIC_API_URL=https://api.twojadomena.com
3DATABASE_URL=postgresql://user:password@production-db:5432/mydb
4FEATURE_FLAGS_ENABLED=trueLes variables préfixées par NEXT_PUBLIC_ sont disponibles dans le navigateur, tandis que les autres ne sont visibles que côté serveur.
Gestion sécurisée des secrets
Pour les secrets de production :
- Ne stocke jamais de secrets dans le dépôt
- Utilise la gestion des secrets de la plateforme d'hébergement (Vercel, Netlify)
- Pour les opérations sensibles, privilégie le code côté serveur (API Routes)
1// pages/api/secure-operation.ts
2export default async function handler(req, res) {
3 // API_SECRET n'est pas accessible dans le code client
4 const apiSecret = process.env.API_SECRET;
5
6 // Exécution sécurisée de l'opération avec le secret
7 const result = await performSecureOperation(apiSecret);
8
9 // Ne renvoie que des données sûres
10 res.status(200).json({ success: true, data: result.safeData });
11}Tests de production
Avant le déploiement, lance des tests sur la version de production :
- Tests de performance - utilise Lighthouse ou WebPageTest
- Tests fonctionnels - vérifie que toutes les fonctionnalités marchent comme prévu
- Tests de réactivité - vérifie différentes tailles d'écran
- Tests navigateurs - vérifie la compatibilité avec différents navigateurs
Automatiser les tests avant la mise en production
Mets en place un script qui lancera tous les tests nécessaires :
1// scripts/pre-deploy-checks.js
2const { execSync } = require('child_process');
3
4// Lance les tests unitaires
5console.log('Running unit tests...');
6execSync('npm run test', { stdio: 'inherit' });
7
8// Lance les tests end-to-end
9console.log('Running E2E tests...');
10execSync('npm run test:e2e', { stdio: 'inherit' });
11
12// Vérifie les types
13console.log('Checking types...');
14execSync('npm run typecheck', { stdio: 'inherit' });
15
16// Vérifie le linting
17console.log('Vérification du linting...');
18execSync('npm run lint', { stdio: 'inherit' });
19
20// Builde l'application
21console.log("Build de l'application...");
22execSync('npm run build', { stdio: 'inherit' });
23
24console.log('Toutes les vérifications de pré-déploiement sont passées avec succès !');Résumé
Préparer une application Next.js pour la production est un processus en plusieurs étapes qui comprend :
- Le build d'une version optimisée de l'application
- L'analyse du bundle pour repérer les opportunités d'optimisation
- L'implémentation de techniques pour améliorer les performances
- La configuration des variables d'environnement
- La gestion sécurisée des secrets
- Les tests de production
Un processus de préparation à la production mené avec soin garantit de meilleures performances, une meilleure sécurité et une meilleure expérience utilisateur, ce qui se traduit par le succès commercial du projet.
N'oublie pas que l'optimisation est un processus continu - un monitoring régulier des performances et la mise à jour de l'application selon les nouvelles bonnes pratiques sont la clé d'une application performante.
Code de cette leçon : App.tsx
1// Demo: Preparing Next.js application for production
2// Build process, optimization, deployment checklist
3import React, { useState } from 'react';
4
5// Processus de build de Next.js :
6// 1. npm run build → next build
7// - Compiles TypeScript
8// - Generates static pages (SSG)
9// - Optimizes bundle (tree shaking, code splitting)
10// - Generates sitemap, robots.txt
11// 2. npm run start → next start
12// - Starts production server
13// - Serves static files from .next/
14// - Handles SSR and ISR
15
16interface ChecklistItem {
17 id: string;
18 category: string;
19 title: string;
20 description: string;
21 impact: 'critical' | 'high' | 'medium';
22 command?: string;
23}
24
25const checklist: ChecklistItem[] = [
26 {
27 id: '1', category: 'Build', title: 'Build without errors',
28 description: 'npm run build must complete successfully',
29 impact: 'critical', command: 'npm run build',
30 },
31 {
32 id: '2', category: 'Build', title: 'TypeScript - no type errors',
33 description: 'Strict mode, zero any warnings',
34 impact: 'critical', command: 'npx tsc --noEmit',
35 },
36 {
37 id: '3', category: 'Build', title: 'Linting',
38 description: 'ESLint with no warnings or errors',
39 impact: 'high', command: 'npm run lint',
40 },
41 {
42 id: '4', category: 'Performance', title: 'Bundle analysis',
43 description: 'Check bundle size, remove unused dependencies',
44 impact: 'high', command: 'ANALYZE=true npm run build',
45 },
46 {
47 id: '5', category: 'Performance', title: 'Image optimization',
48 description: 'Use next/image, WebP/AVIF, lazy loading',
49 impact: 'high',
50 },
51 {
52 id: '6', category: 'Performance', title: 'Font optimization',
53 description: 'Use next/font, self-hosting, font-display: swap',
54 impact: 'medium',
55 },
56 {
57 id: '7', category: 'SEO', title: 'Metadata on every page',
58 description: 'title, description, Open Graph pour le social sharing',
59 impact: 'high',
60 },
61 {
62 id: '8', category: 'SEO', title: 'sitemap.xml et robots.txt',
63 description: 'Automatic generation in app/sitemap.ts and app/robots.ts',
64 impact: 'medium',
65 },
66 {
67 id: '9', category: 'Security', title: 'Environment variables',
68 description: 'Secrets in .env.local, NEXT_PUBLIC_ only for public ones',
69 impact: 'critical',
70 },
71 {
72 id: '10', category: 'Security', title: 'Security headers',
73 description: 'CSP, X-Frame-Options, HSTS dans next.config.js',
74 impact: 'high',
75 },
76 {
77 id: '11', category: 'Deploy', title: 'Platform selection',
78 description: 'Vercel (best), Netlify, AWS, Docker',
79 impact: 'high',
80 },
81 {
82 id: '12', category: 'Deploy', title: 'CI/CD pipeline',
83 description: 'GitHub Actions: build → test → deploy',
84 impact: 'medium',
85 },
86];
87
88const impactColors = { critical: '#f44336', high: '#ff9800', medium: '#64ffda' };
89const categories = [...new Set(checklist.map(c => c.category))];
90
91function BuildOutput() {
92 const routes = [
93 { path: '/', type: 'Static', size: '5.2 kB' },
94 { path: '/products', type: 'SSG', size: '12.4 kB' },
95 { path: '/products/[id]', type: 'SSG', size: '8.7 kB' },
96 { path: '/dashboard', type: 'SSR', size: '15.1 kB' },
97 { path: '/api/health', type: 'API', size: '1.2 kB' },
98 ];
99
100 const typeIcons: Record<string, string> = {
101 Static: 'S', SSG: 'G', SSR: 'R', API: 'A',
102 };
103 const typeColors: Record<string, string> = {
104 Static: '#7c4dff', SSG: '#64ffda', SSR: '#ff9800', API: '#2196f3',
105 };
106
107 return (
108 <div style={{
109 background: 'rgba(0,0,0,0.3)', borderRadius: '8px', padding: '16px',
110 }}>
111 <h4 style={{ color: '#64ffda', marginBottom: '8px', fontSize: '0.85rem' }}>
112 $ next build
113 </h4>
114 <div style={{ display: 'grid', gap: '4px' }}>
115 {routes.map(r => (
116 <div key={r.path} style={{
117 display: 'flex', alignItems: 'center', gap: '10px',
118 padding: '6px 8px', fontFamily: 'monospace', fontSize: '0.8rem',
119 }}>
120 <span style={{
121 width: '20px', height: '20px', borderRadius: '4px',
122 background: typeColors[r.type], color: '#0f0f23',
123 display: 'flex', alignItems: 'center', justifyContent: 'center',
124 fontSize: '0.7rem', fontWeight: 'bold', flexShrink: 0,
125 }}>{typeIcons[r.type]}</span>
126 <span style={{ color: '#b0bec5', flex: 1 }}>{r.path}</span>
127 <span style={{ color: '#78909c' }}>{r.size}</span>
128 </div>
129 ))}
130 </div>
131 <div style={{
132 marginTop: '12px', padding: '8px', borderTop: '1px solid rgba(255,255,255,0.05)',
133 display: 'flex', justifyContent: 'space-between',
134 }}>
135 <span style={{ color: '#4caf50', fontSize: '0.8rem' }}>Build completed in 12.4s</span>
136 <span style={{ color: '#78909c', fontSize: '0.8rem' }}>Total: 42.6 kB</span>
137 </div>
138 </div>
139 );
140}
141
142export default function ProductionChecklist() {
143 const [completed, setCompleted] = useState<Set<string>>(new Set());
144 const [activeCategory, setActiveCategory] = useState('all');
145
146 const toggle = (id: string) => {
147 setCompleted(prev => {
148 const next = new Set(prev);
149 if (next.has(id)) next.delete(id); else next.add(id);
150 return next;
151 });
152 };
153
154 const filtered = activeCategory === 'all'
155 ? checklist
156 : checklist.filter(c => c.category === activeCategory);
157
158 const progress = Math.round((completed.size / checklist.length) * 100);
159
160 return (
161 <div style={{
162 background: '#0f0f23', minHeight: '100vh', padding: '24px',
163 color: '#fff', fontFamily: 'system-ui, sans-serif',
164 }}>
165 <h1 style={{ color: '#64ffda', marginBottom: '4px' }}>Production Checklist</h1>
166 <p style={{ color: '#b0bec5', marginBottom: '16px' }}>
167 Preparing Next.js application for deployment
168 </p>
169
170 <div style={{
171 background: 'rgba(100,255,218,0.05)', borderRadius: '10px',
172 padding: '12px 16px', marginBottom: '20px',
173 display: 'flex', alignItems: 'center', gap: '16px',
174 }}>
175 <div style={{
176 width: '48px', height: '48px', borderRadius: '50%',
177 background: `conic-gradient(#64ffda ${progress * 3.6}deg, rgba(255,255,255,0.05) 0deg)`,
178 display: 'flex', alignItems: 'center', justifyContent: 'center',
179 }}>
180 <span style={{ color: '#64ffda', fontWeight: 'bold', fontSize: '0.8rem' }}>{progress}%</span>
181 </div>
182 <div>
183 <p style={{ color: '#fff', margin: 0, fontWeight: 600 }}>{completed.size}/{checklist.length} completed</p>
184 <p style={{ color: '#78909c', margin: 0, fontSize: '0.8rem' }}>
185 {progress === 100 ? 'Ready to deploy!' : 'Complete all checks before deploying'}
186 </p>
187 </div>
188 </div>
189
190 <div style={{ display: 'flex', gap: '8px', marginBottom: '16px' }}>
191 <button onClick={() => setActiveCategory('all')} style={{
192 background: activeCategory === 'all' ? '#64ffda' : 'transparent',
193 color: activeCategory === 'all' ? '#0f0f23' : '#b0bec5',
194 border: '1px solid rgba(100,255,218,0.3)',
195 padding: '6px 12px', borderRadius: '20px', cursor: 'pointer',
196 }}>All</button>
197 {categories.map(c => (
198 <button key={c} onClick={() => setActiveCategory(c)} style={{
199 background: activeCategory === c ? '#64ffda' : 'transparent',
200 color: activeCategory === c ? '#0f0f23' : '#b0bec5',
201 border: '1px solid rgba(100,255,218,0.3)',
202 padding: '6px 12px', borderRadius: '20px', cursor: 'pointer',
203 }}>{c}</button>
204 ))}
205 </div>
206
207 <div style={{ display: 'grid', gap: '8px', marginBottom: '20px' }}>
208 {filtered.map(item => (
209 <div key={item.id} onClick={() => toggle(item.id)} style={{
210 display: 'flex', alignItems: 'center', gap: '12px',
211 background: completed.has(item.id) ? 'rgba(76,175,80,0.05)' : 'rgba(255,255,255,0.02)',
212 border: `1px solid ${completed.has(item.id) ? 'rgba(76,175,80,0.2)' : 'rgba(255,255,255,0.06)'}`,
213 borderRadius: '8px', padding: '12px', cursor: 'pointer',
214 }}>
215 <div style={{
216 width: '22px', height: '22px', borderRadius: '6px',
217 border: `2px solid ${completed.has(item.id) ? '#4caf50' : 'rgba(255,255,255,0.2)'}`,
218 background: completed.has(item.id) ? '#4caf50' : 'transparent',
219 display: 'flex', alignItems: 'center', justifyContent: 'center',
220 color: '#fff', fontSize: '0.7rem', flexShrink: 0,
221 }}>
222 {completed.has(item.id) ? 'V' : ''}
223 </div>
224 <div style={{ flex: 1 }}>
225 <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
226 <span style={{
227 color: completed.has(item.id) ? '#78909c' : '#fff',
228 textDecoration: completed.has(item.id) ? 'line-through' : 'none',
229 }}>{item.title}</span>
230 <span style={{
231 background: `${impactColors[item.impact]}15`,
232 color: impactColors[item.impact],
233 padding: '1px 8px', borderRadius: '10px', fontSize: '0.65rem',
234 }}>{item.impact}</span>
235 </div>
236 <p style={{ color: '#78909c', fontSize: '0.8rem', margin: '2px 0 0' }}>{item.description}</p>
237 </div>
238 </div>
239 ))}
240 </div>
241
242 <BuildOutput />
243 </div>
244 );
245}Tu as repéré une erreur dans cette leçon ?
Teste tes connaissances
Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.
1. La commande « npm run build » dans Next.js :
2. Le bundle analyzer dans Next.js sert à :
Voici 2 des 3 questions de cette leçon. Réponds aux autres dans le jeu.
Tâches pratiques dans le jeu
- Éditeur de code
Construis une interface avec optimisation de la taille du bundle et lazy loading
- Clique dans l'ordre
Place les éléments dans le bon ordre : npm → install → @next/bundle-analyzer