| name | bundle-analysis |
| description | Analyze and optimize JavaScript bundle size, identify large dependencies, and improve load times. Use when bundles are large, investigating performance issues, or optimizing frontend assets. |
| allowed-tools | Read, Edit, Write, Bash, Grep |
Bundle Analysis Skill
This skill helps you analyze and optimize JavaScript bundle sizes for Next.js and web applications.
When to Use This Skill
- Large bundle sizes (>500KB)
- Slow initial page loads
- High First Contentful Paint (FCP)
- Investigating bundle composition
- Identifying duplicate dependencies
- Optimizing production builds
- Reducing Time to Interactive (TTI)
Bundle Size Goals
- Initial bundle: <200KB (gzipped)
- First Load JS: <300KB total
- Route chunks: <100KB each
- Vendor chunks: <150KB
- Time to Interactive: <3s on 3G
Next.js Bundle Analyzer
Setup
# Install bundle analyzer
pnpm add -D @next/bundle-analyzer
# Or as dev dependency in web app
cd apps/web
pnpm add -D @next/bundle-analyzer
Configuration
// apps/web/next.config.ts
import type { NextConfig } from "next";
import withBundleAnalyzer from "@next/bundle-analyzer";
const bundleAnalyzer = withBundleAnalyzer({
enabled: process.env.ANALYZE === "true",
});
const nextConfig: NextConfig = {
// ... your config
};
export default bundleAnalyzer(nextConfig);
Running Analysis
# Analyze production bundle
cd apps/web
ANALYZE=true pnpm build
# Opens two HTML reports in browser:
# - client.html: Client-side bundle
# - server.html: Server-side bundle
# Analyze specific environment
ANALYZE=true NODE_ENV=production pnpm build
# Save report to file
ANALYZE=true pnpm build > bundle-report.txt
Reading Bundle Reports
Bundle Analyzer Output
Page Size First Load JS
┌ ○ / 5.2 kB 120 kB
├ /_app 0 B 115 kB
├ ○ /404 3.1 kB 118 kB
├ λ /api/cars 0 B 115 kB
├ ○ /blog 8.5 kB 128 kB
├ ○ /blog/[slug] 12.3 kB 132 kB
└ ○ /charts 45.2 kB 165 kB
+ First Load JS shared by all 115 kB
├ chunks/framework-[hash].js 42 kB
├ chunks/main-[hash].js 28 kB
├ chunks/pages/_app-[hash].js 35 kB
└ chunks/webpack-[hash].js 10 kB
○ (Static) automatically rendered as static HTML
λ (Server) server-side renders at runtime
Key Metrics:
- Size: Page-specific JavaScript
- First Load JS: Total JS loaded for initial render
- Shared chunks: Code shared across pages
Interactive Report
Open client.html or server.html in browser:
- Large boxes: Heavy dependencies
- Colors: Different packages
- Hover: See exact sizes
- Click: Drill down into dependencies
Common Issues
1. Large Dependencies
# Find large packages
cd apps/web
pnpm list --depth=0 | sort -k2 -n
# Example output:
# lodash 1.2 MB
# moment 500 KB
# recharts 300 KB
# @heroui/react 250 KB
Solutions:
// ❌ Import entire library
import _ from "lodash";
import moment from "moment";
// ✅ Import only what you need
import debounce from "lodash/debounce";
import groupBy from "lodash/groupBy";
// ✅ Use lighter alternatives
import { format } from "date-fns"; // Instead of moment
import dayjs from "dayjs"; // Smaller than moment
2. Duplicate Dependencies
# Check for duplicates
pnpm list <package-name>
# Example: Multiple versions of react
pnpm list react
# Output:
# @sgcarstrends/web
# ├── react@18.3.0
# └─┬ some-package
# └── react@18.2.0 # Duplicate!
Solutions:
// package.json
{
"pnpm": {
"overrides": {
"react": "18.3.0",
"react-dom": "18.3.0"
}
}
}
3. Missing Code Splitting
// ❌ Import heavy component directly
import Chart from "@/components/Chart";
export default function Page() {
return <Chart data={data} />;
}
// ✅ Dynamic import with code splitting
import dynamic from "next/dynamic";
const Chart = dynamic(() => import("@/components/Chart"), {
loading: () => <div>Loading chart...</div>,
ssr: false, // Client-side only if needed
});
export default function Page() {
return <Chart data={data} />;
}
4. Large JSON/Data Files
// ❌ Import large JSON files
import brands from "@/data/car-brands.json"; // 500KB
// ✅ Load dynamically
export async function getBrands() {
const response = await fetch("/api/brands");
return response.json();
}
// ✅ Or use dynamic import
export async function getBrands() {
const { default: brands } = await import("@/data/car-brands.json");
return brands;
}
Optimization Techniques
1. Tree Shaking
// ✅ Named imports enable tree shaking
import { Button, Card } from "@heroui/react";
// ❌ Default import includes everything
import HeroUI from "@heroui/react";
Verify tree shaking:
// package.json
{
"sideEffects": false // Enable aggressive tree shaking
}
// Or specify side-effect files
{
"sideEffects": ["*.css", "*.scss"]
}
2. Code Splitting by Route
// Next.js automatically code-splits by route
// Each page becomes a separate chunk
// app/page.tsx -> chunk for /
// app/blog/page.tsx -> chunk for /blog
// app/charts/page.tsx -> chunk for /charts
// ✅ Additional manual splitting
const HeavyComponent = dynamic(() => import("./HeavyComponent"));
3. Lazy Loading
// ✅ Load components on interaction
"use client";
import { useState } from "react";
import dynamic from "next/dynamic";
const CommentForm = dynamic(() => import("./CommentForm"));
export default function BlogPost() {
const [showComments, setShowComments] = useState(false);
return (
<div>
<article>{/* post content */}</article>
<button onClick={() => setShowComments(true)}>
Show Comments
</button>
{showComments && <CommentForm />}
</div>
);
}
4. Optimize Dependencies
# Replace heavy packages with lighter alternatives
# ❌ moment (500KB)
pnpm remove moment
# ✅ date-fns (30KB) or dayjs (7KB)
pnpm add date-fns
# ❌ lodash (full library)
# ✅ lodash-es (ESM with tree-shaking)
pnpm add lodash-es
# ❌ axios (for simple requests)
# ✅ native fetch or ky (12KB)
pnpm add ky
5. Image Optimization
// ✅ Use Next.js Image component
import Image from "next/image";
export default function Logo() {
return (
<Image
src="/logo.png"
alt="Logo"
width={200}
height={100}
priority // For above-fold images
quality={75} // Reduce quality if acceptable
/>
);
}
// ✅ Use modern formats
// - WebP: 25-35% smaller than JPEG/PNG
// - AVIF: 50% smaller than JPEG (if supported)
6. Font Optimization
// app/layout.tsx
import { Inter } from "next/font/google";
// ✅ Load only needed weights and subsets
const inter = Inter({
subsets: ["latin"],
weight: ["400", "600", "700"], // Only load what you use
display: "swap",
preload: true,
});
export default function RootLayout({ children }) {
return (
<html lang="en" className={inter.className}>
<body>{children}</body>
</html>
);
}
Advanced Analysis
Webpack Bundle Analyzer
# For custom webpack configs
pnpm add -D webpack-bundle-analyzer
# View detailed dependency tree
ANALYZE=true pnpm build
Source Map Explorer
# Analyze source maps
pnpm add -D source-map-explorer
# Generate production build with source maps
pnpm build
# Analyze
npx source-map-explorer '.next/static/chunks/*.js'
Bundle Buddy
# Visualize bundle relationships
npx bundle-buddy '.next/**/*.js.map'
Monitoring Bundle Size
CI Bundle Size Check
# .github/workflows/bundle-size.yml
name: Bundle Size Check
on:
pull_request:
branches: [main]
jobs:
bundle-size:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v4
with:
node-version: 20
cache: "pnpm"
- name: Install dependencies
run: pnpm install
- name: Build
run: pnpm -F @sgcarstrends/web build
- name: Analyze bundle
run: |
BUNDLE_SIZE=$(du -sh apps/web/.next/static | cut -f1)
echo "Bundle size: $BUNDLE_SIZE"
# Fail if bundle exceeds limit
SIZE_KB=$(du -sk apps/web/.next/static | cut -f1)
if [ $SIZE_KB -gt 500 ]; then
echo "Bundle size exceeds 500KB!"
exit 1
fi
- name: Comment PR
uses: actions/github-script@v6
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: '📦 Bundle size: ${{ env.BUNDLE_SIZE }}'
})
Bundle Size Tracking
# Track bundle size over time
echo "$(date +%Y-%m-%d),$(du -sk apps/web/.next/static | cut -f1)" >> bundle-history.csv
# Plot with gnuplot or spreadsheet
Performance Budgets
next.config.ts
// apps/web/next.config.ts
const nextConfig: NextConfig = {
// Warn if bundles exceed limits
onDemandEntries: {
maxInactiveAge: 25 * 1000,
pagesBufferLength: 2,
},
// Set performance budgets
experimental: {
optimizeCss: true,
optimizePackageImports: ["@heroui/react", "recharts"],
},
};
Lighthouse CI
# .lighthouserc.json
{
"ci": {
"collect": {
"startServerCommand": "pnpm start",
"url": ["http://localhost:3000", "http://localhost:3000/charts"]
},
"assert": {
"assertions": {
"categories:performance": ["error", { "minScore": 0.9 }],
"resource-summary:script:size": ["error", { "maxNumericValue": 300000 }],
"total-byte-weight": ["error", { "maxNumericValue": 500000 }]
}
}
}
}
Best Practices
1. Measure Before Optimizing
# ✅ Always measure first
ANALYZE=true pnpm build
# Identify actual bottlenecks
# Don't prematurely optimize
2. Prioritize Critical Path
// ✅ Load critical resources first
// Above-fold content, essential JS/CSS
// ❌ Don't block initial render
// Defer analytics, chat widgets, etc.
3. Use External CDN
// For heavy third-party libraries
// Load from CDN instead of bundling
// next.config.ts
const nextConfig: NextConfig = {
experimental: {
externalDir: true,
},
};
4. Regular Audits
# ✅ Run bundle analysis regularly
# - Before each release
# - When adding dependencies
# - After major refactors
ANALYZE=true pnpm build
Troubleshooting
Bundle Size Not Reducing
# Issue: Optimizations not working
# Solution: Check production build
# Ensure building for production
NODE_ENV=production pnpm build
# Verify minification
cat .next/static/chunks/main-*.js | head -n 1
# Should be minified (single line)
Analyzer Not Opening
# Issue: Bundle analyzer not showing reports
# Solution: Check environment variable
# Ensure ANALYZE is set
echo $ANALYZE # Should print "true"
# Run explicitly
cd apps/web
ANALYZE=true pnpm build
# Check for errors in build output
Large Unexplained Bundle
# Issue: Bundle larger than expected
# Solution: Investigate with source-map-explorer
pnpm build
npx source-map-explorer '.next/static/chunks/*.js' --html bundle-report.html
# Open bundle-report.html to see breakdown
References
- Next.js Bundle Analyzer: https://www.npmjs.com/package/@next/bundle-analyzer
- Webpack Bundle Analyzer: https://github.com/webpack-contrib/webpack-bundle-analyzer
- Web.dev Bundle Size: https://web.dev/your-first-performance-budget/
- Next.js Optimization: https://nextjs.org/docs/app/building-your-application/optimizing
- Related files:
apps/web/next.config.ts- Next.js configuration- Root CLAUDE.md - Performance guidelines
Best Practices Summary
- Analyze Regularly: Check bundle size before releases
- Code Split: Use dynamic imports for heavy components
- Tree Shake: Use named imports, mark side effects
- Optimize Dependencies: Replace heavy packages with lighter alternatives
- Lazy Load: Defer non-critical resources
- Monitor: Set up CI checks and performance budgets
- Use CDN: Offload heavy third-party libraries
- Image Optimization: Use Next.js Image with WebP/AVIF