こんにちは、フリーランスエンジニアの太田雅昭です。
本日は、Next.js 16のTurbopackを使用した際に遭遇した、分かりづらいエラーとその解決方法について紹介します。
環境
- パッケージマネージャー: pnpm
- 依存パッケージ
- next: 16.1.1 (Turbopack)
- @google-cloud/tasks: 6.2.1
問題
Next.js 16のTurbopackを使用してビルドを実行したところ、以下のエラーが発生しました。
Error: Cannot find module as expression is too dynamic
直訳すると「式が動的すぎるためモジュールが見つかりません」となります。動的インポートの記述が複雑すぎて、Turbopackがモジュールの解決を行えなかったことが原因のようです。
エラーの全文
エラーメッセージの全文を確認すれば、何か手がかりが得られるかもしれません。全文は下記です。
▲ Next.js 16.1.1 (Turbopack)
Creating an optimized production build ...
✓ Compiled successfully in 1762.2ms
✓ Finished TypeScript in 951.9ms
Collecting page data using 7 workers ..Error: Failed to collect configuration for /
at ignore-listed frames {
[cause]: Error: Cannot find module as expression is too dynamic
at <unknown> (.next/server/chunks/ssr/[root-of-the-server]__006d6436._.js:15:287766)
at c.getJSON (.next/server/chunks/ssr/[root-of-the-server]__006d6436._.js:15:287858)
at module evaluation (.next/server/chunks/ssr/[root-of-the-server]__006d6436._.js:16:596)
at instantiateModule (.next/server/chunks/ssr/[turbopack]_runtime.js:740:9)
at getOrInstantiateModuleFromParent (.next/server/chunks/ssr/[turbopack]_runtime.js:763:12)
at Context.esmImport [as i] (.next/server/chunks/ssr/[turbopack]_runtime.js:228:20)
at module evaluation (.next/server/chunks/ssr/_7c907267._.js:1:60)
at instantiateModule (.next/server/chunks/ssr/[turbopack]_runtime.js:740:9)
at getOrInstantiateModuleFromParent (.next/server/chunks/ssr/[turbopack]_runtime.js:763:12)
at Context.commonJsRequire [as r] (.next/server/chunks/ssr/[turbopack]_runtime.js:249:12) {
code: 'MODULE_NOT_FOUND'
}
}
> Build error occurred
Error: Failed to collect page data for /
at ignore-listed frames {
type: 'Error'
}
エラーの全文を確認しても、具体的にどのファイルのどの箇所が問題なのか判然としません。スタックトレースはすべてビルド後の.nextディレクトリ内のファイルを指しており、元のソースコードとの対応が取れない状況でした。
AIに相談しても、見当違いのライブラリを指摘されるなど、全く解決の糸口が見えません。せめてエラーメッセージで問題箇所を特定できるようにしてほしいというのは、開発者としての切実な願いです。
原因を探す
エラーメッセージからは原因を特定できなかったため、地道な調査を行うことにしました。
- コードのコメントアウト: 疑わしい箇所のコードを段階的にコメントアウトし、エラーが消えるポイントを探す
- 専用のエクスポートファイルの作成: 外部ライブラリのインポートを分離し、問題の所在を明確にする
このような試行錯誤の結果、最終的に@google-cloud/tasksパッケージを使用している箇所でエラーが発生していることが判明しました。
解決法
原因が@google-cloud/tasksパッケージであることが判明したため、このパッケージをサーバーサイドの外部パッケージとして明示的に指定します。
Next.jsの設定ファイルにserverExternalPackagesオプションを追加し、Turbopackがこのパッケージをバンドルせず、Node.jsのモジュール解決機構に任せるようにします。
// next.config.ts
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
serverExternalPackages: ["@google-cloud/tasks"],
};
export default nextConfig;
この設定により、@google-cloud/tasksはバンドル対象から除外され、実行時にNode.jsによって直接読み込まれるようになります。
これでビルドが正常に完了するようになりました。
Cloud Runでの問題
上記のようにserverExternalPackagesに指定した関係で、サーバーレスであるCloud Runでエラーが発生しました。
⨯ Error: Failed to load external module @google-cloud/tasks-045a0061fd26c043: Error: Cannot find module '/app/node_modules/.pnpm/@google-cloud+tasks@6.2.1/node_modules/@google-cloud/tasks/build/protos/protos.json'
Next.jsをstandaloneでビルドしているのですが、Docker内に適切に必要なファイルを指定できていなかったためでした。以下のようにcloud-tasksで必要なライブラリを含めることで解決しました。なお下記はdaggerのコードの一部です。
.withDirectory("/app/node_modules/.pnpm", deps.directory("/app/node_modules/.pnpm"), {
include: [
"@google-cloud+tasks@*/**",
"@grpc+grpc-js@*/**",
"google-gax@*/**",
"google-auth-library@*/**",
"protobufjs@*/**",
"long@*/**",
],
})
今回は試していませんが、もっと簡単にするには下記のようにincludeを省略すればいいようです(Claude談)。ただし容量が増えるため、できるだけ調整はしたほうがいいかと思います。ライブラリのバージョンアップなどで発狂する前に使うのが良さそうですね。
.withDirectory("/app/node_modules/.pnpm", deps.directory("/app/node_modules/.pnpm"))
まとめ
Turbopackでエラーになるライブラリは、serverExternalPackagesに指定すれば解決する場合があります。今回はコードを直接コメントアウトしたりして原因調査を行いましたが、そうではなく初めから怪しいライブラリを全てserverExternalPackagesに指定して、テストしながら減らす方法も有効ですね。