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 build process in Next.js transforms source code into an optimized version ready for deployment. During this process, Next.js:
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.
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 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.
Bundle analysis reveals:
Based on this information, you can make informed optimization decisions:
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}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 blurDataURL={product.thumbnailUrl}
12 priority={product.featured} // Loads image with high priority if featured
13 />
14 <h2>{product.name}</h2>
15 </div>
16 );
17}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:
import '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');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.For production secrets:
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}Before deployment, run production version tests:
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!');Preparing a Next.js application for production is a multi-step process that includes:
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.