Next.js 16.3 更新でクライアント JS が 104 KiB 減った理由
対象:
apps/web(Next.js 16.2.11 → 16.3.0、Turbopack production build)調査日: 2026-08-09
きっかけ: PR #748 の Bundle Size report で大幅な減少を検出した
結論
Bundle Size report の減少は、ISR、use cache、Partial Prefetching への移行でアプリコードが減ったためではない。Next.js 16.3に含まれるTurbopackのclient bundle修正がほぼ全量を占める。
主因は次の2点だった。
Next.js 16.2.11では、本来ブラウザに不要な
next/dist/server配下の実装がclient bundleへ混入していた。16.3のbrowser variant分離で解消され、共有JSを約56 KiB削減した16.2.11では同じ19モジュールを並び順だけ変えて持つ24.3 KiBのチャンクが3ファイル生成されていた。16.3のモジュール順序正規化で同一チャンクとして扱われ、余分な2ファイル、計48.6 KiBが消えた
概算すると、CIで観測したTotal unique JSの削減は次の内訳で説明できる。
104.3 KiB減 ≒ server-onlyコード・runtimeの削減 55.7 KiB
+ 重複チャンク2ファイルの削減 48.6 KiB
CIで観測した変化
Bundle Size report は、TURBOPACK_STATS=1で生成したroute-bundle-stats.jsonと実チャンクの非圧縮サイズを比較している。
指標 |
Base (16.2.11) |
PR (16.3.0) |
差分 |
|---|---|---|---|
全ルート共通JS |
547.1 KiB |
490.3 KiB |
-56.8 KiB (-10.4%) |
全ルートのユニークJS合計 |
649.1 KiB |
544.7 KiB |
-104.3 KiB (-16.1%) |
ルート固有JSは、全ルート共通チャンクを除いた残りである。
ルート |
Base |
PR |
差分 |
|---|---|---|---|
|
56.9 KiB |
32.5 KiB |
-24.4 KiB (-42.8%) |
|
33.4 KiB |
10.2 KiB |
-23.1 KiB (-69.4%) |
|
28.8 KiB |
4.5 KiB |
-24.3 KiB (-84.3%) |
|
6.9 KiB |
6.8 KiB |
ほぼ変化なし |
|
0.3 KiB |
0.3 KiB |
変化なし |
/aboutの-84.3%は、ページ全体のfirst-load JSが84.3%減ったという意味ではない。共通JSを足した実質的なfirst-loadは約575.9 KiBから494.8 KiBへの減少で、約14.1%減となる。大きな割合に見えるのは、小さいルート固有部分から24.3 KiBの重複チャンクが丸ごと消えたためである。
バージョン更新とアプリ変更の切り分け
Base commit 88521c7とPR head 7f8d62dをworktreeに分け、同じAPIスタブ、同じ環境変数でproduction buildした。さらに、Baseのソースと設定を維持したままnextと@next/envだけ16.3.0へ更新した中間条件を用意した。
TURBOPACK_STATS=1 \
NEXT_PUBLIC_API_URL=http://localhost:43008 \
bun run build
条件 |
共有JS |
ユニークJS合計 |
16.2.11からの差分 |
|---|---|---|---|
Baseソース + Next.js 16.2.11 |
547.0 KiB |
649.0 KiB |
— |
Baseソース + Next.js 16.3.0のみ |
488.8 KiB |
543.3 KiB |
-58.3 KiB / -105.7 KiB |
PR全体(Cache Components移行を含む) |
490.3 KiB |
544.7 KiB |
-56.7 KiB / -104.2 KiB |
Next.jsだけを更新した時点で、PR全体よりわずかに小さくなっている。PRのアプリ・設定変更を加えると、16.3.0単純更新時より共有JSが1.5 KiB、ユニークJS合計が1.4 KiB増えた。したがって、今回の大幅削減をuse cacheやpartialPrefetchingの効果として説明することはできない。
CIはLinux、切り分けはmacOSで実行したため、丸め前の値には0.1 KiB程度の差がある。削減量とチャンク構造は一致した。
原因1: Server側実装のclient bundle混入が解消された
Next.js公式のTurbopack Bundle Analyzerを両バージョンで実行し、client outputに含まれるソースモジュールを比較した。
bunx next experimental-analyze --output
16.2.11では次のServer側モジュールがclient outputに含まれていたが、16.3.0では0になった。表はAnalyzer上の非圧縮サイズで、上位6件だけで51.8 KiBある。
16.2.11でclient outputに入っていたモジュール |
削減量 |
|---|---|
|
20.8 KiB |
|
7.9 KiB |
|
7.4 KiB |
|
6.6 KiB |
|
4.8 KiB |
|
4.2 KiB |
Analyzerのclient JS全体では730,302 bytesから673,264 bytesへ、57,038 bytes(55.7 KiB)減った。新しいnavigation実装の増加分を含めても、Server側コードの除去が上回っている。
これはNext.js 16.3の次の修正と一致する。
#95201 Split typeof-window server requires into .browser variants: PR本文で「recent bundle size regressions」の修正と説明されている
#95366 Split remaining "client-node"-only modules into .browser variants:
next/src/serverがclient-browser bundleへ入るregressionを解消している#95200 Collect modules with browser variants statically:
.browsersiblingを自動検出し、browser向けmodule aliasを生成する
原因2: 同一モジュール集合の重複チャンクが統合された
16.2.11のproduction artifactには、いずれも24,878 bytesで、同じ19個のmodule factoryを持つチャンクが3ファイルあった。差はmodule factoryの並び順だけだった。
1ファイルは全ルート共通チャンク
1ファイルは
/と/page/[page]が追加ロード1ファイルは記事詳細と
/aboutが追加ロード
つまり、該当ルートは同じモジュール集合を共有チャンクとルート固有チャンクから二重に受け取っていた。また、ユニークJS合計では同内容の余分な2ファイルを別ファイルとして数えていた。
16.3.0ではこの2ファイルが消えた。
各該当ルート: 約24.3 KiB減
ユニークJS合計:
24,878 bytes × 2 = 49,756 bytes、約48.6 KiB減
これは#94961 Sort modules in chunks to reduce duplicatesの修正内容と一致する。Turbopackが同じchunk item集合を異なる順番で配置するとcontent hashが変わり、重複ファイルとして配信される問題に対し、module順を正規化して同じファイル名へ収束させている。
小さい要因: Turbopack runtime自体の縮小
Next.js 16.3の公式記事にもSmaller runtime sizeとして、WebAssembly、Worker、top-level async用コードを必要な場合だけ配信する変更が記載されている。
このアプリのturbopack-*.jsは10,580 bytesから9,652 bytesへ928 bytes(約0.9 KiB)減った。改善は確認できるが、104.3 KiB削減の主因ではない。
対応するupstream PRは次のとおり。
ISR / Partial Prefetchingとの関係
今回のPRで導入したCache Components、use cache、cacheLife、Partial Prefetchingは、再生成、キャッシュ、navigation時のshell配信を改善する。一方、上のA/B結果では、それらを入れる前のNext.js 16.3.0単純更新だけで削減が完了している。
したがって、同じアップデートで次の2種類の改善が同時に得られたが、原因は分けて考える必要がある。
ISR / navigation改善: Cache ComponentsとPartial Prefetchingの新しい実行モデル
client bundle削減: Turbopackのbrowser variant分離、重複チャンク除去、runtime縮小
計測上の注意
Bundle Size reportは非圧縮JSを計測している。gzip / Brotli後の転送量やJavaScript実行時間を直接表すものではない
Shared JSは全ルートのチャンク集合の積集合、Total unique JSは和集合、ルート固有JSは共有集合を除いた値である。Turbopackのre-chunkingにより共有・固有の分類は変わり得るroute-bundle-stats.jsonのファイルサイズ比較とBundle Analyzerのmodule内訳は役割が異なる。前者をCIの判定値、後者を原因特定に使用したチャンク名はcontent hashなので環境やbuildで変わる。今回の根拠はファイル名ではなく、サイズ、route参照関係、module factory集合で確認した