We use cookies to enhance your experience on the site
CodeWorlds

Preparing the application for production (build, analyze)

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

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.

Go to CodeWorlds