Production builds must be fast, small, and efficient. Optimize your Next.js app with bundle analysis, code splitting, lazy loading, tree shaking, and image optimization. Reduce bundle sizes, improve load times, and deliver the best possible user experience. Master production optimization and build lightning-fast applications that scale!
Bundle Analysis
Installing Bundle Analyzer
# Install @next/bundle-analyzer
npm install @next/bundle-analyzer
# or
yarn add @next/bundle-analyzer
pnpm add @next/bundle-analyzer
# ✅ Visualize bundle sizes
# ✅ Identify large dependencies
# ✅ Find optimization opportunitiesConfiguring Bundle Analyzer
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
});
/** @type {import('next').NextConfig} */
const nextConfig = {
// Your existing config
reactStrictMode: true,
images: {
domains: ['example.com'],
},
};
module.exports = withBundleAnalyzer(nextConfig);
// ✅ Only runs when ANALYZE=true
// ✅ Opens browser with visualization
// ✅ Shows client and server bundlesRunning Bundle Analysis
# Analyze production build
ANALYZE=true npm run build
# Or add to package.json scripts:
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"analyze": "ANALYZE=true next build"
}
}
# Then run:
npm run analyze
# ✅ Opens browser with bundle visualization
# ✅ See all dependencies and sizes
# ✅ Identify large packagesUnderstanding Bundle Analysis
What to Look For:
- Large dependencies: Libraries taking up significant space
- Duplicate code: Same library imported multiple times
- Unused exports: Importing entire libraries when only using parts
- Client vs Server: Large packages only needed on server
Code Splitting and Lazy Loading
Dynamic Import for Components
import dynamic from 'next/dynamic';
// Lazy load component (only when rendered)
const HeavyChart = dynamic(() => import('@/components/HeavyChart'), {
loading: () => <p>Loading chart...</p>,
ssr: false, // Optional: disable server rendering
});
export default function DashboardPage() {
return (
<div>
<h1>Dashboard</h1>
{/* Chart only loads when component renders */}
<HeavyChart data={chartData} />
</div>
);
}
// ✅ Separate bundle chunk for HeavyChart
// ✅ Only loads when needed
// ✅ Reduces initial bundle size
// ✅ Shows loading stateNamed Exports with Dynamic Import
import dynamic from 'next/dynamic';
// Import named export
const ComplexForm = dynamic(
() => import('@/components/Forms').then(mod => mod.ComplexForm),
{
loading: () => <div>Loading form...</div>,
}
);
export default function Page() {
return (
<div>
<ComplexForm />
</div>
);
}
// ✅ Works with named exports
// ✅ Use .then(mod => mod.ExportName)Client-Only Components
import dynamic from 'next/dynamic';
// Components that only work in browser
const BrowserOnlyComponent = dynamic(
() => import('@/components/BrowserOnly'),
{
ssr: false, // Don't render on server
}
);
const MapComponent = dynamic(
() => import('@/components/Map'),
{
ssr: false, // Maps often need window/document
loading: () => <div className="h-96 bg-gray-200">Loading map...</div>,
}
);
export default function Page() {
return (
<div>
<h1>Location</h1>
<MapComponent />
<BrowserOnlyComponent />
</div>
);
}
// ✅ Prevents SSR errors
// ✅ Components only load in browser
// ✅ Useful for browser-only APIsConditional Loading
'use client';
import { useState } from 'react';
import dynamic from 'next/dynamic';
// Only load when modal opens
const HeavyModal = dynamic(() => import('@/components/HeavyModal'));
export default function Page() {
const [showModal, setShowModal] = useState(false);
return (
<div>
<button onClick={() => setShowModal(true)}>
Open Modal
</button>
{/* Modal only loads when showModal is true */}
{showModal && (
<HeavyModal onClose={() => setShowModal(false)} />
)}
</div>
);
}
// ✅ Modal bundle only loads when opened
// ✅ Reduces initial page load
// ✅ Better performanceRoute-Level Code Splitting
// Next.js automatically code splits by route!
// app/dashboard/page.tsx
export default function DashboardPage() {
return <div>Dashboard</div>;
}
// app/settings/page.tsx
export default function SettingsPage() {
return <div>Settings</div>;
}
// app/profile/page.tsx
export default function ProfilePage() {
return <div>Profile</div>;
}
// Each page is a separate bundle:
// - /dashboard loads only dashboard code
// - /settings loads only settings code
// - /profile loads only profile code
// ✅ Automatic code splitting
// ✅ No configuration needed
// ✅ Each route is separate bundleTree Shaking and Import Optimization
Named Imports vs Default Imports
// ✅ GOOD: Named imports (tree-shakeable)
import { Button, Card } from '@/components/ui';
import { format, parseISO } from 'date-fns';
import { debounce } from 'lodash-es'; // ES modules version
// ❌ BAD: Default import of entire library
import _ from 'lodash'; // Imports entire library!
import * as dateFns from 'date-fns'; // Imports everything!
// ✅ GOOD: Specific lodash imports
import debounce from 'lodash/debounce';
import throttle from 'lodash/throttle';
// Named imports allow bundler to remove unused codeImport from Subpaths
// ✅ GOOD: Import from specific paths
import Button from '@mui/material/Button';
import TextField from '@mui/material/TextField';
// ❌ BAD: Import everything
import { Button, TextField } from '@mui/material';
// This might import more than needed
// ✅ GOOD: Specific icon imports
import { FaUser, FaHome } from 'react-icons/fa';
// ❌ BAD: Import all icons
import * as Icons from 'react-icons/fa';
// Import only what you need from subpathsOptimizing Dependencies
// Use lighter alternatives:
// ❌ Heavy: moment.js (300KB+)
import moment from 'moment';
// ✅ Light: date-fns (20KB)
import { format, parseISO } from 'date-fns';
// ❌ Heavy: lodash (70KB)
import _ from 'lodash';
// ✅ Light: lodash-es with named imports (tree-shakeable)
import { debounce, throttle } from 'lodash-es';
// ❌ Heavy: axios (15KB)
import axios from 'axios';
// ✅ Built-in: fetch API (0KB)
fetch('https://api.example.com/data');
// Choose lighter alternatives when possibleBarrel File Optimization
// ✅ GOOD: Re-export with explicit exports
export { Button } from './Button';
export { Card } from './Card';
export { Input } from './Input';
// Allows tree shaking
// ❌ BAD: Export all
export * from './Button';
export * from './Card';
export * from './Input';
// May prevent tree shaking
// Usage:
import { Button, Card } from '@/components/ui';
// ✅ Only Button and Card bundled
// ✅ Input not included if unusedImage Optimization
Using Next.js Image Component
import Image from 'next/image';
export default function Page() {
return (
<div>
{/* Automatic optimization */}
<Image
src="/hero.jpg"
alt="Hero image"
width={1200}
height={600}
priority // Load immediately (above fold)
/>
{/* Lazy loading (default) */}
<Image
src="/product.jpg"
alt="Product"
width={400}
height={400}
/>
{/* Fill container */}
<div className="relative h-96 w-full">
<Image
src="/banner.jpg"
alt="Banner"
fill
className="object-cover"
/>
</div>
</div>
);
}
// ✅ Automatic format optimization (WebP/AVIF)
// ✅ Automatic responsive images
// ✅ Lazy loading by default
// ✅ Prevents layout shift
// ✅ On-demand optimizationRemote Image Configuration
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
// Allow remote images
remotePatterns: [
{
protocol: 'https',
hostname: 'cdn.example.com',
port: '',
pathname: '/images/**',
},
{
protocol: 'https',
hostname: 'images.unsplash.com',
},
],
// Image sizes for responsive images
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
// Format optimization
formats: ['image/webp', 'image/avif'],
},
};
module.exports = nextConfig;
// ✅ Secure remote image sources
// ✅ Responsive sizes configuration
// ✅ Modern format supportStatic Image Imports
import Image from 'next/image';
import heroImage from '@/public/hero.jpg';
export default function Page() {
return (
<div>
{/* Static import - width/height automatic */}
<Image
src={heroImage}
alt="Hero"
placeholder="blur" // Automatic blur-up placeholder
priority
/>
</div>
);
}
// ✅ Automatic width/height
// ✅ Automatic blur placeholder
// ✅ Type-safe imports
// ✅ Build-time optimizationImage Optimization Best Practices
import Image from 'next/image';
export default function GalleryPage() {
return (
<div>
{/* Above fold: priority */}
<Image
src="/hero.jpg"
alt="Hero"
width={1200}
height={600}
priority // Loads immediately
quality={90} // High quality for hero
/>
{/* Below fold: lazy load */}
<div className="grid grid-cols-3 gap-4">
{products.map(product => (
<Image
key={product.id}
src={product.image}
alt={product.name}
width={400}
height={400}
// No priority = lazy load
quality={75} // Lower quality for thumbnails
/>
))}
</div>
</div>
);
}
// Best practices:
// ✅ priority for above-the-fold images
// ✅ Lazy load below-the-fold images
// ✅ Lower quality for thumbnails (75)
// ✅ Higher quality for hero images (90)
// ✅ Appropriate sizes per usageOptimization File Structure
Organization for optimized builds
Select a file or folder to see details
Font Optimization
Next.js Font Optimization
import { Inter, Roboto_Mono } from 'next/font/google';
// Load Google Fonts
const inter = Inter({
subsets: ['latin'],
display: 'swap',
variable: '--font-inter',
});
const robotoMono = Roboto_Mono({
subsets: ['latin'],
display: 'swap',
variable: '--font-roboto-mono',
});
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en" className={`${inter.variable} ${robotoMono.variable}`}>
<body className={inter.className}>
{children}
</body>
</html>
);
}
// ✅ Automatic font optimization
// ✅ Self-hosted (no external requests)
// ✅ Zero layout shift
// ✅ Automatic font subsettingLocal Fonts
import localFont from 'next/font/local';
// Load local font files
const customFont = localFont({
src: [
{
path: '../public/fonts/CustomFont-Regular.woff2',
weight: '400',
style: 'normal',
},
{
path: '../public/fonts/CustomFont-Bold.woff2',
weight: '700',
style: 'normal',
},
],
variable: '--font-custom',
display: 'swap',
});
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en" className={customFont.variable}>
<body className={customFont.className}>
{children}
</body>
</html>
);
}
// ✅ Load custom local fonts
// ✅ Multiple weights/styles
// ✅ Optimized loading
// ✅ No FOUT (Flash of Unstyled Text)Variable Fonts
import { Inter } from 'next/font/google';
// Variable font with all weights
const inter = Inter({
subsets: ['latin'],
variable: '--font-inter',
// Variable fonts include all weights in one file
});
// Usage in Tailwind CSS:
// tailwind.config.js
module.exports = {
theme: {
extend: {
fontFamily: {
sans: ['var(--font-inter)'],
},
},
},
};
// In components:
<h1 className="font-sans font-bold">Bold text</h1>
<p className="font-sans font-light">Light text</p>
// ✅ Single file, all weights
// ✅ Smaller total size
// ✅ Smooth weight transitionsBuild Configuration
Production next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
// React strict mode
reactStrictMode: true,
// Compress output
compress: true,
// Power by header
poweredByHeader: false, // Security: hide Next.js version
// Generate ETags
generateEtags: true,
// Image optimization
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'cdn.example.com',
},
],
formats: ['image/webp', 'image/avif'],
},
// Experimental features
experimental: {
// Optimize package imports
optimizePackageImports: ['@mui/material', 'lodash-es'],
},
// Webpack configuration
webpack: (config, { isServer }) => {
// Custom webpack config
if (!isServer) {
// Client-only config
}
return config;
},
};
module.exports = nextConfig;
// ✅ Production-ready configuration
// ✅ Security headers
// ✅ Compression enabled
// ✅ Image optimizationEnvironment Variables
# Production environment variables
NODE_ENV=production
# API URLs
NEXT_PUBLIC_API_URL=https://api.production.com
API_SECRET_KEY=prod-secret-key
# Database
DATABASE_URL=postgresql://prod-server/db
# Analytics
NEXT_PUBLIC_GA_ID=UA-PROD-ID
# Feature flags
ENABLE_ANALYTICS=true
ENABLE_MAINTENANCE_MODE=false
# ✅ Production-specific values
# ✅ Secure secrets
# ✅ Production API endpointsOutput Configuration
/** @type {import('next').NextConfig} */
const nextConfig = {
// Output mode
output: 'standalone', // For Docker/containerized deployments
// output: 'export', // For static export (no server)
// Standalone output settings
experimental: {
outputFileTracingRoot: undefined, // Trace dependencies
},
// Trailing slash
trailingSlash: false, // /about instead of /about/
// Redirects
async redirects() {
return [
{
source: '/old-page',
destination: '/new-page',
permanent: true, // 308 redirect
},
];
},
// Headers
async headers() {
return [
{
source: '/:path*',
headers: [
{
key: 'X-DNS-Prefetch-Control',
value: 'on',
},
{
key: 'X-Frame-Options',
value: 'SAMEORIGIN',
},
],
},
];
},
};
module.exports = nextConfig;
// ✅ Deployment-specific output
// ✅ Security headers
// ✅ SEO-friendly URLsBuild Process Optimization
Production Build Command
# Standard production build
npm run build
# Build with analysis
ANALYZE=true npm run build
# Build output:
# - .next/static/ - Static assets (JS, CSS)
# - .next/server/ - Server-side code
# - .next/cache/ - Build cache
# - public/ - Public static files
# ✅ Optimized bundles
# ✅ Minified code
# ✅ Tree-shaken outputBuild Performance Tips
# Use build cache (enabled by default)
# Cache stored in .next/cache/
# Clean cache if needed
rm -rf .next/cache
# Parallel builds (automatic in Next.js 13+)
# Next.js builds pages in parallel
# Limit static generation in development
# Use generateStaticParams conditionally:
export async function generateStaticParams() {
if (process.env.NODE_ENV === 'development') {
return [{ id: '1' }]; // Only generate one page
}
const items = await fetchAll();
return items.map(item => ({ id: item.id }));
}
# ✅ Faster dev builds
# ✅ Complete prod buildsCI/CD Build Optimization
name: Build and Deploy
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
# Cache dependencies
- name: Cache dependencies
uses: actions/cache@v3
with:
path: |
~/.npm
node_modules
.next/cache
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
env:
NODE_ENV: production
- name: Deploy
run: npm run deploy
# ✅ Cache npm packages
# ✅ Cache Next.js build
# ✅ Faster CI/CD buildsProduction Optimization Checklist
✅ Pre-Deployment Checklist
- Bundle Analysis: Run bundle analyzer, check for large dependencies
- Code Splitting: Use dynamic imports for heavy components
- Image Optimization: Use Next.js Image component, optimize formats
- Font Optimization: Use next/font for automatic optimization
- Tree Shaking: Use named imports, avoid importing entire libraries
- Environment Variables: Set production values, secure secrets
- Static Generation: Use generateStaticParams for known routes
- Caching: Configure appropriate cache times (revalidate)
- Security Headers: Add CSP, X-Frame-Options, etc.
- Compression: Enable gzip/brotli compression
Performance Budget
/** @type {import('next').NextConfig} */
const nextConfig = {
// Set performance budgets
experimental: {
// Warn on large client bundles
optimizePackageImports: ['@mui/material', 'lodash-es'],
},
webpack: (config, { isServer }) => {
if (!isServer) {
// Warn on large bundles
config.performance = {
maxAssetSize: 244000, // 244KB
maxEntrypointSize: 244000,
hints: 'warning',
};
}
return config;
},
};
module.exports = nextConfig;
// ✅ Set size limits
// ✅ Get warnings on large bundles
// ✅ Maintain performance standardsKey Takeaways
- Bundle analysis - visualize and optimize bundle sizes
- Code splitting - dynamic imports for lazy loading
- Tree shaking - named imports to remove unused code
- Image optimization - Next.js Image component for automatic optimization
- Font optimization - next/font for self-hosted fonts
- Build configuration - optimize next.config.js
- Static generation - pre-generate known routes
- Production checklist - verify all optimizations before deploy
What's Next?
You've mastered build optimization! Next, we'll explore Deploying to Vercel—the easiest and most optimized way to deploy Next.js applications. Learn automatic deployments, environment variables, preview deployments, custom domains, and production best practices. You'll deploy your optimized app to production!
We'll cover Vercel deployment, CI/CD, environment configuration, and production monitoring.
⚡ Optimization Priority
Focus on high-impact optimizations first: bundle analysis (find problems), code splitting (reduce initial load), image optimization (largest assets), and tree shaking (remove unused code). Use the bundle analyzer to identify the biggest wins, then optimize systematically!