Claude Media
Claude Codeでバンドルサイズを削減する — analyzer出力の渡し方と上限の置き方

Claude Codeでバンドルサイズを削減する — analyzer出力の渡し方と上限の置き方

bundle analyzerの出力をClaude Codeに渡し、重い依存の置換と動的importを決める手順。削減後にsize-limitで上限をCIに置き、再膨張を止めるところまで。

バンドルサイズが膨らんだとき、Claude Codeに「軽くして」と頼むだけでは当て推量の置換が増えます。効くのは、bundle analyzerが出した数字を渡し、何をどの順で削るかを決めさせることです。削減後はsize-limitで上限をCIに置けば、再び膨らんだ時点で止まります。

この記事は、analyzerの出力を作る、Claude Codeに渡す、削り方を仕分ける、上限を置く、という順に進めます。例は、Next.jsとwebpack系のプロジェクトを想定しています。

渡す前に、どの出力を使うか決める

analyzerの出力は、人間向けの画面と、機械が読める形式に分かれます。Claude Codeに渡せるのは後者です。

出力作り方Claude Codeへの渡し方
webpackのstats JSON作り方BundleAnalyzerPlugin の generateStatsFile: true、または webpack --profile --json > stats.jsonClaude Codeへの渡し方ファイルのパスを指定して読ませる
Next.js(Turbopack)のグラフ作り方next analyze --output --snapshot before のあと next analyze exportClaude Codeへの渡し方JSON Linesを保存してパスを渡す
size-limitの --why作り方@size-limit/esbuild-why などを入れて実行Claude Codeへの渡し方生成されたHTMLは画面用。数字は別途取る

Next.jsのTurbopack向けBundle Analyzerは、v16.1以降で使えます。next analyze export で保存したスナップショットをJSON Linesで書き出す機能はv16.4以降です。Next.jsの手順書は、この出力を「スクリプトやAIツール向け」と位置づけています。webpack系の人は、stats JSONが同じ役割です。

analyzerの出力を、読める形で作る

Next.js 16.4以降の場合

スナップショットを名前付きで保存し、JSON Linesに書き出します。

npx next analyze --output --snapshot before
npx next analyze export --snapshot before > bundle-graph.jsonl

--output は対話画面を開かず、.next/diagnostics/analyze に解析結果を書きます。ディレクトリごとコピーしておけば、削減前後の比較に使えます。

cp -r .next/diagnostics/analyze ./analyze-before-refactor

webpackの場合

stats JSONを出します。statsOptions に source: false を渡すと、モジュールのソースを除いて小さくできます。Claude Codeに読ませるなら、この指定が効きます。

webpack.config.js
const path = require("path");
const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer");
 
module.exports = {
  plugins: [
    new BundleAnalyzerPlugin({
      analyzerMode: "disabled",
      generateStatsFile: true,
      statsFilename: path.resolve(__dirname, "reports/stats.json"),
      statsOptions: { source: false },
    }),
  ],
};

analyzerMode: "disabled" にすると、レポートの画面は開かず、stats JSONだけが出ます。CIやClaude Codeの実行環境でブラウザを開かせたくないときに向きます。

Claude Codeに渡して、削る順番を決めさせる

パイプではなく、パスで渡す

claude -p は標準入力を読めるので、ログやJSONをパイプで渡せます。ただし、パイプで渡せる量は10MBまでで、超えるとエラーで終了します。stats JSONやグラフのスナップショットはすぐこの上限に近づくので、10MBを超える入力は、ファイルに書いてパスをプロンプトに書く方が安全です。

最初は編集させず、計画だけ出させる

いきなり編集させず、plan modeで始めます。plan modeでは、Claudeがファイルを読んで計画を出すだけで、承認するまで編集しません。起動は claude --permission-mode plan です。

claude --permission-mode plan

依頼文は、入力の場所、見てほしい観点、禁止事項、計測の方法を1つにまとめます。

reports/stats.json にwebpackのstatsがある。
1. サイズの大きいモジュールを上位10件挙げ、どのエントリから入っているか示して
2. それぞれについて「置換」「動的import」「サーバー側へ移動」「そのまま」のどれが妥当か、
   根拠となるimport元のファイルを添えて仕分けて
3. まだコードは変更しない
サイズは parsed ではなく gzip で比べる。

