$ cat posts/20260711-9zsc1x.mdx
Cannot find module './611.js' の正体 — Next.jsの.nextキャッシュとブランチ切り替え
はじめに
このブログの開発中に、devサーバーでページを開いたら突然こんなエラーが出た。
Runtime Error
Cannot find module './611.js'
Require stack:
- .next/server/webpack-runtime.js
- .next/server/app/blog/[slug]/page.js
- ...
611.js なんてファイル、自分では一度も書いていない。コードのどこを見ても原因が見つからない。
結論から書くと、コードのバグではなく .next ディレクトリ(ビルドキャッシュ)とソースコードの不整合だった。直し方はこれだけ。
# devサーバーを止めてから
rm -rf .next
npm run devこの記事では「なぜこれで直るのか」を、.next に何が入っているのかから追ってみる。
何をしたら壊れたのか
状況はこうだった。
npm run devでdevサーバーを起動したまま- 別の作業のために
git checkoutでブランチを何度も切り替えた - ブラウザでページを開いたら冒頭のエラー
devサーバーを起動しっぱなしで、その下のソースコードをブランチ切り替えで丸ごと入れ替えたのが原因だ。
.next には何が入っているのか
next dev や next build を実行すると、プロジェクト直下に .next ディレクトリができる。中身はだいたいこう。
.next/
├── cache/ # webpack/SWCのコンパイルキャッシュ(ビルド高速化用)
├── server/ # サーバー側で実行されるコンパイル済みJS
│ ├── webpack-runtime.js # ← エラーのRequire stackの先頭にいたやつ
│ └── app/blog/[slug]/page.js
└── static/ # ブラウザに配信されるJS/CSS(チャンク)
ポイントは、webpackがコードを数値IDのチャンクに分割することだ。611.js というのはwebpackが機械的に採番したチャンクファイルで、webpack-runtime.js が「モジュールID → チャンクファイル」の対応表を持っている。
なぜブランチ切り替えで壊れるのか
devサーバーのコンパイルは差分(インクリメンタル)前提で動いている。ファイルを1つ保存したら、その差分だけを再コンパイルして、既存のチャンクやID対応表は再利用する。
ところが git checkout はファイルを丸ごと入れ替える。モジュールの依存グラフが大きく変わると、チャンクの採番もやり直しになる。このとき:
- 対応表(
webpack-runtime.js)は「611.jsにあるはず」と古い情報を持ったまま - 実際の
611.jsは再採番で消えている
という食い違いが起きて、Cannot find module './611.js' になる。
.next を消せば対応表もチャンクもゼロから作り直されるので、確実に直るというわけだ。
もうひとつの引き金: npm install と vendor-chunks/*.js
後日、今度は別のブランチで作業中にこんなエラーが出た。
Cannot find module './vendor-chunks/esprima.js'
Require stack:
- .next/server/webpack-runtime.js
- .next/server/app/blog/[slug]/page.js
- ...
ファイル名は 611.js と違うが、Require stackの顔ぶれは同じ。これも .next とソースの不整合で、rm -rf .next で直った。前と違うのは2点だけだ。
引き金が git checkout ではなく npm install だった。 このときは新しい依存(ESLint)を足した直後だった。npm install は node_modules の中身を書き換えるので、ブランチ切り替えと同じく依存グラフが変わり、チャンクの採番がやり直しになる。自分のソースを1行も触っていなくても、依存を足しただけで壊れることがある、というのがポイント。
チャンク名が数字ではなく vendor-chunks/<名前>.js だった。 webpackは自分のアプリコードを数値ID(611.js)に、node_modules 由来のコードを vendor-chunks/<パッケージ名>.js に分けて出力する。今回の esprima は、frontmatterをパースする gray-matter が内部で使っている依存だ(gray-matter → js-yaml → esprima)。数字でも名前でも、エラーの本質は同じ「対応表とチャンクの食い違い」。
まとめると、.next が壊れる引き金は「ソースの入れ替え(git checkout)」だけでなく「依存の入れ替え(npm install)」もある。どちらも対処は同じ rm -rf .next だ。
副作用: エラーメッセージが当てにならなくなる
キャッシュ不整合の厄介なところは、表示されるエラーが本当の原因と無関係なことだ。今回の調査中には、同じ原因でこんなエラーも出た。
[Error: <Html> should not be imported outside of pages/_document.]
Error occurred prerendering page "/404".
App Routerしか使っていないのに pages/_document の話をされて面食らったが、これも rm -rf .next で消えた。「コードを触っていないのに意味不明なエラーが出た」ときは、まずキャッシュを疑うのが精神衛生上よい。
対処と予防
直すとき
# devサーバーを Ctrl+C で止める(起動したまま消すと再生成と競合する)
rm -rf .next
npm run dev.next/cache が消えるので初回コンパイルは少し遅くなるが、それだけ。.next はいつ消しても安全な純粋な生成物だ(gitignore対象でもある)。
予防するには
- ブランチを切り替えたら、devサーバーを再起動する癖をつける。大きく履歴が飛ぶとき(rebase後、複数ブランチ間の移動)は
.nextも消す npm installで依存を足した/変えた直後も同じ。dev起動中なら再起動、怪しければrm -rf .next- レビューなどで複数ブランチを行き来するなら
git worktreeでブランチごとに作業ディレクトリを分けると、.nextもディレクトリごとに分離されて衝突しない - CI(このブログはGitHub Actions)では毎回クリーンな環境でビルドするので、この問題はローカル固有
まとめ
Cannot find module './<数字>.js'も'./vendor-chunks/<名前>.js'も、正体は.nextとソースの不整合。コードは悪くない(数字=アプリコード、名前=node_modules由来のチャンク)- devサーバーは差分コンパイル前提なので、
git checkout(ソースの入れ替え)にもnpm install(依存の入れ替え)にも弱い - 直すのは
rm -rf .next、予防は「ブランチ切り替え・依存追加のあとにdev再起動」かgit worktree - キャッシュが壊れているときのエラーメッセージは信用しない