Bundle Analyzer
Analyze a JS/TS production bundle and surface the biggest size wins — heavy dependencies, duplicate packages, missing code-splitting, oversized polyfills, and dev/server code leaking into the client. Use when a bundle is too large and you need a ranked, actionable reduction plan.
Install to ~/.claude/skills/bundle-analyzer/SKILL.md
Inspect a JavaScript/TypeScript production bundle and find where the bytes actually go. The skill builds a stats report, attributes weight to specific modules and packages, and hunts for the patterns that bloat bundles in practice — a 200KB date library imported for one helper, two copies of the same package at different versions, a route that ships eagerly instead of lazily, a polyfill set targeting browsers you dropped years ago, or server-only code that slipped past the client boundary. It returns a ranked list of concrete reductions with the estimated savings of each, so you fix the 80KB win before the 4KB one.
When to use this skill
- A production bundle (or a specific route/chunk) has grown past budget and you need to know exactly what to cut.
- You suspect duplicate packages, a heavyweight dependency, or a barrel import dragging in a whole library.
- A first-load or main chunk is too big and you want to know what should be code-split or deferred.
- You want to confirm dev-only tooling, source maps, or server code is not leaking into the client bundle.
NOTE
Always measure the production build, not dev. Dev bundles include HMR runtime, unminified source, and no tree-shaking, so their sizes are meaningless for this analysis. Compare gzip/brotli transfer sizes, not raw bytes — that is what users actually download.
Instructions
- Locate the build and detect the bundler. Identify the toolchain before doing anything — do not guess. Check
package.jsonscripts and lockfiles fornext,vite,webpack,rollup,esbuild, or@remix-run. Note the package manager (package-lock.json,pnpm-lock.yaml,yarn.lock,bun.lockb) since duplicate-detection commands differ per manager. - Produce a stats report using the project's own analyzer. Match existing config rather than bolting on a new tool:
- Next.js — run the production build and read its per-route First Load JS table; if
@next/bundle-analyzeris wired up, runANALYZE=true npm run build. - Vite/Rollup — use
rollup-plugin-visualizerif present, or build and inspectdist/assets/*sizes. - Webpack — generate
--jsonstats (webpack --profile --json=stats.json, which writes the file directly so console warnings don't corrupt the JSON) and analyze, e.g. withsource-map-explorerover the emitted bundle + map. - If no analyzer is configured, fall back to
npx source-map-explorer 'dist/**/*.js'against the built output and its source maps.
- Next.js — run the production build and read its per-route First Load JS table; if
- Attribute weight to packages, not just files. Map the largest modules back to their npm packages. For each heavyweight dependency, determine whether it is fully used or pulled in by a barrel/side-effect import, and whether a lighter alternative exists (e.g.
date-fns/dayjsovermoment, nativeIntlovernumeral,lodash-eswith named imports overlodash). - Detect duplicates and version skew. Run
npm ls <pkg>/pnpm why <pkg>/yarn why <pkg>on suspect packages to find the same library bundled at multiple versions, and check for both ESM and CJS copies of the same dep. Flag candidates forresolutions/overridesor dedupe. - Find missing code-splitting and oversized polyfills. Look for large modules in the entry/main chunk that are only needed on one route or behind an interaction (charts, editors, markdown renderers, PDF libs) — these belong behind
import()/next/dynamic/React.lazy. Inspect the polyfill/transpile target (browserslist,targetintsconfig/vite/tsup) forcore-jsor regenerator-runtime bloat aimed at browsers you no longer support. - Hunt for leaked dev/server code. Grep the client bundle and imports for things that should never ship: test/mock files,
process.envdebug branches, server-only modules (fs,cryptoserver usage, DB clients, secrets), and dev dependencies imported from app code. In Next.js, confirm Server Component /"use client"boundaries are not dragging server modules into client chunks. - Verify each proposed cut. Do not estimate from intuition alone. Where feasible, apply the change behind the analyzer (or
--dry) and re-run the build to measure the real delta. At minimum, cite the measured pre-change size from the stats report for every finding. - Report a ranked plan. Output findings ordered by estimated gzip savings, each with: the module/package, current size, the specific fix, the expected reduction, and a rough effort/risk rating. Flag anything you could not measure precisely so the user knows what to confirm.
WARNING
Tree-shaking only works on side-effect-free ESM. A default or namespace import from a CJS package (or a package missing "sideEffects": false) pulls in the whole module regardless of what you use — so "import one helper" can still cost the full library. Verify the import shape, not just the import statement.
Examples
A ranked findings report for a Next.js app whose largest route shipped 412 KB of First Load JS:
Bundle analysis — route /dashboard (First Load JS: 412 KB gzip → target 180 KB)
Ranked by estimated gzip savings:
1. moment + moment-timezone .................. 71 KB [HIGH]
Imported in 3 files for formatting only. Replace with date-fns
named imports (tree-shakeable). Est. -64 KB. Effort: M, Risk: low.
2. Duplicate react (18.2.0 + 18.3.1) .......... 44 KB [HIGH]
`npm ls react` shows two copies via an old @charting/core dep.
Add an override to pin a single version + dedupe. Est. -44 KB.
Effort: S, Risk: low.
3. recharts loaded eagerly in entry chunk ..... 38 KB [HIGH]
Only rendered below the fold on /dashboard. Move behind
next/dynamic({ ssr: false }). Est. -38 KB from First Load.
Effort: S, Risk: low.
4. lodash default import (whole library) ...... 24 KB [MED]
`import _ from "lodash"`. Switch to `lodash-es` + named imports
(debounce, groupBy). Est. -21 KB. Effort: S, Risk: low.
5. core-js polyfills for IE11 ................. 19 KB [MED]
browserslist still includes "ie 11". Drop it (no IE traffic in
analytics). Est. -19 KB. Effort: S, Risk: med (confirm targets).
6. server-only `pg` Pool pulled into client ... 12 KB [HIGH]
db/client.ts imported from a "use client" component. Move the
query behind a Server Action / route handler. Est. -12 KB +
removes a secret-leak vector. Effort: M, Risk: med.
Estimated total reduction: ~198 KB gzip (412 → ~214 KB).
Top 3 fixes alone recover 146 KB. Re-run the analyzer after each.Re-run the build after applying the top findings to confirm the measured First Load JS dropped as projected, and re-check npm ls to verify the duplicate is gone.
Related
- Performance EngineerUse this agent to profile and optimize performance — latency, throughput, memory, bundle size. Examples — a slow endpoint, an N+1 query, a heavy render, a large JS bundle.
- Frontend DeveloperUse this agent to build UI — responsive layouts, components, accessibility, and design-system work. Examples — implementing a Figma design, fixing a11y issues, building a reusable component.