最後の1行を入れる理由は、サイズに種類があるからです。webpack-bundle-analyzerは、stat(変換前の入力サイズ)、parsed(出力サイズ。minify済みならその値)、gzip、brotli、zstdを区別して報告します。既定の表示は parsed です。どれで比べるかを決めずに頼むと、計画の前後で単位がずれます。

1回で1つの依存だけ変えさせる

計画を承認したら、実装は依存ごとに分けます。置換と動的importは、画面の挙動に影響するからです。1つ変えるたびに、同じコマンドでanalyzerを走らせ、数字を比べます。CLAUDE.mdには、次のような測り方を書いておくと、毎回指示しなくて済みます。

## バンドルサイズ
- 測定: `ANALYZE=true npm run build` のあと、reports/stats.json を gzip で比較する
- 1回の変更で触る依存は1つだけ。変更前後のサイズ差をコミットメッセージに書く
- 挙動に影響する変更(動的import、サーバーへの移動)は、`npm test` を通してから報告する

テストの回し方は、VitestとCLAUDE.mdの記事にまとめています。

画面で見る人と、Claude Codeに読ませる人の分担

Next.jsのBundle Analyzerを画面で開くと、ルート、実行環境(クライアントかサーバー)、種類(JavaScript・CSS・JSON)で絞り込めます。モジュールをクリックすると、サイズとimportの連鎖が見え、どこから使われているかをたどれます。

この画面は、人間が「どこを疑うか」を決めるのに向いています。たとえば、トップページのクライアント側JavaScriptだけに絞って上位を見れば、依頼文に「このルートのクライアントだけを対象にする」と書けます。Claude Codeに全ルートを一度に見させるより、対象を絞った方が、提案の根拠となるimport元を確認しやすくなります。

Next.jsの手順書は、AIエージェント向けに next-bundle-optimizer というスキルも紹介しています。スキルを入れて「このサイトの初期表示を調べて」と頼むと、監査だけのモードでは変更せずにレポートを作ります。修正まで頼むこともでき、ルートを指定した調査もできます。インストール手順は次のとおりです。

npx skills add vercel/next.js --skill next-bundle-optimizer

このスキルが何を出力するかは、Next.js側の手順書に細かい記述がありません。自分のプロジェクトで試して、plan modeの仕分けと見比べるのが確実です。

削減前後の比較は、スナップショットを2つ取って行う

「削れたか」の判断は、感覚ではなく同じ手順で取った2つの数字で行います。Next.jsなら次の流れです。

手順

スナップショットで前後を比べる流れ

  1. 1

    変更前を保存する

    npx next analyze --output --snapshot before を実行し、変更前のグラフを残します。

  2. 2

    1つだけ変更する

    plan modeで承認した計画のうち、依存1つ分だけをClaude Codeに実装させます。

  3. 3

    変更後を保存する

    npx next analyze --output --snapshot after を実行します。名前を変えれば、前のスナップショットは上書きされません。

  4. 4

    2つを書き出して渡す

    next analyze export --snapshot before と --snapshot after をそれぞれJSON Linesに保存し、2つのパスを渡して差を要約させます。

webpackなら、stats.jsonを stats-before.json と stats-after.json のように別名で保存します。名前を固定しておけば、Claude Codeに差分の要約を頼むときも、依頼文がそのまま使えます。

削り方は、原因の型で選ぶ

analyzerで大きなモジュールが見つかったとき、対処は原因で変わります。Next.jsの手順書は、典型例を3つ挙げています。

原因対処設定・書き方
アイコンやユーティリティなど、数百のexportを持つパッケージ対処使うモジュールだけ読み込む設定・書き方next.config.js の experimental.optimizePackageImports に追加
シンタックスハイライトやグラフ描画など、見た目のためだけのライブラリがクライアントに入っている対処Server Componentに移す設定・書き方prism-react-rendererをClient Componentで使う例を、shikiをサーバー側で動かす形に置き換える
サーバー側でバンドルされているパッケージ対処バンドル対象から外す設定・書き方serverExternalPackages に追加

optimizePackageImports は、一部のライブラリを自動で最適化するため、リストに入れなくてもよいものがあります。対象の一覧は、Next.jsの設定リファレンスにあります。入れる前にClaude Codeへ確認させると、無駄な設定が増えません。

