Advanced (Updated: 6/2/2026)

Claude Code Bundle Analysis Guide for Vite, Astro, and Next.js

Analyze JS bundles with Claude Code, find duplicate packages, add dynamic imports, and enforce CI bundle budgets.

Claude Code Bundle Analysis Guide for Vite, Astro, and Next.js

JavaScript bundles rarely become huge in one dramatic commit. They usually grow through reasonable changes: a chart for the admin page, a rich text editor, a date helper, an auth SDK, an A/B testing snippet, a map, then a video player. The problem is not that those features are wrong. The problem is that many projects ship them to users who never needed them on the first page view.

Bundle analysis is the practice of opening the production JavaScript and CSS output and asking, “What are we actually sending to the browser?” For beginners, think of it as weighing a suitcase and then checking which items made it heavy. Claude Code can help, but only when the task is framed as measurement, diagnosis, implementation, and verification. A vague prompt like “make the app faster” invites risky edits. A better workflow gives Claude Code the build output, the budget, the pages to protect, and the checks it must run before it is done.

This guide focuses on Vite, Astro, and Next-like JavaScript apps. You will add rollup-plugin-visualizer or source-map-explorer, look for duplicate packages, split heavy modules with dynamic import, create a CI bundle budget, and use Claude Code as a reviewer. For related ClaudeCodeLab reading, pair this with code splitting, tree shaking, and performance optimization.

The Workflow

The numbers you will see most often are raw size, gzip size, and brotli size. Raw size is useful when reading analyzer diagrams. Gzip and brotli are closer to what users receive over the network. Do not optimize one number blindly. A vendor chunk that compresses well can still be expensive to parse and execute.

Use this sequence as the default loop:

flowchart LR
  A["production build"] --> B["visual report"]
  B --> C["duplicate packages"]
  C --> D["replace or dedupe"]
  B --> E["route-level split"]
  D --> F["bundle budget in CI"]
  E --> F
  F --> G["Claude Code review"]

Keep official references open while you work. Vite documents production builds in Building for Production. Astro has an official recipe for analyzing bundle size. Next.js documents Package Bundling. For budget design, web.dev’s Performance budgets 101 is a practical baseline. For the agent workflow itself, use the official Claude Code overview.

Visualize Vite and Astro Bundles

For Vite and many Astro projects, rollup-plugin-visualizer is the fastest first pass. It creates an HTML treemap that shows which modules and packages occupy the production output.

npm install -D rollup-plugin-visualizer
// vite.config.ts
import { defineConfig } from "vite";
import { visualizer } from "rollup-plugin-visualizer";

export default defineConfig({
  plugins: [
    visualizer({
      filename: "dist/bundle-stats.html",
      template: "treemap",
      gzipSize: true,
      brotliSize: true,
      open: false
    })
  ],
  build: {
    sourcemap: true,
    rollupOptions: {
      output: {
        manualChunks: {
          react: ["react", "react-dom"],
          charts: ["recharts"],
          editor: ["@tiptap/react", "@tiptap/starter-kit"]
        }
      }
    }
  }
});

For Astro, put the visualizer under the Vite config:

// astro.config.mjs
import { defineConfig } from "astro/config";
import { visualizer } from "rollup-plugin-visualizer";

export default defineConfig({
  vite: {
    plugins: [
      visualizer({
        filename: "dist/bundle-stats.html",
        template: "treemap",
        gzipSize: true,
        brotliSize: true
      })
    ],
    build: {
      sourcemap: true
    }
  }
});

Run the build and open the report:

npm run build
open dist/bundle-stats.html

On Windows PowerShell, use:

start dist/bundle-stats.html

In the report, look for heavy modules that are not needed on first load: chart libraries, editors, maps, video players, Markdown processors, date libraries, analytics helpers, and admin-only UI.

Read Source Maps

source-map-explorer uses generated JavaScript and source maps to show what ended up in the bundle. It is useful when you want a second opinion after the treemap.

npm install -D source-map-explorer
npm run build
npx source-map-explorer "dist/assets/*.js" --html dist/source-map-report.html

For Next.js, use the official bundle analysis path for your current version, or configure @next/bundle-analyzer when that matches your setup. Do not copy an old webpack-only recipe into a Turbopack project without checking the Next.js docs first.

// next.config.mjs
import bundleAnalyzer from "@next/bundle-analyzer";

const withBundleAnalyzer = bundleAnalyzer({
  enabled: process.env.ANALYZE === "true"
});

export default withBundleAnalyzer({
  reactStrictMode: true
});
npm install -D @next/bundle-analyzer
ANALYZE=true npm run build

PowerShell uses a different environment variable syntax:

$env:ANALYZE="true"; npm run build

Find Duplicate Packages

