Node.jsで「ERR_REQUIRE_ESM」エラーが出たときの解決策

Node.js プロジェクトを開発していると、次のようなエラーメッセージに遭遇することがあります。

Error [ERR_REQUIRE_ESM]: Must use import to load ES Module: /path/to/module.js

これは、CommonJS と ES Modules (ESM) の混在 によって発生する典型的なエラーです。近年の Node.js では、requireimport の使い分け、package.jsontype 設定、Babel や TypeScript のトランスパイル結果など、さまざまな要因で混乱が生じます。

本記事では、原因と解決方法を整理し、実運用でのベストプラクティスをまとめます。

1. ERR_REQUIRE_ESMエラーの発生背景

Node.js はバージョン 12 以降から正式に ES Modules をサポートしています。しかし、従来の CommonJS 形式(require / module.exports)と、ESM 形式(import / export)の互換性は完全ではありません

そのため、以下の状況でエラーが発生します。

  • package.json"type": "module" を設定しているのに require() を使用
  • ESM として公開されているライブラリを require() で読み込もうとした
  • トランスパイル設定(Babel / TypeScript)と Node.js 実行環境のモジュール解釈が一致していない

このエラーは、モジュール解決のルールを正しく理解していないと何度もハマる落とし穴です。

2. 再現例と典型的なエラーメッセージ

再現しやすい最小例は次のとおりです。

// package.json
{
  "name": "esm-error-example",
  "type": "module"
}
// index.js
const lib = require('./lib.js')

実行すると以下のエラーが表示されます。

Error [ERR_REQUIRE_ESM]: Must use import to load ES Module: /path/to/lib.js
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1219:13)

このエラーは「あなたのプロジェクトは ESM として動作しているのに、require() を使おうとしている」と警告しています。

逆に、type: "commonjs"(デフォルト)環境で import を使用してもエラーになります。

import fs from 'fs'
// SyntaxError: Cannot use import statement outside a module

3. 解決策1: ESMへの統一

もっとも推奨されるのは、プロジェクト全体を ESM に統一する方法です。

  1. package.json"type": "module" を設定
  2. 全てのモジュールを import / export に書き換える
  3. __dirname, __filename, require が必要な場合は import.meta.urlcreateRequire を使用
// 修正例
import { readFile } from 'fs/promises'

const data = await readFile(new URL('./data.json', import.meta.url))
console.log(data.toString())

もし CommonJS のモジュールを読み込む必要がある場合は、createRequire を利用できます。

import { createRequire } from 'module'
const require = createRequire(import.meta.url)
const cjsLib = require('./legacy-lib.cjs')

4. 解決策2: CommonJS環境に統一

既存プロジェクトが大きく、ESM への移行が困難な場合は CommonJS 環境に固定する 方法もあります。

  • package.json から "type": "module" を削除
  • すべてのファイルで requiremodule.exports を利用
  • ESM ライブラリを読み込む場合は dynamic import() を使用
(async () => {
  const esmLib = await import('./esm-only-lib.js')
  esmLib.default()
})()

この方法は既存資産を維持したまま少しずつ移行する際に有効です。

5. 実運用上の落とし穴とCI/CDでの注意点

実際の開発では、ローカルでは動くのに CI/CD で落ちるケースが多発します。

  • CI 環境が古い Node.js バージョンを使用している
  • Babel や tsc が CommonJS 出力をしているのに、type: module を付けてしまう
  • node --experimental-specifier-resolution=node の有無で挙動が変わる

対策としては以下がおすすめです。

  • .nvmrcvolta を使い Node.js バージョンを固定
  • ビルド設定と runtime の module 解釈を揃える
  • CI で node -p "process.versions.node" を実行しバージョンをログ出力
  • package.json の "exports" フィールドを正しく設定

また、混在環境を避けるため、ESLint で no-restricted-imports を使い、特定のモジュールシステムの利用を制限するのも有効です。

6. まとめ

「ERR_REQUIRE_ESM」エラーは、Node.js のモジュール解釈がプロジェクト設定と一致していないときに発生します。根本解決には以下のいずれかの選択が必要です。

  • プロジェクト全体を ESM に統一し、import/export を利用
  • もしくは CommonJS に固定し、必要な箇所のみ dynamic import() を使う

移行中のプロジェクトでは、トランスパイル後のコードと package.json の設定が整合しているかを必ず確認しましょう。CI/CD 環境での実行結果もチェックし、バージョンや実行オプションの差異による事故を防止することが重要です。

参考

タイトルとURLをコピーしました