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.json | Claude Codeへの渡し方ファイルのパスを指定して読ませる |
| Next.js(Turbopack)のグラフ | 作り方next analyze --output --snapshot before のあと next analyze export | Claude 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-refactorwebpackの場合
stats JSONを出します。statsOptions に source: false を渡すと、モジュールのソースを除いて小さくできます。Claude Codeに読ませるなら、この指定が効きます。
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
変更前を保存する
npx next analyze --output --snapshot beforeを実行し、変更前のグラフを残します。 - 2
1つだけ変更する
plan modeで承認した計画のうち、依存1つ分だけをClaude Codeに実装させます。
- 3
変更後を保存する
npx next analyze --output --snapshot afterを実行します。名前を変えれば、前のスナップショットは上書きされません。 - 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%を足した値から始めるのが無難です。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。