| name | bundle-analysis |
| description | Analyze JavaScript bundle size, identify bloat, and implement fixes: code splitting, lazy loading, tree shaking, and dependency swaps. Use when page load is slow, Lighthouse performance score is low, or before launching a web app. |
Bundle Analysis
You are analyzing and optimizing the JavaScript bundle. Work through each phase in order.
Framework: {{args}}
Phase 1: Measure Baseline
Generate a bundle analysis report:
Windows: run these bash snippets through the Bash tool or Git Bash, not PowerShell. The command -v, &&/|| chains, and the inline ANALYZE=true ... prefix below are bash-only. In PowerShell the build step is $env:ANALYZE = "true"; $PM run build.
command -v bun >/dev/null 2>&1 && PM=bun || { command -v pnpm >/dev/null 2>&1 && PM=pnpm || PM=npm; }
Next.js:
$PM add -d @next/bundle-analyzer
ANALYZE=true $PM run build
Vite:
$PM add -d rollup-plugin-visualizer
$PM run build
Other:
$PM add -d webpack-bundle-analyzer
Record baseline metrics:
- Total JS size (gzipped)
- Largest chunks by size
- First Load JS (if Next.js)
- Lighthouse Performance score
Phase 2: Identify Top Offenders
Spawn 2 parallel subagents:
| Subagent | Focus |
|---|
| 1 | Read the bundle analyzer output: which modules are largest? Which appear in multiple chunks when they shouldn't? |
| 2 | package.json dependencies: find packages with cheaper alternatives (e.g., moment → date-fns, lodash → native, axios → fetch) |
Produce a ranked list of optimizations by estimated savings.
Phase 3: Code Splitting
For routes/pages that are not on the critical path:
- Dynamic imports: lazy-load components loaded below the fold or on interaction:
const HeavyChart = lazy(() => import('./HeavyChart'))
- Route-based splitting: verify each route is its own chunk (Next.js does this automatically; Vite needs route lazy imports)
- Vendor chunk splitting: ensure large dependencies (React, charting libs) are split into their own chunks for long-term caching
- Conditional imports: only import heavy polyfills or libs when the browser actually needs them
Phase 4: Dependency Swaps
For each identified heavy dependency with a lighter alternative:
- Confirm the replacement covers all current usage patterns
- Install replacement, remove old package
- Update all import sites
- Run tests: confirm nothing breaks
- Measure the size delta
Common swaps:
moment / dayjs → date-fns (tree-shakeable) or Temporal (native in Node 26+ and most modern browsers as of 2026, but Safari still lacks support — needs the @js-temporal/polyfill for full browser coverage)
lodash → native array/object methods or lodash-es with tree shaking
axios → native fetch
uuid → crypto.randomUUID() (native)
- Full
@mui/material → pick only needed components
Phase 5: Tree Shaking
Verify tree shaking is working:
- Check that all imports from large packages use named imports:
import { specific } from 'lib' not import lib from 'lib'
- Confirm
"sideEffects": false in the package.json of any internal packages
- For barrel files (
index.ts that re-exports everything), consider direct imports instead
- Verify
browserslist target is set: shipping modern JS to modern browsers saves size
Phase 6: Image & Font Optimization (bonus)
While in the performance mindset:
Phase 7: Measure After
Re-run the bundle analyzer and Lighthouse:
Present a before/after comparison table.
Completion Report
- Baseline metrics (size, Lighthouse score)
- Optimizations applied (list with estimated savings each)
- Final metrics (size, Lighthouse score)
- Deferred optimizations (with reason)