解決方法:TypeScriptの「Top-level await expressions are only allowed」エラー

intermediate🔵 TypeScript2026-07-08| TypeScript 3.8以上、Node.js 14.8以上、すべてのOS(Windows、macOS、Linux

Error Message

Top-level await expressions are only allowed when the 'module' option is set to 'es2022', 'esnext', 'system', 'node16', or 'nodenext'.

問題点

ファイルのルートで直接データを取得したり、データベース接続を初期化しようとして、壁にぶつかったのではないでしょうか。スムーズに実行される代わりに、TypeScriptがファイルをモダンなモジュールとして認識しないため、コンパイラエラーでブロックされてしまいます。

次のような一般的なシナリオを考えてみましょう:

// index.ts
const response = await fetch('https://api.example.com/data');
const data = await response.json();
console.log(data);

デフォルトでは、TypeScriptは多くの場合CommonJSをターゲットにします。このレガシーなシステムは require() に依存しており、厳密に同期処理を行います。トップレベルの await はECMAScriptモジュール(ESM)専用の機能であるため、実行時にコードが壊れるのを防ぐためにコンパイラがエラーをスローします。

TL;DR: クイックフィックス

tsconfig.json を開き、設定をモダンな標準に更新してください。通常、次の3行でケースの90%が解決します:

{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "moduleResolution": "nodenext"
  }
}

Node.js向けにビルドしている場合は、package.json"type": "module" も追加する必要があります。

詳細な修正方法

1. tsconfig.json をモダンにする

実際、エラーメッセージは非常に役立ちます。この機能をサポートする特定のモジュールシステムがリストされています。これを適切に修正するには、環境がESMを処理できるほどモダンであることをTypeScriptに伝える設定になっているか確認してください。

{
  "compilerOptions": {
    /* ネイティブのトップレベルawaitをサポートするための最小ターゲットはES2022です */
    "target": "es2022", 
    
    /* Node.js 16以降を使用している場合は 'nodenext' を使用します */
    "module": "nodenext", 
    
    "moduleResolution": "nodenext",
    
    /* IDEにモダンなAPI定義を含めます */
    "lib": ["esnext", "dom"]
  }
}

モダンなバックエンドプロジェクトでは、nodenext を使用するのが最も確実です。これにより、TypeScriptはimport文でファイル拡張子を必須にするなど、Node.jsと同じルールに従うようになります。

2. package.json で ESM を有効にする

TypeScriptを更新するだけでは不十分な場合があります。コンパイルされた .js ファイルをNode.jsで実行する場合、ランタイムはそれらを古いスクリプトではなくモジュールとして扱う必要があることを知る必要があります。package.json ファイルに type フィールドを追加してください:

{
  "name": "my-app",
  "version": "1.0.0",
  "type": "module"
}

これがないと、TypeScriptコンパイラに問題がなくても、Node.jsが await キーワードを検出したときに SyntaxError: Unexpected reserved word をスローします。

3. IIFEによる回避策(レガシープロジェクト向け)

ESMに切り替えられない場合もあります。レガシーなコードベースや、モダンなモジュールルールの下ではクラッシュしてしまう依存関係に縛られているかもしれません。このような場合は、ロジックを即時実行関数式(IIFE)でラップします。

(async () => {
  try {
    const response = await fetch('https://api.example.com/data');
    const data = await response.json();
    console.log(data);
  } catch (err) {
    console.error("Initialization failed", err);
  }
})();

これにより、awaitasync 関数ブロックの中に配置されます。これにより、コンパイラを満足させつつ、起動時にコードを即座に実行させることができます。

なぜこれが起こるのか

JavaScriptはもともと、単純な順序でファイルを次々に読み込むように設計されていました。ES2017で async/await が登場したとき、予測可能性を保つためにその使用は関数本体に制限されていました。エコシステムがECMAScriptモジュールへと移行するにつれ、仕様も進化しました。トップレベルの await はES2022で正式に追加されました。しかし、Node.jsが10年以上にわたって使用してきたシステムであるCommonJSは、根本的に同期型です。ファイルのトップレベルでPromiseを待つために実行を一時停止することはできません。

修正を確認する方法

赤い波線が消えただけで安心しないでください。すべてが正しく設定されていることを確認するために、次の手順に従ってください:

  • TS サーバーの再起動: VS Codeでコマンドパレット(Cmd/Ctrl + Shift + P)を開き、「TypeScript: TS サーバーをリスタート」を実行します。
  • ビルドの確認: npx tsc を実行します。ターミナルにエラーが出力されずにコマンドが終了する必要があります。
  • 出力の検査: dist/index.js ファイルを開きます。トランスパイルされた複雑な __awaiter ヘルパーではなく、書いた通りに await キーワードが表示されているはずです。
  • ランタイムのテスト: node dist/index.js を実行します。データがコンソールにログ出力されれば、準備完了です。

よくある落とし穴

  • 古いNode.js: トップレベルの await には Node.js 14.8.0 以降が必要です。Node 12 のような古い LTS バージョンを使用している場合、これらの修正はどれも機能しません。
  • 'target' の罠: targetes5 に設定されている場合、TypeScript はコードを20年前の JavaScript に変換しようとします。ES5 には Promise の概念がないため、エラーが発生します。

Related Error Notes