Bundle Analyzer
Analyzes JavaScript bundle sizes, identifies optimization opportunities, and helps reduce bundle size for faster page loads.
When to Use
- "Analyze my bundle size"
- "Why is my bundle so large?"
- "Optimize webpack bundle"
- "Reduce bundle size"
- "Find large dependencies"
- "Setup bundle analysis"
Instructions
1. Detect Build Tool
Check which bundler is being used:
# Check package.json
grep -E "(webpack|vite|rollup|parcel|esbuild)" package.json
# Check config files
[ -f "webpack.config.js" ] && echo "Webpack"
[ -f "vite.config.js" ] && echo "Vite"
[ -f "rollup.config.js" ] && echo "Rollup"
2. Install Analysis Tool
For Webpack:
npm install --save-dev webpack-bundle-analyzer
For Vite:
npm install --save-dev rollup-plugin-visualizer
For Rollup:
npm install --save-dev rollup-plugin-visualizer
Cross-platform:
npm install --save-dev source-map-explorer
3. Configure Analysis
Webpack
webpack.config.js:
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer')
module.exports = {
// ... other config
plugins: [
new BundleAnalyzerPlugin({
analyzerMode: 'static',
reportFilename: 'bundle-report.html',
openAnalyzer: true,
generateStatsFile: true,
statsFilename: 'bundle-stats.json'
})
]
}
Or for conditional analysis:
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer')
module.exports = {
plugins: [
process.env.ANALYZE && new BundleAnalyzerPlugin()
].filter(Boolean)
}
package.json scripts:
{
"scripts": {
"build": "webpack",
"build:analyze": "ANALYZE=true webpack",
"analyze": "webpack-bundle-analyzer dist/stats.json"
}
}
Vite
vite.config.js:
import { defineConfig } from 'vite'
import { visualizer } from 'rollup-plugin-visualizer'
export default defineConfig({
plugins: [
visualizer({
open: true,
gzipSize: true,
brotliSize: true,
filename: 'dist/stats.html'
})
],
build: {
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
utils: ['lodash', 'date-fns']
}
}
}
}
})
Next.js
next.config.js:
const { ANALYZE } = process.env
module.exports = {
webpack: (config, { isServer }) => {
if (ANALYZE) {
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer')
config.plugins.push(
new BundleAnalyzerPlugin({
analyzerMode: 'static',
reportFilename: isServer
? '../analyze/server.html'
: './analyze/client.html'
})
)
}
return config
}
}
Or use @next/bundle-analyzer:
npm install --save-dev @next/bundle-analyzer
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true'
})
module.exports = withBundleAnalyzer({
// Next.js config
})
package.json:
{
"scripts": {
"analyze": "ANALYZE=true next build"
}
}
Create React App
npm install --save-dev source-map-explorer
package.json:
{
"scripts": {
"analyze": "source-map-explorer 'build/static/js/*.js'"
}
}
4. Analyze Bundle
Run analysis:
npm run build:analyze
# or
npm run analyze
Generate report showing:
- Total bundle size
- Breakdown by module
- Treemap visualization
- Gzipped sizes
- Duplicate dependencies
5. Identify Issues
Common issues to look for:
-
Large Dependencies
- Moment.js (use date-fns or dayjs instead)
- Lodash (use lodash-es or individual functions)
- Full libraries when only using small parts
-
Duplicate Dependencies
- Same package included multiple times
- Different versions of same package
-
Unused Code
- Dead code not tree-shaken
- CSS/JS not actually used
-
Large Images/Assets
- Images not optimized
- SVGs not compressed
-
Development Code in Production
- Console logs
- Dev-only packages
- Source maps in production
6. Suggest Optimizations
Optimization Strategies
1. Code Splitting
// Dynamic imports
const HeavyComponent = lazy(() => import('./HeavyComponent'))
// Route-based splitting
const Dashboard = lazy(() => import('./pages/Dashboard'))
const Settings = lazy(() => import('./pages/Settings'))
// Webpack magic comments
const module = import(
/* webpackChunkName: "my-chunk" */
/* webpackPrefetch: true */
'./module'
)
2. Tree Shaking
// ❌ BAD: Imports entire library
import _ from 'lodash'
// ✅ GOOD: Import only what you need
import debounce from 'lodash/debounce'
import throttle from 'lodash/throttle'
// ✅ BETTER: Use lodash-es for tree-shaking
import { debounce, throttle } from 'lodash-es'
3. Replace Large Libraries
// ❌ BAD: Moment.js (heavy)
import moment from 'moment'
// ✅ GOOD: date-fns (modular)
import { format, parseISO } from 'date-fns'
// ✅ GOOD: dayjs (lightweight)
import dayjs from 'dayjs'
4. Lazy Load Routes (React Router)
import { lazy, Suspense } from 'react'
import { BrowserRouter, Routes, Route } from 'react-router-dom'
const Home = lazy(() => import('./pages/Home'))
const About = lazy(() => import('./pages/About'))
const Dashboard = lazy(() => import('./pages/Dashboard'))
function App() {
return (
<BrowserRouter>
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/dashboard" element={<Dashboard />} />
</Routes>
</Suspense>
</BrowserRouter>
)
}
5. Manual Chunks (Vite/Rollup)
// vite.config.js
export default {
build: {
rollupOptions: {
output: {
manualChunks(id) {
// Vendor chunk for node_modules
if (id.includes('node_modules')) {
if (id.includes('react') || id.includes('react-dom')) {
return 'react-vendor'
}
if (id.includes('@mui')) {
return 'mui-vendor'
}
return 'vendor'
}
}
}
}
}
}
6. Externalize Dependencies (CDN)
// webpack.config.js
module.exports = {
externals: {
react: 'React',
'react-dom': 'ReactDOM',
lodash: '_'
}
}
<!-- index.html -->
<script src="https://cdn.jsdelivr.net/npm/react@18/umd/react.production.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/react-dom@18/umd/react-dom.production.min.js"></script>
7. Optimize Images
// next.config.js
module.exports = {
images: {
formats: ['image/avif', 'image/webp'],
deviceSizes: [640, 750, 828, 1080, 1200, 1920],
}
}
// Use next/image
import Image from 'next/image'
<Image
src="/photo.jpg"
width={500}
height={300}
alt="Photo"
/>
8. Remove Unused CSS
# Install PurgeCSS
npm install --save-dev @fullhuman/postcss-purgecss
// postcss.config.js
module.exports = {
plugins: [
require('@fullhuman/postcss-purgecss')({
content: ['./src/**/*.{js,jsx,ts,tsx}'],
defaultExtractor: content => content.match(/[\w-/:]+(?<!:)/g) || []
})
]
}
9. Compression
// webpack.config.js
const CompressionPlugin = require('compression-webpack-plugin')
module.exports = {
plugins: [
new CompressionPlugin({
algorithm: 'gzip',
test: /\.(js|css|html|svg)$/,
threshold: 10240,
minRatio: 0.8
})
]
}
10. Environment-Specific Code
// webpack.config.js
const webpack = require('webpack')
module.exports = {
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production')
})
]
}
// In code
if (process.env.NODE_ENV !== 'production') {
// This will be removed in production build
console.log('Development mode')
}
7. Size Budgets
webpack.config.js:
module.exports = {
performance: {
maxEntrypointSize: 250000, // 250kb
maxAssetSize: 250000,
hints: 'warning'
}
}
package.json with size-limit:
npm install --save-dev size-limit @size-limit/preset-app
{
"size-limit": [
{
"path": "dist/bundle.js",
"limit": "300 KB"
},
{
"path": "dist/vendor.js",
"limit": "200 KB"
}
]
}
8. CI/CD Integration
GitHub Actions:
name: Bundle Size Check
on: [pull_request]
jobs:
size:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- run: npm ci
- run: npm run build
- name: Check bundle size
uses: andresz1/size-limit-action@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
9. Monitoring
Set up continuous monitoring:
// Report to analytics
if (typeof window !== 'undefined' && window.performance) {
const perfData = window.performance.getEntriesByType('resource')
perfData.forEach(entry => {
if (entry.name.includes('.js')) {
console.log(`${entry.name}: ${(entry.transferSize / 1024).toFixed(2)} KB`)
}
})
}
Lighthouse CI:
# .lighthouserc.js
module.exports = {
ci: {
assert: {
assertions: {
'total-byte-weight': ['error', { maxNumericValue: 1000000 }],
'resource-summary:script:size': ['error', { maxNumericValue: 500000 }]
}
}
}
}
10. Generate Report
Create comprehensive report:
# Bundle Analysis Report
## Summary
- Total bundle size: 450 KB (gzipped: 150 KB)
- Number of chunks: 5
- Largest chunk: vendor.js (200 KB)
## Top 10 Largest Dependencies
1. moment.js - 72 KB (❌ Consider replacing with date-fns)
2. lodash - 65 KB (⚠️ Use lodash-es for tree-shaking)
3. @mui/material - 120 KB (✅ Already code-split)
4. react-dom - 40 KB (✅ Essential)
5. chart.js - 35 KB (⚠️ Lazy load if not on first page)
## Optimization Opportunities
- [ ] Replace moment.js with date-fns (-50 KB)
- [ ] Use lodash-es instead of lodash (-30 KB)
- [ ] Lazy load chart.js (-35 KB)
- [ ] Code split routes (-80 KB initial load)
- [ ] Remove unused CSS (-15 KB)
## Estimated Savings
Total potential reduction: 210 KB (47% smaller)
New bundle size: 240 KB
## Action Items
1. Immediate: Replace moment.js
2. Short-term: Implement route-based code splitting
3. Long-term: Audit and remove unused dependencies
Best Practices
DO:
- Analyze on every major release
- Set size budgets and enforce them
- Use code splitting for routes
- Lazy load below-the-fold content
- Tree-shake effectively
- Monitor bundle size in CI/CD
DON'T:
- Import entire libraries
- Ignore duplicate dependencies
- Skip production optimizations
- Forget to compress assets
- Include dev dependencies in production
- Use large polyfills unnecessarily
Size Targets
General Guidelines:
- Main bundle: < 200 KB (gzipped)
- Vendor bundle: < 300 KB (gzipped)
- Total page weight: < 1 MB
- Time to Interactive: < 3 seconds on 3G
Mobile-first:
- First bundle: < 100 KB (gzipped)
- Critical CSS: < 14 KB (first TCP round)
- Above-fold images: < 200 KB
Analysis Checklist
- [ ] Bundle analysis tool installed
- [ ] Baseline measurements taken
- [ ] Large dependencies identified
- [ ] Duplicate dependencies found
- [ ] Code splitting implemented
- [ ] Tree shaking verified
- [ ] Size budgets set
- [ ] CI/CD checks added
- [ ] Production build optimized
- [ ] Monitoring in place