Next.js の moduleResolution を TypeScript 7.0 目線で見直す
はじめに
普段開発している Next.js プロジェクトを VS Code で確認していたところ、tsconfig.json の moduleResolution: "node" に関するエラーが表示されていました。
moduleResolution は、TypeScript が import の参照先をどのように解決するかを決める設定です。普段の開発ではあまり意識しない項目ですが、Node.js、Next.js、バンドラー、TypeScript のバージョンによって適切な値が変わります。
今回エラーになっていた moduleResolution: "node" は、現在の TypeScript では node10 相当の古い解決方式として扱われます。
TypeScript 7.0 では、TypeScript 6.0 で非推奨となった一部の設定がハードエラーとして扱われるため、事前に見直しておく必要があります。その中に moduleResolution: "node" の解決方式も含まれています。
この記事では、VS Code 上で表示された moduleResolution: "node" のエラーをきっかけに、Next.js アプリケーションの tsconfig.json をどのように見直すかを整理します。
今回確認した tsconfig.json
今回確認した tsconfig.json は以下です。
{
"compilerOptions": {
"allowJs": true,
"allowSyntheticDefaultImports": true,
"module": "esnext",
"moduleResolution": "node",
"noUnusedLocals": true,
"noUnusedParameters": true,
"preserveConstEnums": true,
"removeComments": false,
"sourceMap": true,
"strict": false,
"strictPropertyInitialization": false,
"strictNullChecks": false,
"target": "esnext",
"forceConsistentCasingInFileNames": true,
"esModuleInterop": true,
"isolatedModules": true,
"resolveJsonModule": true,
"noFallthroughCasesInSwitch": true
}
}この中で今回の主な確認対象は、以下の設定です。
"moduleResolution": "node"moduleResolution とは何か
moduleResolution は、TypeScript が import の参照先をどのように探すかを決める設定です。
たとえば、次のような import があったとします。
import { Button } from "./Button";
import dayjs from "dayjs";このとき TypeScript は、./Button や dayjs がどのファイル、またはどの型定義を指しているのかを解決する必要があります。
具体的には、次のようなものを探します。
- .ts / .tsx / .d.ts などのファイル
- node_modules 配下のパッケージ
- package.json の types / exports / imports
- index.ts のような省略されたファイル
- paths で定義されたエイリアス
この探し方のルールを決めるのが moduleResolution です。
注意したいのは、moduleResolution は TypeScript の型チェック時の解決ルールであり、実際にブラウザや Node.js がコードを実行するときの解決そのものではない、という点です。
Next.js のような環境では、実際のビルドや実行時の解決には Next.js やバンドラー側の仕組みも関わります。そのため、TypeScript 側の解決ルールと実際のビルド環境の解決ルールが大きくずれていると、型チェックでは通るのにビルドで失敗する、またはその逆のような問題が起きることがあります。
moduleResolution: "node" はなぜ見直し対象なのか
現在指定している moduleResolution: "node" は、現在の TypeScript では node10 相当の古い解決方式で、主に CommonJS の require を前提にした時代の Node.js 向け解決方式です。
一方で、現在の Node.js では ESM と CommonJS が併存しており、package.json の exports や imports なども考慮する必要があります。TypeScript の公式ドキュメントでも、現代の Node.js 向けには node16 や nodenext、バンドラー向けには bundler が選択肢として説明されています。
TypeScript 7.0 を見据えると、moduleResolution: "node" のままにしておくのではなく、プロジェクトの実行環境に合わた値へ移行する必要があります。
Next.js アプリ本体では bundler が候補になる
Next.js のアプリケーションコードは、TypeScript の出力を Node.js がそのまま直接実行するというより、Next.js のビルドパイプラインを通して処理されます。
Next.js は TypeScript を組み込みでサポートしており、next dev や next build の実行時に Next.js 側の仕組みも関わります。公式ドキュメントでも、Next.js は TypeScript を組み込みでサポートし、推奨される設定を含む tsconfig.json を扱うことが説明されています。
そのため、Next.js のアプリケーション本体では、moduleResolution: "bundler" が候補になります。
今回のプロジェクトでも、対象は Next.js のアプリケーションコードです。そのため、moduleResolution: "node" から moduleResolution: "bundler" へ変更する方針にしました。
修正後の tsconfig.json
{
"compilerOptions": {
"allowJs": true,
"allowSyntheticDefaultImports": true,
"module": "esnext",
"moduleResolution": "bundler",
"noUnusedLocals": true,
"noUnusedParameters": true,
"preserveConstEnums": true,
"removeComments": false,
"sourceMap": true,
"strict": false,
"strictPropertyInitialization": false,
"strictNullChecks": false,
"target": "esnext",
"forceConsistentCasingInFileNames": true,
"esModuleInterop": true,
"isolatedModules": true,
"resolveJsonModule": true,
"noFallthroughCasesInSwitch": true
}
}変更したのは、次の1行です。
- "moduleResolution": "node",
+ "moduleResolution": "bundler",今回は moduleResolution の見直しを主目的にしているため、他の設定はそのままにしています。
他にも見直したい設定
今回の主題は moduleResolution ですが、TypeScript 6.0 / 7.0 への移行を見据えると、他にも見直したい設定があります。
たとえば、今回の tsconfig.json では以下の設定が気になります。
"strict": false,
"strictNullChecks": false,
"strictPropertyInitialization": falseこれらは TypeScript 7.0 で直ちに使えなくなる設定ではありません。
ただし、型チェックをかなり緩める設定です。現時点では既存コードへの影響を考慮して false のままにしていますが、今後の変更にあわせて段階的に見直していきたいです。
また、今回の tsconfig.json には含まれていませんが、baseUrl や paths を使っている場合も注意が必要です。特に import エイリアスを使っているプロジェクトでは、TypeScript 側の解決と Next.js 側の解決が一致しているかを確認しておく必要があります。
補足: TypeScript 7.0 について
TypeScript 7.0 では、TypeScript コンパイラや関連ツールの実装が Go ベースのネイティブ実装に移行します。
プレビュー段階では @typescript/native-preview パッケージとして提供されており、tsc の代わりに tsgo コマンドで試すことができます。
今回の moduleResolution: "node" の見直しは、この TypeScript 7.0 移行の流れとも関係しています。TypeScript 6.0 では node / node10 が非推奨となり、TypeScript 7.0 ではサポート対象外になる方針が示されているためです。
そのため、今すぐ TypeScript 7.0 へ移行するわけではなくても、既存の tsconfig.json を見直すきっかけになります。

まとめ
今回は、Next.js プロジェクトで表示された moduleResolution: "node" のエラーをきっかけに、tsconfig.json を見直しました。
moduleResolution: "node" は、現在では node10 相当の古い解決方式として扱われます。TypeScript 7.0 を見据える場合は、nodenext または bundler への移行を検討する必要があります。
Next.js のアプリケーションコードは、通常 Next.js のビルドパイプラインを通して処理されます。そのため、今回のプロジェクトでは moduleResolution: "bundler" を候補にしました。
一方で、Node.js で直接実行するスクリプトやライブラリ公開用のコードでは、nodenext の方が適している場合があります。
bundler は「Next.js アプリ本体向けの選択肢」として捉え、プロジェクト内のコードの実行環境に応じて使い分けるのがよさそうです。