Large dependencies are obvious in a treemap. Duplicate dependencies are easier to miss. A project can accidentally ship two versions of date-fns, both lodash and lodash-es, or a UI package that brings its own incompatible peer dependency.

Start with package-manager evidence:

npm ls date-fns lodash lodash-es
npm dedupe

For pnpm:

pnpm why date-fns
pnpm dedupe

Then give Claude Code a task that separates evidence from changes:

Analyze this repository's production bundle.
1. Use dist/bundle-stats.html or source-map-explorer output to list heavy dependencies.
2. Use npm ls or pnpm why to identify duplicate packages.
3. Separate candidates into replace, dedupe, dynamic import, and remove.
4. Preserve existing UI, SEO text, CTA links, and analytics events.
5. Make the smallest safe change and run npm run build plus the bundle budget check.

Replacement decisions should be boring and evidence-based:

CausePractical fixCheck before merging
moment ships everywhereUse Intl.DateTimeFormat, date-fns, or a smaller scoped helperTime zones and locales
Whole lodash importImport specific functions or use native APIsESM/CommonJS mixing
Admin-only editorLoad after click or on admin routes onlyLoading and error UI
Chart library on home pageSplit reports or dashboardsResponsive layout
Duplicate package versionsDedupe or align versionsPeer dependency constraints

Split Heavy Code With Dynamic Import

dynamic import loads code only when it is needed. It is not a magic diet for the app; it moves work from initial load to a later interaction. That is exactly what you want for admin reports, rich editors, map widgets, and rarely used modals.

// src/features/reports/ReportsButton.tsx
import { useState } from "react";

export function ReportsButton() {
  const [html, setHtml] = useState<string>("");
  const [loading, setLoading] = useState(false);

  async function handleClick() {
    setLoading(true);
    const { renderRevenueReport } = await import("./renderRevenueReport");
    setHtml(renderRevenueReport([12000, 18400, 9300]));
    setLoading(false);
  }

  return (
    <section>
      <button type="button" onClick={handleClick} disabled={loading}>
        {loading ? "Generating report" : "Show revenue report"}
      </button>
      <output aria-live="polite">{html}</output>
    </section>
  );
}
// src/features/reports/renderRevenueReport.ts
export function renderRevenueReport(values: number[]): string {
  const total = values.reduce((sum, value) => sum + value, 0);
  return `Monthly total: ${new Intl.NumberFormat("en-US").format(total)} USD`;
}

For a Next.js client-only editor, keep SEO-critical copy and conversion CTAs in server-rendered content. Delay only the editor.

// app/admin/EditorSlot.tsx
"use client";

import dynamic from "next/dynamic";

const RichEditor = dynamic(() => import("./RichEditor"), {
  ssr: false,
  loading: () => <p aria-live="polite">Loading editor...</p>
});

export function EditorSlot() {
  return <RichEditor initialMarkdown="# Draft" />;
}

The common failure is using ssr: false on content that needs to be indexed or seen immediately: article text, pricing, product copy, consultation buttons, and purchase links. Keep revenue and search content visible before JavaScript hydration.

Enforce a CI Bundle Budget

A one-time cleanup will decay. Add a budget so the next heavy dependency must be explained in a pull request.

// scripts/check-bundle-budget.mjs
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
import path from "node:path";
import { brotliCompressSync, gzipSync } from "node:zlib";

const targetDir = "dist/assets";
const maxTotalGzip = 220 * 1024;
const maxSingleGzip = 140 * 1024;

function walk(dir) {
  return readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
    const fullPath = path.join(dir, entry.name);
    if (entry.isDirectory()) return walk(fullPath);
    return /\.(js|css)$/.test(entry.name) ? [fullPath] : [];
  });
}

if (!existsSync(targetDir)) {
  console.error(`Missing ${targetDir}. Run npm run build first.`);
  process.exit(1);
}

const rows = walk(targetDir).map((file) => {
  const content = readFileSync(file);
  return {
    file,
    raw: statSync(file).size,
    gzip: gzipSync(content).byteLength,
    brotli: brotliCompressSync(content).byteLength
  };
}).sort((a, b) => b.gzip - a.gzip);

console.table(rows.map((row) => ({
  file: row.file,
  rawKB: (row.raw / 1024).toFixed(1),
  gzipKB: (row.gzip / 1024).toFixed(1),
  brotliKB: (row.brotli / 1024).toFixed(1)
})));

const totalGzip = rows.reduce((sum, row) => sum + row.gzip, 0);
const tooLarge = rows.filter((row) => row.gzip > maxSingleGzip);

if (totalGzip > maxTotalGzip || tooLarge.length > 0) {
  console.error(`Bundle budget failed. total gzip=${totalGzip} bytes`);
  for (const row of tooLarge) {
    console.error(`Large asset: ${row.file} gzip=${row.gzip} bytes`);
  }
  process.exit(1);
}

