Node.jsのERR_REQUIRE_ASYNC_MODULEを直す:トップレベルawaitと動的importの切り分け

Node.jsのERR_REQUIRE_ASYNC_MODULEはESM依存グラフのトップレベルawaitが原因。動的import()をawaitして待ち、namedとdefault exportを確認する案内。

require()先のES Module、またはそのimport依存グラフにトップレベルawaitがあると、同期処理のrequire()では読み込めません。CommonJS側をimport()へ変え、返されるPromiseの完了をawaitするのが基本の修正です。

ERR_REQUIRE_ASYNC_MODULEや「require() cannot be used on an ESM graph with top-level await」は、直接の読み込み先だけでなく、その先の依存を含むESMグラフが非同期であることを示します。

目次

同期ESMはrequireできるが、非同期ESMはできない

現在のNode.jsでは、ESMをすべてrequire()できないわけではありません。Node.jsのCommonJS modules資料では、トップレベルawaitを含まない完全に同期的なESMグラフは、条件を満たせばrequire()で読み込めると説明されています。グラフ内のどこかにトップレベルawaitがある場合は、評価完了を同期的に返せないため、このエラーになります。

同期ESMのrequire()はNode.js 20.19.0、22.12.0、23.0.0以降で既定有効です。「CommonJSからESMは一切requireできない」という古い一般論では切り分けられません。2026年10月4日時点の主な系列差は次のとおりです。

系列本件での違い
Node 2222.12.0以降で同期ESMのrequire()が既定有効。診断フラグはモジュールを評価して場所を探す
Node 2424.15.0以降でrequire(esm)が非実験扱い。24.20.0以降には診断改善がバックポート。それ以前の診断フラグはモジュールを評価する
Node 2626.5.0以降でrequireStackとtopLevelAwaitLocationsを追加し、診断時にモジュールを評価しない

Node 26.5.0で追加された診断用error propertiesを、Node 22やNode 24全般にあるものとして扱ってはいけません。topLevelAwaitLocationsは診断フラグ使用時だけ設定されます。Node 24では24.20.0以降にバックポートされていますが、それ以前にはありません。バックポートはNode 24.20.0のErrors資料で確認できます。詳細はERR_REQUIRE_ASYNC_MODULEの公式説明を確認してください。

空の作業用ディレクトリで再現する

新しい空のディレクトリに次の2ファイルを作ります。.mjsはESM、.cjsはCommonJSを明示するため、package.jsonは不要です。

async.mjs

export const value = await Promise.resolve('ok');

main.cjs

const { value } = require('./async.mjs');

console.log(value);
node main.cjs

async.mjsはファイル直下でawaitしているため、同期的なrequire()では読めず、ERR_REQUIRE_ASYNC_MODULEになります。これは公式仕様に基づく最小例です。まずnode --versionで実行版を確認してください。

依存グラフにトップレベルawaitがあればawait import()へ変更する判断フロー。診断はNode22、Node24.20未満のNode24、Node26.5未満のNode26ではモジュールを評価し、副作用が起きる可能性がある。Node24.20以降とNode26.5以降では評価しない。実行版を確認し、信頼できる最小再現環境で診断する。
同期読み込みの条件と非同期ESMを区別します。診断フラグの評価有無はNodeの版に依存します。 図をクリック・タップして拡大

import()で読込完了を待つ修正

CommonJSから非同期ESMを読む場合は、import()が返すPromiseを待ちます。呼び出しとエラー処理まで含めると次の形です。

修正後のmain.cjs

async function main() {
  const { value } = await import('./async.mjs');
  console.log(value);
}

main().catch((error) => {
  console.error('モジュールの読み込みに失敗しました');
  console.error(error);
  process.exitCode = 1;
});

valueはnamed exportなので分割代入できます。読み込み先がexport defaultなら、const { default: value } = await import(...)またはmodule.defaultを使います。import()はPromiseなので、awaitせずにmodule.valueを読んではいけません。

上の再現例と修正例はWindowsのNode.js v24.19.0で実行し、require()ではERR_REQUIRE_ASYNC_MODULE、import()修正後はokの出力を確認しました。

直接のファイルにawaitがなくても失敗する理由

判定対象は依存グラフ全体です。たとえばentry.mjs自身にトップレベルawaitがなくても、そこからimport './async.mjs'していれば、require('./entry.mjs')は失敗します。依存ライブラリ更新後に発生した場合は、追加・更新された依存先まで追います。

build toolやtest toolがプラグインを同期require()する設計なら、呼び出し側だけでは直せない場合があります。利用中のNode.js版と、各ツールのESM・トップレベルawait対応を公式資料で確認してください。

実プロジェクトでの切り分け順

  1. node --versionで、ローカル、CI、コンテナの実行版を確認する
  2. 入口の拡張子と、最寄りのpackage.jsonの"type"を確認する
  3. 失敗したstep、最初のエラー、stackから同期require()の入口を特定する
  4. 直接の対象から、そのimport先へ依存グラフを追う
  5. 必要なら信頼できる最小再現projectで--experimental-print-required-tlaを使う
  6. import()への変更、依存更新、toolの互換設定を検討する

.cjsはCommonJS、.mjsはESMです。.jsは最寄りのpackage.jsonにある"type": "commonjs"または"type": "module"の影響を受けます。type指定がない場合は、対応版ではESM構文の検出も関わります。判定規則はModules: Packages公式資料を参照してください。

--experimental-print-required-tlaは版によって性質が違います。Node 22、Node 24.20.0より前のNode 24、Node 26.5.0より前のNode 26では、場所を探すためにモジュールを評価します。Node 26.5.0以降とNode 24.20.0以降では、評価せずに場所を出力します。未知のコードや本番入口へ安易に付けず、実行してよいコードを隔離した再現環境で使ってください。変更の根拠はNode 26.5.0とNode 24.20.0のリリースノートにも記載されています。

上の最小例を診断するコマンドは次のとおりです。エラーを捕捉していない例なので、対応版ではトップレベルawaitの場所が標準エラー出力へ表示されます。

node --experimental-print-required-tla main.cjs

--no-require-moduleは非同期ESMを同期化する修正ではありません。同期ESMをrequire()する機能を無効化するだけで、トップレベルawaitを含むグラフの非同期性は消えません。

よくある質問

async関数内のawaitも原因ですか?

関数内のawaitだけならトップレベルawaitではありません。関数の外側で、モジュール評価中に使われるawaitが対象です。

トップレベルawaitを削除すべきですか?

必須ではありません。非同期初期化が必要ならimport()で待つのが自然です。設計を変えられる場合は、初期化処理を明示的なasync関数へ移す方法もあります。

ERR_REQUIRE_ESMとは違いますか?

古いNode.jsや同期ESMのrequire()を無効化した環境ではERR_REQUIRE_ESMが出る場合があります。対応版では同期ESMは読み込め、非同期ESMグラフに対してERR_REQUIRE_ASYNC_MODULEが発生します。

公式参考資料

切り分けの中心は「ESMかどうか」だけではなく、依存グラフを同期的に評価できるかです。トップレベルawaitを含むグラフはimport()で完了を待ち、同期読込を前提とするツールでは互換性も確認してください。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次