2026年09月08日 13:40 / - PV

next buildがこう言って落ちました。
Error: Cannot find module './undefined'
./undefined。そんなモジュールを書いた覚えはありません。
しかも厄介なことに、その少し上にはこう出ています。
✓ Compiled successfully
コンパイルは成功しています。落ちるのはその後のCollecting page dataの段階です。「型もsyntaxも通っているのに落ちる」という、原因の見当がつきにくい壊れ方でした。
犯人は動的import(await import(...))にパスエイリアスを渡していたことでした。
こういうコードを書いていました。
// tsconfig.json の baseUrl を "." にしているので、
// 静的 import ではこの書き方ができる
const { doSomething } = await import('lib/someModule')
tsconfig.json(あるいはjsconfig.json)でbaseUrlを設定していると、こう書けます。
{
"compilerOptions": {
"baseUrl": "."
}
}
これでimport { x } from 'lib/someModule'という静的importは問題なく動きます。エディタの補完も効きますし、型も通ります。
ところが同じパスを動的importに渡すと、ビルドの終盤で落ちます。
静的importの場合、webpackはビルド時に解決先を確定できます。lib/someModuleという文字列を、設定(baseUrlやpaths)に従って実ファイルへ辿れます。
動的importは扱いが違います。import()は実行時に評価される式なので、webpackは「実行時に何が来るか分からない」ケースに備えます。中身が変数だったりテンプレート文字列だったりすると、**候補になりうるファイルをまとめた「コンテキストモジュール」**を作って、実行時に選ばせる形にします。
パスエイリアスが解決できないまま動的importに渡ると、この「実行時に決める」側の処理へ流れます。そして解決先が決まらないままundefinedが入り込み、
Cannot find module './undefined'
という、元の記述からは何も読み取れないエラーになります。
エラー文に自分が書いたパスが一切出てこないのが、この問題のいちばん厄介なところです。lib/someModuleで検索しても何も出ません。
いちばん簡単で確実です。
// ✗ 落ちる
const { doSomething } = await import('lib/someModule')
// ✓ 通る
import { doSomething } from 'lib/someModule'
そもそも自作の小さなモジュールを動的importする利点は多くありません。バンドルサイズを削りたくてやるものですが、数KBのユーティリティなら効果はほぼ無く、落ちるリスクだけを買っていることになります。
遅延読み込みしたい理由が本当にあるなら、エイリアスをやめて相対パスにします。
// ✓ 相対パスなら動的importでも解決できる
const { doSomething } = await import('../../lib/someModule')
見た目は美しくありませんが、webpackが迷わずに解決できます。
紛らわしいですが、外部パッケージの動的importは普通に動きます。
// ✓ これは問題ない
const JSZip = (await import('jszip')).default
うちでもファイル圧縮まわりでこの書き方をしていて、何の問題も起きていません。
つまり「動的importが悪い」のではなく、「動的import × 自前のパスエイリアス」の組み合わせだけが危険です。重いライブラリを必要なときだけ読み込む、という本来の使い方は続けて大丈夫です。
エラー文が役に立たないので、力技で探します。
grep -rn "await import(\|import(" --include=*.ts --include=*.tsx --include=*.js --include=*.jsx pages lib components
出てきたimport()のうち、引数が./や../で始まっておらず、かつnode_modulesのパッケージ名でもないものが容疑者です。
Collecting page dataで落ちている以上、対象はページから辿れるコードです。全部見る必要はありません。
もうひとつハマった点があります。
うちはツールページを段階的に公開する仕組みを持っていて、公開対象外のページはビルドの対象から外れることがあります。その結果、
問題のあるコードを書いてから、しばらく経ってビルドが落ち始めるという現象が起きました。落ちた日に触ったコードとは無関係の場所が原因だったわけです。「今日の変更を巻き戻したのに直らない」となったら、この可能性を疑ってください。
教訓としては、手元のブランチでビルドが通っても安心しないこと。公開設定やフィーチャーフラグでビルド対象が変わる作りなら、本番と同じ条件(main相当)でビルドを通すまでが検証です。
動きます。next devは必要なページを都度コンパイルする作りで、本番ビルドのCollecting page dataにあたる工程がありません。
devで動いたことは、buildが通る証明になりません。この種の不具合は本番ビルドでしか出ないので、pushする前に一度next buildを通す習慣にしておくと安全です。
pathsでエイリアスを明示すれば直りますか?baseUrlだけでなくpathsで明示すると解決される場合もありますが、私は追いかけませんでした。
理由は単純で、静的importに書き換えれば確実に直るからです。ビルドツールの解決順序に依存した書き方を残すより、依存しない書き方にしてしまうほうが後々楽だと判断しました。
Compiled successfullyの後に落ちるのはなぜですか?next buildは「コードをまとめる工程」と「各ページのデータを集めて事前生成する工程」に分かれています。後者がCollecting page dataです。
ここでは実際にページのコードが実行されます。 だから、コンパイルは通るが実行時に解決できない、という今回のようなケースは後半で初めて露見します。
__webpack_require__.a is not a functionが出ましたそちらは別物で、開発サーバーを起動したまま.nextディレクトリを消したときによく出ます。webpackのランタイムとチャンクが食い違うのが原因です。
私もビルド検証のためにrm -rf .nextをして、そのままdevサーバーの画面を開いて500に出くわしました。devサーバーを起動し直せば直ります。
Cannot find module './undefined'は、動的importの解決に失敗したサインawait import('lib/xxx')のようなパスエイリアス付きの動的importawait import('jszip')は動く)Compiled successfullyの後、Collecting page dataで落ちるのが特徴エラーメッセージに手がかりが無いタイプなので、「./undefined」で検索してここに辿り着いた方の役に立てば嬉しいです。
この記事が何かのお役に立てれば幸いです。
最後までお読みいただきありがとうございました!
Share Me!