console.log(`Bundle budget passed. total gzip=${totalGzip} bytes`);
# .github/workflows/bundle-budget.yml
name: Bundle Budget

on:
  pull_request:

jobs:
  bundle-budget:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: npm
      - run: npm ci
      - run: npm run build
      - run: node scripts/check-bundle-budget.mjs
      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: bundle-stats
          path: |
            dist/bundle-stats.html
            dist/source-map-report.html

Do not start with an impossible budget. Measure the current production build, add roughly 10% room, and require an explanation when a PR crosses it. This makes performance a review habit instead of a cleanup event.

Real Use Cases

First, consider a SaaS admin dashboard. The user-facing dashboard needs navigation, filters, and the primary CTA immediately. Admin-only charts, audit logs, CSV export, and billing reports can live in separate chunks. Ask Claude Code to touch only src/features/admin and to keep auth, navigation, and analytics stable.

Second, consider a content or course site. Article body, headings, purchase links, and consultation CTAs should render early. Markdown preview, image cropping, and campaign management can load later. On a monetized media site, this is not only a speed task; it protects ad visibility, affiliate clicks, and consultation leads. Connect this with analytics implementation so you can see whether the change improved the path that matters.

Third, consider a landing page with a map, video, calculator, or diagnostic form. The first viewport should show the offer, proof, pricing, and action. Heavy widgets can load on scroll or click. For media-heavy UI, review the video player guide and the accessibility implementation guide.

Fourth, consider an internal UI package. A top-level import from @company/ui can accidentally evaluate DatePicker, Modal, Chart, icon packs, and theme setup when the page only needs Button. Split package exports and mark CSS or setup files as side effects intentionally.

Failure Cases

The first failure is measuring a development build. Always measure a production build. Vite dev server behavior, hot module replacement, and unminified output are not evidence for a bundle budget.

The second failure is splitting too aggressively. Many tiny chunks can increase requests and delay interaction. Split large, optional, user-action-based features, not every component.

The third failure is leaking source maps by accident. Source maps are useful for analysis and error reporting, but public maps can expose too much implementation detail. Decide the policy with monitoring and security in mind.

The fourth failure is overusing manualChunks. A fixed chunk strategy can help, but it can also become stale as dependencies change. Review whether the manual split is still useful after major package updates.

The fifth failure is asking Claude Code to edit without asking it to verify. Use a review prompt after the implementation:

Review this PR from a bundle analysis perspective.
- Are first-load dependencies still necessary?
- Are any duplicate package versions present?
- Do dynamic imports include loading and error states?
- Did SEO text, pricing, CTAs, and analytics events remain outside delayed chunks?
- Does the bundle budget failure log help developers find the cause?
- Summarize npm run build and the generated report paths.

Monetization Path

Bundle analysis is not just engineering hygiene. Faster initial rendering means article text, product links, free resources, and consultation CTAs become visible sooner. For a site like ClaudeCodeLab, that affects ad revenue, template purchases, and training inquiries.

For individual practice, start with the free cheat sheet and keep the workflow near your terminal. If you want reusable review prompts and implementation templates, use the materials in products. For teams that need Vite, Astro, Next.js, CLAUDE.md, CI budgets, and review rules designed together, the next step is Claude Code training and consultation.

Hands-On Verification

For this article, I checked the sample workflow against a Vite/React-style setup: the visualizer configuration writes an HTML report, the source-map-explorer command expects source maps, and the Node budget script uses built-in fs, path, and zlib modules. The most important practical lesson was not “replace every large package.” It was to separate what must appear on first load from what can wait. Rich editors and charts are excellent candidates. Article body, pricing, purchase links, and consultation CTAs usually are not.

Summary

A reliable Claude Code bundle workflow starts with evidence. Generate a production build, visualize it, inspect duplicates, move optional heavy features behind dynamic import, and enforce the result in CI. Then ask Claude Code for a review that protects SEO content, revenue CTAs, analytics, and user-visible behavior.

Bundle analysis, tree shaking, and code splitting are not separate chores. They are one operating loop for keeping a JavaScript app fast after it starts earning traffic.

#Claude Code #bundle analysis #Webpack #Vite #performance
Free

Free PDF: Claude Code Cheatsheet

Enter your email and download the one-page Claude Code cheatsheet for commands, review habits, and safe workflows.

We handle your data with care and never send spam.

Level up your Claude Code workflow

Start with the free PDF, use Gumroad guides when you need repeatable workflows, and book consultation when rollout or revenue paths need human judgment.

Masa

About the Author

Masa

Engineer focused on practical Claude Code workflows. Runs claudecode-lab.com, a 10-language technical media site.