iOS&Androidスマートフォン開発(Swift/Kotlin/React-Native) および Next.js/LaravelをはじめとしたWeb開発ならば、ぜひ創業13年の弊社にお任せください。ワンストップでお届けします。お問合せはこちらから

Next.jsで「Cannot find module './undefined'」。動的importにパスエイリアスを渡すとビルドが落ちます

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

yuku_tasのアイコン

こんにちは、Webサービス開発者のyuku_tasです。

簡単なプロフィール: MoldSpoon Inc.代表。開業から15年、業界に携わって20年。Webサイト/iOS/Androidアプリ開発から、データ分析業務・コンサル業務、ベンチャーから保険会社まで様々な案件を個人会社の形で請け負ってまいりました。詳しくはこちら

はじめに

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を設定していると、こう書けます。

tsconfig.json
{
  "compilerOptions": {
    "baseUrl": "."
  }
}

これでimport { x } from 'lib/someModule'という静的importは問題なく動きます。エディタの補完も効きますし、型も通ります。

ところが同じパスを動的importに渡すと、ビルドの終盤で落ちます。

なぜ静的importは動いて動的importは落ちるのか

静的importの場合、webpackはビルド時に解決先を確定できます。lib/someModuleという文字列を、設定(baseUrlpaths)に従って実ファイルへ辿れます。

動的importは扱いが違います。import()実行時に評価される式なので、webpackは「実行時に何が来るか分からない」ケースに備えます。中身が変数だったりテンプレート文字列だったりすると、**候補になりうるファイルをまとめた「コンテキストモジュール」**を作って、実行時に選ばせる形にします。

パスエイリアスが解決できないまま動的importに渡ると、この「実行時に決める」側の処理へ流れます。そして解決先が決まらないままundefinedが入り込み、

Cannot find module './undefined'

という、元の記述からは何も読み取れないエラーになります。

エラー文に自分が書いたパスが一切出てこないのが、この問題のいちばん厄介なところです。lib/someModuleで検索しても何も出ません。

対処

自作モジュールは静的importにする

いちばん簡単で確実です。

// ✗ 落ちる
const { doSomething } = await import('lib/someModule')

// ✓ 通る
import { doSomething } from 'lib/someModule'

そもそも自作の小さなモジュールを動的importする利点は多くありません。バンドルサイズを削りたくてやるものですが、数KBのユーティリティなら効果はほぼ無く、落ちるリスクだけを買っていることになります。

どうしても動的にしたいなら相対パスで書く

遅延読み込みしたい理由が本当にあるなら、エイリアスをやめて相対パスにします。

// ✓ 相対パスなら動的importでも解決できる
const { doSomething } = await import('../../lib/someModule')

見た目は美しくありませんが、webpackが迷わずに解決できます。

node_modulesのパッケージなら気にしなくていい

紛らわしいですが、外部パッケージの動的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相当)でビルドを通すまでが検証です。

よくある質問(FAQ)

Q. 開発サーバー(next dev)では動いていました

動きます。next devは必要なページを都度コンパイルする作りで、本番ビルドのCollecting page dataにあたる工程がありません。

devで動いたことは、buildが通る証明になりません。この種の不具合は本番ビルドでしか出ないので、pushする前に一度next buildを通す習慣にしておくと安全です。

Q. pathsでエイリアスを明示すれば直りますか?

baseUrlだけでなくpathsで明示すると解決される場合もありますが、私は追いかけませんでした。

理由は単純で、静的importに書き換えれば確実に直るからです。ビルドツールの解決順序に依存した書き方を残すより、依存しない書き方にしてしまうほうが後々楽だと判断しました。

Q. Compiled successfullyの後に落ちるのはなぜですか?

next buildは「コードをまとめる工程」と「各ページのデータを集めて事前生成する工程」に分かれています。後者がCollecting page dataです。

ここでは実際にページのコードが実行されます。 だから、コンパイルは通るが実行時に解決できない、という今回のようなケースは後半で初めて露見します。

Q. 似たエラーで__webpack_require__.a is not a functionが出ました

そちらは別物で、開発サーバーを起動したまま.nextディレクトリを消したときによく出ます。webpackのランタイムとチャンクが食い違うのが原因です。

私もビルド検証のためにrm -rf .nextをして、そのままdevサーバーの画面を開いて500に出くわしました。devサーバーを起動し直せば直ります。

まとめ

  • Cannot find module './undefined'は、動的importの解決に失敗したサイン
  • 原因はawait import('lib/xxx')のようなパスエイリアス付きの動的import
  • 自作モジュールは静的importに。どうしてもなら相対パス
  • node_modulesのパッケージの動的importは問題ないawait import('jszip')は動く)
  • Compiled successfullyの後、Collecting page dataで落ちるのが特徴

エラーメッセージに手がかりが無いタイプなので、「./undefined」で検索してここに辿り着いた方の役に立てば嬉しいです。


この記事が何かのお役に立てれば幸いです。
最後までお読みいただきありがとうございました!

Share Me!