Bundle Analysis
Understanding what's in your JavaScript bundle is crucial for performance. Large bundles slow down page loads and hurt Core Web Vitals.
Why Bundle Size Matters
| Bundle Size | Impact |
|---|---|
| < 100 KB | Excellent - fast on any connection |
| 100-250 KB | Good - acceptable for most users |
| 250-500 KB | Concerning - slow on mobile |
| > 500 KB | Poor - significant performance impact |
These are compressed (gzip) sizes for the main JavaScript bundle.
Using @next/bundle-analyzer
Install and configure:
npm install @next/bundle-analyzer
// next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
})
module.exports = withBundleAnalyzer({
// your config
})
Run analysis:
ANALYZE=true npm run build
This opens an interactive treemap showing:
- Client bundles (what users download)
- Server bundles (runs on server only)
- Shared chunks
Reading the Build Output
Route (app) Size First Load JS
┌ ○ / 5.2 kB 89.1 kB
├ ○ /about 2.1 kB 86.0 kB
├ ● /blog/[slug] 3.4 kB 87.3 kB
└ λ /dashboard 12.8 kB 96.7 kB
+ First Load JS shared by all 83.9 kB
├ chunks/framework-xxx.js 45.2 kB
├ chunks/main-xxx.js 27.1 kB
└ other shared chunks 11.6 kB
- Size: Route-specific code
- First Load JS: Total JS for that route (shared + route-specific)
- Shared: Downloaded once, cached for all routes
Common Bundle Bloat Causes
1. Large Libraries
// ❌ Importing entire library
import _ from 'lodash'
_.isEmpty(obj)
// ✅ Import only what you need
import isEmpty from 'lodash/isEmpty'
isEmpty(obj)
// ✅ Or use native alternatives
Object.keys(obj).length === 0
2. Client Components with Heavy Deps
// ❌ Heavy charting library in client bundle
'use client'
import { Chart } from 'chart.js' // 200KB+
// ✅ Dynamic import with loading state
'use client'
import dynamic from 'next/dynamic'
const Chart = dynamic(() => import('./Chart'), {
loading: () => <ChartSkeleton />,
ssr: false,
})
3. Unused Code
// ❌ Exporting everything from index files
export * from './ComponentA'
export * from './ComponentB'
export * from './ComponentC'
// ✅ Named exports allow tree-shaking
export { ComponentA } from './ComponentA'
Analyzing Specific Routes
Check individual route sizes:
// next.config.js
module.exports = {
experimental: {
// Show detailed chunk info in build output
outputFileTracingIncludes: {
'/api/*': ['./node_modules/**/*.wasm'],
},
},
}
Package Analysis Tools
Check package sizes before installing:
# Check package size
npx cost-of-modules
# Or use bundlephobia.com
# https://bundlephobia.com/package/lodash@4.17.21
| Package | Size (minified + gzip) | Alternative |
|---|---|---|
| moment | 72 KB | date-fns (3 KB per function) |
| lodash | 71 KB | lodash-es (tree-shakeable) |
| chart.js | 200 KB | Lightweight: uPlot (35 KB) |
Optimization Checklist
- Run bundle analyzer after significant changes
- Check for duplicate packages in node_modules
- Use dynamic imports for heavy components
- Prefer Server Components for non-interactive code
- Import specifically from large libraries
- Consider lighter alternatives for heavy packages
Summary
- Use
@next/bundle-analyzerto visualize bundle contents - Client bundles directly impact user experience
- Dynamic imports code-split heavy libraries
- Import specific functions, not entire libraries
- Server Components don't add to client bundle
- Check package sizes before adding dependencies

