Next.js Bundle Analyzer (project-scoped)
Analyze Turbopack bundle output from next experimental-analyze --output for this repository.
Workflow
Step 1: Generate analyze output
Use the pre-defined pnpm script (see package.json):
pnpm analyze:output
This runs next experimental-analyze --output and writes results to .next/diagnostics/analyze/.
Note: Next.js 16 is a breaking-change release. If the command is missing or the output path changes, consult
node_modules/next/dist/docs/before adjusting.
Step 2: Run the analysis script
uv run .claude/skills/next-bundle-analyzer/scripts/analyze_bundle.py .next/diagnostics/analyze
The script has PEP 723 inline metadata (requires-python = ">=3.11"), so uv provisions its own interpreter automatically — no venv needed.
Step 3: Interpret results
| Section | What to look for |
|---|---|
| Total bundle size | Compressed size matters for network; raw size for parse time |
| Top output files | Files >100KB (raw) are candidates for investigation |
| Top npm packages | User-land packages unexpectedly large (not next/react) |
| Duplicate packages | Same package at multiple versions = wasted bytes |
| Recommendations | Auto-detected optimization opportunities |
The script is pnpm-aware and correctly attributes packages from node_modules/.pnpm/<name>@<version>/.
Output file markers (in "TOP OUTPUT FILES" listing)
[client-fs]— shipped to the browser. Focus optimization here.[output]— server-only artifacts (SSR / RSC runtime).[pkg]— internalnext/dist/compiled/*. A gzip size of2 Bis a marker that the file is not a shipped compressed chunk; usually framework overhead, not actionable.
When the ">100KB candidate" rule from the table above triggers on [pkg] / [output] entries, it's almost always framework weight. Real wins come from trimming [client-fs] entries.
When "Recommendations" is empty
The script's auto-recommendations only fire on user-land packages crossing a size threshold. Empty = no obviously large user-land package. This is not a signal that nothing can be optimized — cross-reference package.json and manually flag icon / UI / utility libraries (e.g. lucide-react, @base-ui/react) that may still benefit from optimizePackageImports.
Step 4: Apply optimizations
Reference: node_modules/next/dist/docs/01-app/02-guides/package-bundling.md (read this before editing config to confirm API names for Next.js 16).
Large packages with many named exports → Add to optimizePackageImports in next.config.ts:
experimental: {
optimizePackageImports: ['icon-library', 'utility-library'],
}
Candidates in this project are typically: lucide-react, @base-ui/react, or similar icon/utility-heavy packages.
Heavy client-side libraries that could run server-side → Move logic to a Server Component (e.g. markdown parsing, date formatting, syntax highlighting).
Duplicate packages → Run pnpm dedupe.
Server-only packages leaking into client → Add to serverExternalPackages in next.config.ts:
serverExternalPackages: ['package-name'],
Comparing before/after
# Save current state
cp -r .next/diagnostics/analyze ./analyze-before
# Make changes, then re-analyze
pnpm analyze:output
# Compare
uv run .claude/skills/next-bundle-analyzer/scripts/analyze_bundle.py ./analyze-before
uv run .claude/skills/next-bundle-analyzer/scripts/analyze_bundle.py .next/diagnostics/analyze
Key diff signals to compare:
- Total compressed increased → user-facing network cost went up.
- Top npm packages gained an unexpected entry → a refactor pulled a new heavy dep into the client bundle.
- Duplicate packages went from none to some → run
pnpm dedupe. - A new
[client-fs]output file crossed 100KB → candidate for code-splitting oroptimizePackageImports.
Interactive mode (visual treemap)
For browser-based exploration (no --output flag):
pnpm analyze
# Opens http://localhost:4000