Next.js Bundle Analyzer: Optimize Your Build Size
Bundle size directly impacts your Next.js app's loading speed and performance. The @next/bundle-analyzer plugin helps you visualize and optimize what's in your bundle.
What is Bundle Analysis?
A "bundle" is the compiled JavaScript sent to browsers when users visit your site. Analyzing it helps answer:
- What's taking up space? Which libraries/code are largest?
- Why are they included? What imports are pulling them in?
- Can we reduce it? Which dependencies can be optimized?
Without analysis, you're flying blind. With it, you get visual insights into every byte.
Install @next/bundle-analyzer
npm install --save-dev @next/bundle-analyzer
Configuration: next.config.js
Add this to your next.config.js:
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
})
module.exports = withBundleAnalyzer({
// Your other Next.js config
})
Generate Bundle Report
ANALYZE=true npm run build
This generates:
.next/analyze/client.html- Client-side bundle visualization.next/analyze/server.html- Server-side bundle visualization
Open these HTML files in your browser. You'll see an interactive treemap showing what's taking up space.
Understanding the Visualization
The bundle analyzer generates a treemap where:
- Size of box = How much space that library takes
- Color = Different packages/modules
- Hovering = Shows exact size and percentage
Example Output
π¦ client.js (450 KB)
βββ π¦ react (120 KB) 36%
βββ π¦ lodash (85 KB) 20%
βββ π¦ moment.js (45 KB) 13%
βββ π¦ axios (30 KB) 7%
βββ π¦ your code (170 KB) 24%
Interpretation: React is necessary. But 85 KB for Lodash? That might be worth replacing with a lighter alternative.
Common Bundle Problems & Solutions
Problem 1: Unused Dependencies
Sign: You see a package in the bundle but never use it.
Solution: Remove it
npm uninstall unused-package
Problem 2: Large Date Libraries
Sign: Moment.js, date-fns, or day.js taking up lots of space.
moment.js: 67 KB β
Solution: Use lightweight alternative
# Replace moment.js (67 KB)
npm uninstall moment
npm install date-fns # 13 KB - 5x smaller
Problem 3: Duplicate Dependencies
Sign: Same package appears twice with different versions.
lodash: 30 KB
lodash-es: 32 KB (duplicate!)
Solution: Use npm dedup
npm dedup
Problem 4: Heavy State Management Libraries
Sign: Redux or other state libraries taking up significant space.
redux: 45 KB
react-redux: 35 KB
Solution: Consider lightweight alternatives
# Instead of Redux
npm install zustand # 2.8 KB
# or
npm install jotai # 4.3 KB
Problem 5: Unnecessary Polyfills
Sign: Old polyfills included that modern browsers don't need.
Solution: Target modern browsers in next.config.js
module.exports = {
target: 'es2020', // Modern browsers only
}
Real Example: E-commerce Site
Before Optimization:
Total: 520 KB
βββ moment.js: 67 KB
βββ lodash: 45 KB
βββ jQuery: 30 KB (unused!)
βββ old polyfills: 25 KB
βββ actual code: 353 KB
After Optimization:
Total: 280 KB (46% reduction!)
βββ date-fns: 13 KB (replaced moment)
βββ ramda: 24 KB (replaced lodash)
βββ (removed jQuery)
βββ (removed polyfills)
βββ actual code: 243 KB
Result:
- 240 KB smaller
- ~500ms faster initial load
- Better Core Web Vitals scores
Next.js Specific: Code Splitting
Next.js automatically code-splits routes, but you can optimize further:
Automatic Route Splitting
pages/
βββ index.js (homepage)
βββ about.js (separate bundle)
βββ products.js (separate bundle)
Each page gets its own bundle. Users only download the code they need.
Dynamic Imports
// Regular import (included in all pages)
import HeavyComponent from './HeavyComponent'
// Dynamic import (only when needed)
const HeavyComponent = dynamic(() => import('./HeavyComponent'))
For components only needed on specific pages, use dynamic imports:
import dynamic from 'next/dynamic'
const AdminPanel = dynamic(() => import('../components/AdminPanel'), {
loading: () => <div>Loading...</div>,
})
export default function Dashboard() {
return (
<div>
<h1>Dashboard</h1>
<AdminPanel /> {/* Only loaded when needed */}
</div>
)
}
External Script Optimization
For third-party scripts (analytics, ads), use next/script:
import Script from 'next/script'
export default function Home() {
return (
<>
<Script src="https://cdn.example.com/analytics.js" strategy="afterInteractive" />
<h1>Welcome</h1>
</>
)
}
Strategies:
beforeInteractive: Load immediately (critical)afterInteractive: Load after page is interactive (normal)lazyOnload: Load when page scrolls (non-essential)
Practical Optimization Checklist
β Run: ANALYZE=true npm run build
β Identify largest packages
β Replace heavy libraries with lighter alternatives
β Remove unused dependencies (npm uninstall)
β Use dynamic imports for large components
β Audit third-party scripts (analytics, ads)
β Enable gzip compression
β Set appropriate compression level
β Monitor bundle size in CI/CD
CI/CD Integration: Monitor Bundle Size
Add to your GitHub Actions or similar:
name: Bundle Size Check
on: [pull_request]
jobs:
analyze:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
- run: npm install
- run: ANALYZE=true npm run build
- name: Upload bundle analysis
uses: actions/upload-artifact@v2
with:
name: bundle-analysis
path: .next/analyze
Advanced: Custom Bundle Size Limits
Using bundlesize CLI:
npm install --save-dev bundlesize
Create .bundlesizerc.json:
{
"files": [
{
"path": ".next/static/chunks/main.js",
"maxSize": "250kb"
},
{
"path": ".next/static/chunks/pages/_app.js",
"maxSize": "200kb"
}
]
}
Fail builds if bundle exceeds limits:
bundlesize
Performance Impact of Bundle Size
Bundle Size β Load Time β User Experience
100 KB: ~0.5s (excellent)
200 KB: ~1.0s (good)
300 KB: ~1.5s (acceptable)
500 KB: ~2.5s (slow)
1000 KB: ~5.0s (very slow)
On slow networks, every 100 KB adds ~0.5-1 second.
Best Practices
- Analyze regularly - Run analysis on every major change
- Set budget limits - Use
bundlesizeto enforce limits - Code split aggressively - Use dynamic imports for large components
- Audit dependencies - Review what each package contributes
- Prefer tree-shakeable libraries - Choose ES modules over CommonJS
- Use compression - Enable gzip (Next.js does by default)
- Monitor in CI/CD - Catch bundle bloat before merging
Real-World Optimization Story
Project: Content management site with 50+ admin pages
Initial Bundle:
- Total: 1.2 MB (way too big!)
- React Table: 150 KB (used on 3 pages only)
- Draft.js: 200 KB (used on 1 page only)
- moment.js: 67 KB
Optimization Strategy:
- Dynamic import React Table only on admin pages
- Move Draft.js to dynamic import
- Replace moment with date-fns
- Remove unused icon library
Results:
- Main bundle: 1.2 MB β 450 KB (62% reduction!)
- Admin pages still load fast with dynamic chunks
- Lighthouse score: 45 β 82
Conclusion
Bundle analysis isn't optionalβit's essential for performance:
- Identify bloat with
@next/bundle-analyzer - Replace heavy packages with lighter alternatives
- Code split with dynamic imports
- Monitor with CI/CD integration
- Optimize continuously
Even small bundle reductions compound: 50 KB saved = ~200-500ms faster load time.