Bundle Analysis¶
Navigation: Home > Development > Bundle Analysis
Overview¶
Bundle analysis helps you understand the size and composition of your application's JavaScript bundles. This guide covers how to analyze, optimize, and monitor bundle size in the Simon Stijnen Portfolio project using Next.js Bundle Analyzer.
Why Bundle Size Matters¶
Performance Impact¶
Large bundles lead to:
- Slower page loads
- Poor user experience on slow networks
- Higher data costs for users
- Lower search engine rankings
- Reduced conversion rates
Research findings:
- 1-second delay = 7% reduction in conversions
- 53% of mobile users abandon sites that take > 3 seconds to load
- Google uses page speed as a ranking factor
Core Web Vitals¶
Bundle size directly affects Core Web Vitals:
- LCP (Largest Contentful Paint) - How quickly main content loads
- FID (First Input Delay) - How quickly page responds to interactions
- CLS (Cumulative Layout Shift) - Visual stability during loading
Target metrics:
- LCP < 2.5 seconds
- FID < 100 milliseconds
- CLS < 0.1
Bundle Analyzer Setup¶
Installation¶
The project already includes @next/bundle-analyzer:
If not installed:
Configuration¶
File: next.config.ts
import bundleAnalyzer from "@next/bundle-analyzer";
const withBundleAnalyzer = bundleAnalyzer({
enabled: process.env.ANALYZE === "true",
});
export default withBundleAnalyzer(nextConfig);
How it works:
- Only runs when
ANALYZE=trueenvironment variable is set - Wraps Next.js config with analyzer
- Generates visual reports after build
NPM Script¶
File: package.json
Running Bundle Analysis¶
Basic Usage¶
What happens:
- Sets
ANALYZE=trueenvironment variable - Builds production version of app
- Generates bundle analysis reports
- Opens reports in browser automatically
Terminal output:
▲ Next.js 15.5.9
Creating an optimized production build ...
✓ Compiled successfully
✓ Linting and checking validity of types
✓ Collecting page data
✓ Generating static pages (7/7)
✓ Finalizing page optimization
Route (app) Size First Load JS
┌ ○ / 142 B 87.2 kB
├ ○ /_not-found 871 B 81.9 kB
├ ○ /about 142 B 87.2 kB
├ ○ /achievements 142 B 87.2 kB
├ ○ /contact 142 B 87.2 kB
├ ƒ /projects/[slug] 142 B 87.2 kB
└ ○ /skills 142 B 87.2 kB
Webpack Bundle Analyzer saved reports to:
.next/analyze/client.html
.next/analyze/server.html
Browser automatically opens:
- Client bundle: http://127.0.0.1:8888
- Server bundle: http://127.0.0.1:8889
Manual Report Access¶
If browser doesn't open automatically:
# macOS
open .next/analyze/client.html
open .next/analyze/server.html
# Linux
xdg-open .next/analyze/client.html
xdg-open .next/analyze/server.html
# Windows
start .next/analyze/client.html
start .next/analyze/server.html
Understanding the Report¶
Visual Layout¶
The bundle analyzer shows a tree map where:
- Each rectangle represents a module
- Size of rectangle = size of module
- Color = different package or chunk
- Hover for details (file path, size)
Example visualization:
┌─────────────────────────────────────────────────────┐
│ node_modules │
│ ┌─────────────┐ ┌──────┐ ┌────────┐ │
│ │ react-dom │ │ react│ │ next │ │
│ │ 130 KB │ │ 85 KB│ │ 180 KB │ │
│ └─────────────┘ └──────┘ └────────┘ │
│ │
│ ┌────────┐ ┌─────────┐ │
│ │ lucide │ │ @radix │ │
│ │ 45 KB │ │ 90 KB │ │
│ └────────┘ └─────────┘ │
└─────────────────────────────────────────────────────┘
Size Metrics¶
Three size metrics displayed:
- Stat Size - Size of original source code
- Parsed Size - Size after minification (before gzip)
- Gzipped Size - Size after compression (what users download)
Example:
Focus on gzipped size - that's what users actually download.
Client vs Server Bundles¶
Client bundle (.next/analyze/client.html)
- JavaScript sent to browser
- Includes React, components, libraries
- Critical for page load performance
- Optimize this first
Server bundle (.next/analyze/server.html)
- Code running on server
- Server Components, API routes, middleware
- Doesn't affect client performance
- Still important for server costs
Analyzing Results¶
Identifying Large Dependencies¶
Look for:
- Unexpectedly large packages
- Duplicate dependencies
- Unused dependencies
Finding Optimization Opportunities¶
Questions to ask:
- Can this be code-split?
- Is this needed on first load?
-
Can it be loaded dynamically?
-
Can this be tree-shaken?
- Am I importing only what I need?
-
Does the package support tree shaking?
-
Is there a smaller alternative?
- date-fns vs day.js
- lodash vs lodash-es
-
moment.js vs date-fns
-
Can this be removed?
- Unused dependencies
- Development-only code in production
- Commented-out code
Optimization Strategies¶
1. Code Splitting¶
Dynamic imports for heavy components:
// ❌ Before: Loads immediately
import { HeavyChart } from "@/components/heavy-chart";
export default function Dashboard() {
return <HeavyChart data={data} />;
}
// ✅ After: Loads only when needed
import dynamic from "next/dynamic";
const HeavyChart = dynamic(() => import("@/components/heavy-chart"), {
loading: () => <div>Loading chart...</div>,
});
export default function Dashboard() {
return <HeavyChart data={data} />;
}
Savings: Move 50-100 KB from initial bundle to lazy-loaded chunk
Route-based code splitting:
Next.js automatically code-splits by route:
pages/
├── index.tsx → bundles/page-index.js (30 KB)
├── about.tsx → bundles/page-about.js (15 KB)
└── projects/
└── [slug].tsx → bundles/page-project.js (25 KB)
Each page only loads its own JavaScript.
2. Tree Shaking¶
Import only what you need:
// ❌ Before: Imports entire library (100 KB)
import _ from "lodash";
const result = _.groupBy(items, "category");
// ✅ After: Imports single function (5 KB)
import { groupBy } from "lodash-es";
const result = groupBy(items, "category");
// ✅ Even better: Native JavaScript
const result = items.reduce((acc, item) => {
acc[item.category] = acc[item.category] || [];
acc[item.category].push(item);
return acc;
}, {});
Lucide icons example:
// ❌ Before: Could import all icons (200+ KB)
import * as Icons from "lucide-react";
// ✅ After: Import specific icons (2 KB per icon)
import { Home, User, Settings } from "lucide-react";
Radix UI example:
// ❌ Before: Imports entire package
import * as Dialog from "@radix-ui/react-dialog";
// ✅ After: Import specific components
import { Dialog, DialogContent, DialogTrigger } from "@radix-ui/react-dialog";
3. Replace Large Dependencies¶
Common replacements:
| Large Library | Size | Smaller Alternative | Size | Savings |
|---|---|---|---|---|
| moment.js | 288 KB | date-fns | 78 KB | 210 KB |
| lodash | 70 KB | lodash-es | 24 KB | 46 KB |
| axios | 32 KB | native fetch | 0 KB | 32 KB |
| chart.js | 240 KB | recharts | 150 KB | 90 KB |
| material-ui | 350 KB | shadcn/ui | ~50 KB | 300 KB |
| react-icons (all) | 2500 KB | lucide-react | 100 KB | 2400 KB |
Example replacement:
// Before
import moment from "moment";
const formatted = moment(date).format("MMMM DD, YYYY");
// After
import { format } from "date-fns";
const formatted = format(date, "MMMM dd, yyyy");
4. Next.js Optimizations¶
Experimental package imports:
// next.config.ts
const nextConfig = {
experimental: {
optimizePackageImports: ["lucide-react", "@radix-ui/react-icons"],
},
};
What it does:
- Automatically tree-shakes specified packages
- Only includes icons/components actually used
- No manual import optimization needed
SWC minification:
// next.config.ts (already enabled by default)
const nextConfig = {
swcMinify: true, // Faster minification than Terser
};
5. Remove Unused Code¶
Find unused dependencies:
# Install depcheck
npm install -g depcheck
# Run analysis
depcheck
# Output:
Unused dependencies
* unused-package
* old-library
Remove unused imports:
# ESLint finds unused imports
npm run lint
# Output:
app/page.tsx
15:7 warning 'UnusedImport' is defined but never used @typescript-eslint/no-unused-vars
Remove and clean:
# Remove unused package
npm uninstall unused-package
# Remove unused imports in code
# (ESLint auto-fix handles this)
npm run lint -- --fix
6. Compress and Optimize¶
Enable compression (already enabled by default):
Image optimization (already configured):
import Image from "next/image";
// Next.js automatically:
// - Converts to WebP/AVIF
// - Generates responsive sizes
// - Lazy loads images
<Image src="/image.png" alt="Description" width={800} height={600} />;
Monitoring Bundle Size¶
Build Output¶
Every production build shows sizes:
Output:
Route (app) Size First Load JS
┌ ○ / 142 B 87.2 kB
├ ○ /_not-found 871 B 81.9 kB
├ ○ /about 142 B 87.2 kB
├ ○ /achievements 142 B 87.2 kB
├ ○ /contact 142 B 87.2 kB
├ ƒ /projects/[slug] 142 B 87.2 kB
└ ○ /skills 142 B 87.2 kB
○ (Static) prerendered as static content
ƒ (Dynamic) server-rendered on demand
First Load JS shared by all 87.1 kB
├ chunks/framework-[hash].js 42.3 kB
├ chunks/main-[hash].js 31.8 kB
├ chunks/webpack-[hash].js 1.95 kB
└ css/[hash].css 11.0 kB
Key metrics:
- Size - Page-specific code
- First Load JS - Total JavaScript for first page load
- Shared by all - Common code used by all pages
Setting Size Budgets¶
Create size budget file:
// size-budgets.json
{
"budgets": [
{
"path": "**/*",
"maxSize": "100kb",
"maxGzippedSize": "35kb"
},
{
"path": "**/chunks/framework-*.js",
"maxSize": "150kb"
}
]
}
Check against budget:
# After build
npm run build
# Manually compare against budgets
# (Next.js doesn't enforce budgets directly)
CI Integration¶
Add bundle size check to CI:
# .github/workflows/bundle-size.yml
name: Bundle Size
on: [push, pull_request]
jobs:
analyze:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: "20"
- name: Install dependencies
run: npm ci
- name: Build and analyze
run: npm run build:analyze
- name: Upload reports
uses: actions/upload-artifact@v3
with:
name: bundle-analysis
path: .next/analyze/
- name: Comment bundle size
uses: actions/github-script@v6
with:
script: |
const fs = require('fs');
const buildOutput = fs.readFileSync('.next/build-manifest.json');
// Parse and comment on PR
Third-party Tools¶
Bundle analysis services:
- Bundlephobia - Check npm package sizes
- bundlephobia.com
-
Search for packages before installing
-
Package Phobia - Alternative bundle size checker
- packagephobia.com
-
Shows install size and dependencies
-
Bundle Buddy - Webpack bundle analysis
- bundlebuddy.com
- Upload webpack stats for analysis
Best Practices¶
✅ Do's¶
- Run bundle analysis before major releases
- Monitor bundle size trends over time
- Set and enforce size budgets
- Use dynamic imports for heavy components
- Import only what you need (tree shaking)
- Replace large dependencies with smaller alternatives
- Remove unused dependencies regularly
- Enable all Next.js optimizations
❌ Don'ts¶
- Don't ignore bundle size warnings
- Don't import entire libraries
- Don't add dependencies without checking size
- Don't skip bundle analysis
- Don't assume "it's just a small library"
- Don't ignore duplicate dependencies
- Don't load everything on initial render
Troubleshooting¶
Issue: Analysis Report Not Opening¶
Solution:
# Manually open reports
open .next/analyze/client.html
# Or check if files exist
ls -lh .next/analyze/
Issue: No .next/analyze Directory¶
Solution:
# Ensure ANALYZE=true is set
ANALYZE=true npm run build
# Or use the npm script
npm run build:analyze
Issue: Can't Identify Large Module¶
Solution:
Use the search feature in the bundle analyzer report:
- Open client.html in browser
- Use search box at top
- Search for package name
- View hierarchy and size
Real-World Example¶
Before Optimization¶
Total bundle size: 450 KB (gzipped)
Largest packages:
- react-dom: 130 KB
- moment.js: 288 KB
- lodash: 70 KB
- chart.js: 240 KB
- @material-ui/core: 350 KB
First Load JS: 650 KB
After Optimization¶
Total bundle size: 180 KB (gzipped)
Optimized packages:
- react-dom: 130 KB (no change, essential)
- date-fns: 78 KB (replaced moment.js, -210 KB)
- lodash-es: 24 KB (replaced lodash, -46 KB)
- recharts: 150 KB (replaced chart.js, -90 KB)
- shadcn/ui: 50 KB (replaced Material-UI, -300 KB)
First Load JS: 280 KB
Total savings: 270 KB (60% reduction)
Performance improvements:
- Load time: 4.2s → 1.8s (57% faster)
- Time to Interactive: 5.1s → 2.3s (55% faster)
- Lighthouse score: 72 → 95
See Also¶
- Next.js Config - Configuration for bundle optimization
- Development Workflow - Including bundle analysis in workflow
- NPM Scripts - Build and analyze commands
- Next.js Bundle Analyzer
- Webpack Bundle Analyzer
- Web.dev Bundle Size Guide
Next Steps¶
- Configure CDN for faster asset delivery
Last updated: February 2026