置換と動的importは、Next.jsに限らない手段です。動的import(import())は、ESモジュールを非同期かつ動的に読み込む構文です。初期バンドルから外れるので、最初の表示では使わない画面や機能に向きます。逆に、最初の画面で必ず使うコードに適用しても、初期サイズは変わりません。読み込みが後ろにずれるだけなので、全体のサイズ削減とは別の話です。

分析の結果は、次のような形にまとめさせると、後で見直しやすくなります。

| モジュール | gzip | 入口 | 判断 | 理由 |
|---|---|---|---|---|
| (例) 日付ライブラリ | 20kB | 商品一覧 | 置換 | 使っているAPIが3つだけ |
| (例) チャート | 90kB | 管理画面 | 動的import | 初期表示で使わない |

表の値は例です。実際の数字は、手元のanalyzerの出力から入れます。

未使用の依存やexportが原因のときは、バンドル解析より先に未使用コードを消す方が早く済みます。手順はClaude Codeでknipを使う記事にあります。

size-limitで、削減後の上限をCIに置く

削って終わりにすると、数か月で元に戻ります。size-limitはJavaScriptの性能予算を扱うツールで、コミットごとにCIで実際のコストを計算し、上限を超えるとエラーにします。

アプリ向けの設定は、次のとおりです。プリセットを入れ、package.json にsize-limitのセクションと size スクリプトを足します。

npm install --save-dev size-limit @size-limit/file
{
  "size-limit": [
    {
      "limit": "35 kB",
      "path": "dist/app-*.js"
    }
  ],
  "scripts": {
    "size": "npm run build && size-limit"
  }
}

limit の決め方は、現在の合計サイズに25%を足した値です。上の 35 kB は、手順書の例です。削減後に測った数字から、自分のプロジェクトの値を決めます。上限をぎりぎりに置くと、小さな変更でも落ちてCIが信用されなくなります。

GitHub Actionsでは、andresz1/size-limit-action を使うと、PRにサイズの変化がコメントされ、上限を超えたPRは拒否されます。

name: 'size'
on:
  pull_request:
    branches:
      - main
jobs:
  size:
    runs-on: ubuntu-latest
    env:
      CI_JOB_NUMBER: 1
    steps:
      - uses: actions/checkout@v4
      - uses: andresz1/size-limit-action@v1
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}

上限を超えたとき、何が増えたかは size-limit --why で調べられます。@size-limit/esbuild-why などを入れると、esbuild-why.html が生成されます。

CIでClaude Codeを動かして、上限超過の原因を調べさせるなら、claude --bare -p が向きます。--bare はフックやスキルの自動検出を省き、どのマシンでも同じ結果になります。bareモードはサブスクリプションのログインを使わないので、ANTHROPIC_API_KEY を環境変数に置く必要があります。

つまずきやすい点

  • 比較する単位がずれる: parsed と gzip を混ぜて比べると、削れていないのに削れたように見えます。依頼文とCLAUDE.mdに、比較する単位を書いておきます。
  • 入力が大きすぎる: stats JSONはすぐ大きくなります。source: false で小さくし、10MBを超えるなら、パイプではなくパスで渡します。
  • 画面だけのレポートを渡している: server モードの画面や esbuild-why.html は人間向けです。Claude Codeに渡すなら、stats JSONやJSON Linesを出します。
  • 動的importで安心する: 初期バンドルは減っても、合計は減りません。上限をCIに置くときは、初期バンドルに当たるファイルを path に指定します。

まとめ

バンドル削減は、analyzerが数字を出し、Claude Codeが仕分けて実装し、size-limitが再膨張を止める分担で回ります。最初の1回は、plan modeで仕分けの表を出させ、サイズを比べる単位と、1回の変更で触る依存の数を決めてから実装に入ると、戻す範囲が小さく済みます。上限は、削減後の実測値に25%を足した値から始めるのが無難です。

参照した一次ソース

この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。

  1. nextjs.org/docs/app/guides/package-bundling
  2. github.com/webpack-contrib/webpack-bundle-analyzer
  3. github.com/ai/size-limit
  4. code.claude.com/docs/en/headless
  5. code.claude.com/docs/en/common-workflows
  6. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import
この記事を共有:XはてブLinkedIn