Next.js course Β· Module 8: Deployment and Production
Preparing the application for production (build, analyze)
In this lesson6
Preparing a Next.js application for production deployment is a crucial stage of the development process that requires special attention to optimization, analysis, and configuration. A properly prepared application will provide a better user experience, higher performance, and easier maintenance.
The Next.js application build process
The build process in Next.js transforms source code into an optimized version ready for deployment. During this process, Next.js:
- Compiles React and TypeScript code into JavaScript
- Minifies CSS and JavaScript code
- Optimizes images
- Pre-renders static routes at build time (Static Site Generation for routes without dynamic data)
- Prepares Server Components and route handlers to render on the server (Server-Side Rendering)
- Creates the files needed for client-side navigation
Basic build process
To build a Next.js application, follow these steps:
1# First, make sure all dependencies are installed
2npm install
3
4# Then build the application
5npm run buildAfter the build process completes, Next.js will display a summary of pages, their types (static, SSR, API), and the size of each page. This summary provides valuable information about application performance.
Running the built application
After successfully building the application, you can run the production version locally using:
1npm run startThis command starts a Node.js server that serves your Next.js application in production mode, without hot-reloading and with optimized assets.
Bundle analysis
Bundle size analysis is essential to understand exactly what affects your application's size. Next.js offers a built-in bundle analysis tool that can be enabled using an environment variable:
1ANALYZE=true npm run buildTo use this feature, you need to install and configure the tool @next/bundle-analyzer:
1npm install --save-dev @next/bundle-analyzerThen update the file next.config.js:
1const withBundleAnalyzer = require('@next/bundle-analyzer')({
2 enabled: process.env.ANALYZE === 'true',
3})
4
5module.exports = withBundleAnalyzer({
6 // Place other Next.js configuration options here
7})After running the build with the analyzer enabled, visual reports will be generated showing a detailed breakdown of client and server bundle sizes.
Interpreting bundle analysis results
Bundle analysis reveals:
- Large dependencies - libraries that significantly affect bundle size
- Duplicates - the same packages loaded multiple times
- Unused code - dead code that can be removed
- Module distribution - which parts of the application take up the most space
Based on this information, you can make informed optimization decisions:
- Replace large libraries with lighter alternatives
- Implement lazy loading for large components
- Remove unused dependencies
- Split code into smaller chunks
Performance optimization
Code splitting
Next.js automatically implements code splitting, but you can further optimize this process using dynamic imports:
1import dynamic from 'next/dynamic';
2
3// Instead of importing directly
4// import HeavyComponent from '../components/HeavyComponent';
5
6// Use dynamic import
7const HeavyComponent = dynamic(() => import('../components/HeavyComponent'), {
8 loading: () => <p>Loading...</p>,
9 // SSR can be disabled for components that are not needed on the server
10 ssr: false,
11});
12
13export default function HomePage() {
14 return (
15 <div>
16 <h1>Home page</h1>
17 <HeavyComponent />
18 </div>
19 );
20}Image optimization
Next.js offers the component Image which automatically optimizes images, but it's worth additionally configuring the 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} // Loads image with high priority if featured
14 />
15 <h2>{product.name}</h2>
16 </div>
17 );
18}Tree shaking
Tree shaking is a technique for eliminating dead code during the build process. Next.js uses Webpack, which performs tree shaking automatically. To maximize the benefits of tree shaking:
- Use ES Module imports instead of require
- Avoid side-effect imports (e.g.
import 'library') - Prefer named imports over default imports when you only need part of a library:
1// Bad: imports the entire library
2import _ from 'lodash';
3const sortedItems = _.sortBy(items, 'name');
4
5// Good: imports only the needed function
6import { sortBy } from 'lodash-es';
7const sortedItems = sortBy(items, 'name');Environment variables
Next.js handles environment variables through files .env, .env.local, .env.development i .env.production. They are key for configuring the application in different environments.
1# .env.production
2NEXT_PUBLIC_API_URL=https://api.yourdomain.com
3DATABASE_URL=postgresql://user:password@production-db:5432/mydb
4FEATURE_FLAGS_ENABLED=trueVariables prefixed with NEXT_PUBLIC_ are available in the browser, while the rest are visible only on the server.
Secure secrets management
For production secrets:
- Never store secrets in the repository
- Use the hosting platform's secrets management (Vercel, Netlify)
- For sensitive operations, prefer server-side code (API Routes)
1// pages/api/secure-operation.ts
2export default async function handler(req, res) {
3 // API_SECRET is not accessible in client code
4 const apiSecret = process.env.API_SECRET;
5
6 // Securely perform the operation with the secret
7 const result = await performSecureOperation(apiSecret);
8
9 // Return only safe data
10 res.status(200).json({ success: true, data: result.safeData });
11}Production testing
Before deployment, run production version tests:
- Performance tests - use Lighthouse or WebPageTest
- Functional tests - check that all features work as expected
- Responsiveness tests - check different screen sizes
- Browser tests - check compatibility with different browsers
Pre-production test automation
Set up a script that will run all necessary tests:
1// scripts/pre-deploy-checks.js
2const { execSync } = require('child_process');
3
4// Run unit tests
5console.log('Running unit tests...');
6execSync('npm run test', { stdio: 'inherit' });
7
8// Run end-to-end tests
9console.log('Running E2E tests...');
10execSync('npm run test:e2e', { stdio: 'inherit' });
11
12// Check types
13console.log('Checking types...');
14execSync('npm run typecheck', { stdio: 'inherit' });
15
16// Check linting
17console.log('Checking linting...');
18execSync('npm run lint', { stdio: 'inherit' });
19
20// Build the application
21console.log('Building the application...');
22execSync('npm run build', { stdio: 'inherit' });
23
24console.log('All pre-deployment checks passed successfully!');Summary
Preparing a Next.js application for production is a multi-step process that includes:
- Building an optimized version of the application
- Bundle analysis to identify optimization opportunities
- Implementing techniques to improve performance
- Configuring environment variables
- Secure secrets management
- Production testing
A carefully conducted production preparation process ensures better performance, security, and user experience, which translates into business success of the project.
Remember that optimization is an ongoing process - regular performance monitoring and updating the application according to new best practices is the key to maintaining a performant application.
Code for this lesson: App.tsx
1// Demo: Preparing Next.js application for production
2// Build process, optimization, deployment checklist
3import React, { useState } from 'react';
4
5// Next.js build process:
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 for social sharing',
59 impact: 'high',
60 },
61 {
62 id: '8', category: 'SEO', title: 'sitemap.xml and 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 in 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}Spotted a mistake in this lesson?
Check yourself
Answer the questions from this lesson. Pick an answer to see right away whether it is correct.
1. The 'npm run build' command in Next.js:
2. Bundle analyzer in Next.js is used for:
These are 2 of 3 questions for this lesson. Solve the rest in the game.
Hands-on tasks in the game
- Code editor
Build an interface with bundle size optimization and lazy loading
- Click in order
Arrange the elements in the correct order: npm β install β @next/bundle-analyzer