Next.js course Β· Module 8: Deployment and Production

Preparing the application for production (build, analyze)

6 min read
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:

  1. Compiles React and TypeScript code into JavaScript
  2. Minifies CSS and JavaScript code
  3. Optimizes images
  4. Pre-renders static routes at build time (Static Site Generation for routes without dynamic data)
  5. Prepares Server Components and route handlers to render on the server (Server-Side Rendering)
  6. 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 build

After 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 start

This 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 build

To use this feature, you need to install and configure the tool @next/bundle-analyzer:

1npm install --save-dev @next/bundle-analyzer

Then 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:

  1. Large dependencies - libraries that significantly affect bundle size
  2. Duplicates - the same packages loaded multiple times
  3. Unused code - dead code that can be removed
  4. 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:

  1. Use ES Module imports instead of require
  2. Avoid side-effect imports (e.g. import 'library')
  3. 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=true

Variables prefixed with NEXT_PUBLIC_ are available in the browser, while the rest are visible only on the server.

Secure secrets management

For production secrets:

  1. Never store secrets in the repository
  2. Use the hosting platform's secrets management (Vercel, Netlify)
  3. 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:

  1. Performance tests - use Lighthouse or WebPageTest
  2. Functional tests - check that all features work as expected
  3. Responsiveness tests - check different screen sizes
  4. 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:

  1. Building an optimized version of the application
  2. Bundle analysis to identify optimization opportunities
  3. Implementing techniques to improve performance
  4. Configuring environment variables
  5. Secure secrets management
  6. 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. 1. The 'npm run build' command in Next.js:

  2. 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

Useful articles