結論

このエラーは、import 文を書いたファイルが「ES モジュールではなく CommonJS(または普通のスクリプト)」として読み込まれたときに出ます。Node.js なら package.json に "type": "module" を書くか拡張子を .mjs に、ブラウザなら <script type="module"> に、Jest なら Babel で変換するか ESM モードで動かします。

この記事でわかること

  • Node.js・ブラウザ・Jest・TypeScript 別の原因と直し方
  • 同じ原因のエラー13通り・近いエラー10通りを再現した原文と、直したあとの出力
  • Jest 29 と Jest 30 で文言が変わった点と、どの直し方を選ぶかの判断表

2026年11月5日更新|Node.js 22.23.4・Jest 30.5.2/29.7.0・TypeScript 6.0.3・Chromium 153で実行確認

この記事で実際に動かしたもの:Node.js 22.23.4 で package.json・拡張子・フラグを変えた20個以上の小さなプロジェクト、Jest 30.5.2 と 29.7.0、TypeScript 6.0.3(tsc)・ts-node 10.9.2・tsx 4.23.12・ts-jest 29.4.15、127.0.0.1 の自作サーバーで配信したページを開いた Chromium 153。

エラーが出た場所で直し方が分かれます。まず「どこで出たか」を決めると、読む節が1つに絞れます。

flowchart LR
    W{import で<br>SyntaxError<br>どこで出た?} -->|node| N[type を module<br>か .mjs に]
    W -->|ブラウザ| B[script に<br>type=module]
    W -->|Jest| J[Babel で変換<br>か ESM モード]
    W -->|tsc| T[module を<br>nodenext に]

やりたいことごとに、いちばん早い方法を先にまとめます。

やりたいこと いちばん早い方法 詳しい節
node app.js で出た package.json に "type": "module" を足す Node.jsで出る原因1
package.json を変えられない そのファイルだけ .mjs にする・import() で読む Node.jsで直す4つの方法
ブラウザのコンソールで出た script タグに type="module" を付ける ブラウザで出る原因と直し方
Jest で出た(自分のテスト) @babel/preset-env の babel.config.js を置く Jestで出る原因と直し方
Jest で出た(node_modules の中) transformIgnorePatterns でそのパッケージだけ変換する Jestで出る原因と直し方
tsc でビルドした JS で出た tsconfig の module を nodenext にする TypeScriptで出る原因と直し方
ts-node で出た・何も表示されない tsx で動かす、または module を CommonJS 系にする TypeScriptで出る原因と直し方
直したら別のエラーが出た require・__dirname・拡張子を ESM の書き方に直す 直したあとに出る近いエラー

確認環境:Ubuntu 24.04.5 LTS の上で、Node.js 22.23.4(npm 10.9.9)を使い、作業用のフォルダーに package.json の "type" の有無・値、拡張子(.js・.mjs・.cjs・.ts)、--no-experimental-detect-module などのフラグを変えた小さなプロジェクトを作って、エラーの原文と直したあとの出力を記録しました。テストは Jest 30.5.2(babel-jest 30.5.2・@babel/core 8.1.0・@babel/preset-env 8.0.7)と Jest 29.7.0、TypeScript は typescript 6.0.3(tsc)・ts-node 10.9.2・tsx 4.23.12・ts-jest 29.4.15 で実行しています。ブラウザは Python 3.13.18 の http.server を 127.0.0.1 に立ててページを配信し、Playwright 1.63.0 の Chromium 153.0.8010.12 で開いてコンソールの表示を集めました。Node.js 20 系など別の版の挙動は、nodejs.org からの取得がこの環境では許可されなかったため、Node.js 公式ドキュメントの版の履歴で確かめています。Firefox・Safari の文言は MDN、Jest の ESM 対応は Jest 公式ドキュメント(30.5)、TypeScript の module 設定は TypeScript 公式ハンドブックで確かめました。図は mermaid 11.16.0 で描画を確かめています(確認日 2026年11月5日)。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Cannot use import statementの原因と直し方|Jest・TS」

Cannot use import statement outside a moduleの意味(エラー原文)

この節では、エラー文がそれぞれの言葉で何を言っているのかと、実際にどう表示されるのかが分かります。

直訳すると「モジュールの外では import 文を使えません」です。JavaScript には、ファイルを読み込む仕組みが2つあります。import / export を使う ES モジュール(ESM)と、require() / module.exports を使う CommonJS(CJS)です。import 文は ESM の中でしか書けないので、実行する側がそのファイルを CommonJS や普通のスクリプトとして読み込むと、1行目の import で構文エラー(SyntaxError)になります。

Node.js 22.23.4 で、package.json に "type": "commonjs" があるフォルダーの app.js に import を書いて実行すると、次のように出ました。

$ node app.js
(node:9016) Warning: Failed to load the ES module: /dev/shm/t285/lab/n2/app.js. Make sure to set "type": "module" in the nearest package.json file or use the .mjs extension.
(Use `node --trace-warnings ...` to show where the warning was created)
/dev/shm/t285/lab/n2/app.js:1
import { add } from './math.js';
^^^^^^

SyntaxError: Cannot use import statement outside a module
    at wrapSafe (node:internal/modules/cjs/loader:1713:18)
    at Module._compile (node:internal/modules/cjs/loader:1755:20)
    ...
Node.js v22.23.4

読むところは3つです。

  • Warning の行:Node.js 22 では、エラーの前に直し方そのものが表示されました。
  • ^^^^^^ の位置:どのファイルの import でつまずいたか。自分のファイル・node_modules・設定ファイルのどれかで直す場所が変わります。
  • スタックの cjs/loader:CommonJS の読み込み器(loader)で止まった、つまり CommonJS として読まれたという証拠です。

ブラウザでは同じ原因でも文言が違います。MDN は次の3つを挙げています。

実行する環境 表示される文言
Chrome・Edge・Node.js(V8) SyntaxError: Cannot use import statement outside a module
Firefox SyntaxError: import declarations may only appear at top level of a module
Safari SyntaxError: Unexpected identifier 'x'. import call expects one or two arguments.

Firefox の文言は「import 宣言はモジュールの最上位にだけ書ける」という意味で、if 文や関数の中に import 文を書いたときにも出ます。その場合は import()(動的インポート)を使います。

原因の一覧表と、最初に確かめる3つのこと

この節では、このエラーの原因が環境ごとに何通りあるかと、どれに当たるかをすぐに確かめる方法が分かります。

原因は「import を書いたファイル」と「そのファイルをどう読むかの設定」が食い違っていることです。手元で再現できた原因を、環境別にまとめました。

環境 原因 手元の結果 直し方
Node.js package.json が "type": "commonjs" このエラー "type": "module"・.mjs
Node.js 拡張子が .cjs このエラー .mjs にする・require に書き換える
Node.js 構文の自動判定が無い版・フラグで無効 このエラー "type" を明示する
ブラウザ script タグに type="module" が無い このエラー type="module" を付ける
ブラウザ 普通の script の中に import を直書き このエラー type="module" を付ける
Jest 29 テストを CommonJS に変換していない このエラー Babel・ESM モード
Jest 30 同上・node_modules の ESM Must use import to load ES Module Babel・transformIgnorePatterns
Jest jest.config.js を ESM で書いた このエラー jest.config.mjs にする
TypeScript module: ESNext で出力して node で実行 このエラー module: nodenext
TypeScript node で .ts を直接実行(CommonJS 扱い) このエラー "type": "module"・.mts

どれに当たるかは、次の3つで確かめられます(手元で実行した結果)。

1

Node.js の版を見る

node -v を実行します。v22.7.0 以降・v20.19.0 以降なら、"type" が無いときに import を見つけて ESM として読み直す自動判定が既定で動きます。それより前の版では、"type" が無い .js は必ず CommonJS です。

ターミナルで1行

2

package.json の "type" を見る

プロジェクトのフォルダーで npm pkg get type を実行します。手元では "module"・"commonjs" のどちらか、書いていなければ {} と表示されました。いちばん近い親フォルダーの package.json が効きます。

ターミナルで1行

3

そのファイルがどちらで動くかを見る

同じフォルダーに console.log(typeof module === 'undefined' ? 'ESM' : 'CommonJS'); だけを書いた which.js を置いて node which.js を実行します。"type": "module" のフォルダーでは ESM、"commonjs" と "type" 無しでは CommonJS と表示されました。

ファイル1つ

ブラウザでは、開発者ツール(F12)の Elements タブで script タグに type="module" があるかを見ます。

Node.jsで出る原因1:package.jsonの"type"がcommonjs

この節では、Node.js で最も多い原因である package.json の "type" と、直し方・直したあとの出力が分かります。

Node.js は、.js ファイルを ESM と CommonJS のどちらで読むかを、いちばん近い package.json の "type" で決めます。"commonjs" と書いてあれば、その下の .js はすべて CommonJS です。公式ドキュメントは、"type" が無い場合も .js は CommonJS として扱うと書いています(ただし後で説明する自動判定があります)。

再現に使ったのは、次の2つのファイルです。

// math.js
export const add = (a, b) => a + b;

// app.js
import { add } from './math.js';
console.log('1 + 2 =', add(1, 2));
{
  "name": "n2",
  "type": "commonjs"
}

この状態で node app.js を実行すると、前の節の原文どおり SyntaxError になりました。package.json の "type" を "module" に変えると、何も書き換えずに動きました。

$ cat package.json
{
  "name": "f2",
  "type": "module"
}
$ node app.js
1 + 2 = 3

モノレポ(1つのリポジトリに複数のパッケージ)では、上の階層の別の package.json の "type" が効いていることがあります。npm pkg get type はファイルのあるフォルダーで実行します。

"type": "module" に変える前に見ること

やってはいけない他のファイルが require() や __dirname を使っているのに、"type" だけを module に変えて終わりにする
→
代わりにgrep で require( と __dirname を探し、ESM の書き方に直すか、そのファイルだけ .cjs にする

"type" はフォルダーの下の .js すべてに効く

ESM と CommonJS の仕組みの違い(読み込み方の対応表・版ごとの挙動)は、関連記事「Node.js ESMとCommonJSの違い|import混在の解決表」で扱っています。この記事はエラーが出たときの入口として、原因の切り分けと直し方に絞ります。

Node.jsで出る原因2:.cjs・自動判定の有無・require との混在

この節では、"type" 以外で Node.js がファイルを CommonJS として読む場合と、版による違い、自動判定にかかる時間が分かります。

拡張子 .cjs のファイルは、package.json に関係なく必ず CommonJS です。.cjs の中に import 文を書くと、どの設定でも同じエラーになりました。

$ node app.cjs
(node:9040) Warning: Failed to load the ES module: /dev/shm/t285/lab/n4/app.cjs. Make sure to set "type": "module" in the nearest package.json file or use the .mjs extension.
/dev/shm/t285/lab/n4/app.cjs:1
import { add } from './math.mjs';
^^^^^^

SyntaxError: Cannot use import statement outside a module

"type" が無いときは自動判定がある(Node.js 22.7 以降・20.19 以降)

Node.js の公式ドキュメントでは、"type" が無い(または package.json が無い).js は「あいまいな入力」とされ、CommonJS として読めないときに import・export などの ESM の構文を見つけると、ESM として読み直します。この構文の自動判定(Syntax detection)は v22.7.0・v20.19.0 で既定で有効になりました。手元の 22.23.4 で "type" の無い package.json のフォルダーで実行すると、エラーにはならず、代わりに警告が出ました。

$ node app.js
1 + 2 = 3
(node:9024) [MODULE_TYPELESS_PACKAGE_JSON] Warning: Module type of file:///dev/shm/t285/lab/n3/app.js is not specified and it doesn't parse as CommonJS.
Reparsing as ES module because module syntax was detected. This incurs a performance overhead.
To eliminate this warning, add "type": "module" to /dev/shm/t285/lab/n3/package.json.

つまり同じコードでも、Node.js 20.18 以前や 22.6 以前ではこのエラーになり、22.7 以降では動きます。「手元では動くのに CI(自動テストのサーバー)ではエラー」なら版の違いを疑います。自動判定を切る --no-experimental-detect-module を付けると、以前の版と同じくエラーになりました。

$ node --no-experimental-detect-module app.js
/dev/shm/t285/lab/n1/app.js:1
import { add } from './math.js';
^^^^^^

SyntaxError: Cannot use import statement outside a module

自動判定に頼ると遅くなるか(実測)

警告に「performance overhead(処理の余分な負担)」とあるので、起動にかかる時間を測りました。各条件で23回起動し、最初の3回を除いた中央値です。

条件 ファイル 起動の中央値
"type": "module" 2ファイル・数行 34.1 ms
"type" 無し(自動判定) 2ファイル・数行 33.8 ms
拡張子 .mjs 2ファイル・数行 32.9 ms
"type": "module" 約1.4MB・3万行のモジュール 150.7 ms・152.7 ms
"type" 無し(自動判定) 約1.4MB・3万行のモジュール 169.0 ms・173.3 ms
約0ms小さなファイルでの差測定のばらつきの範囲
約19ms1.4MB のモジュールでの差2回の測定の平均で約12%遅い
2回ファイルを読む回数CommonJS で失敗してから ESM で読み直す

大きなファイルでは読み直しの分だけ遅くなりました。公式ドキュメントも、CommonJS だけのパッケージでも "type" を書くよう勧めています。自動判定はエラーを避ける助けにはなりますが、"type" を明示するのが確実です。

require と import を1つのファイルに混ぜた場合

"type" の無いフォルダーで、1つのファイルに require と import を両方書くと、自動判定で ESM として読み直された結果、今度は require が使えなくなりました。

$ node app.js
file:///dev/shm/t285/lab/n5/app.js:1
const fs = require('node:fs');
           ^

ReferenceError: require is not defined in ES module scope, you can use import instead

1つのファイルの中では、どちらか一方にそろえます(直し方は「直したあとに出る近いエラー」の節)。

Node.jsで直す4つの方法(type・.mjs・import()・require)

この節では、Node.js でこのエラーを直す方法を4つ、それぞれ直したあとの出力つきで比べられます。

プロジェクト全体を ESM にするなら "type": "module"、一部だけなら .mjs、CommonJS のまま ESM を使うなら import() か require() です。4つとも手元で動きました。

1

"type": "module"

package.json に1行足す。フォルダーの下の .js がすべて ESM になる。

新しいプロジェクト向け

2

拡張子を .mjs

そのファイルだけ ESM にする。import する側も ./math.mjs と書く。

一部だけ ESM にしたい

3

import() で読む

CommonJS のファイルから、動的インポートで ESM を読む。結果は Promise で返る。

どの版でも使える

4

require() で読む

Node.js 22.12 以降・20.19 以降は、require() で ESM を読める。

トップレベル await は不可

方法2:拡張子を .mjs にする

"type": "commonjs" のまま、ファイルを .mjs に変えると動きました。

$ cat app.mjs
import { add } from './math.mjs';
console.log('1 + 2 =', add(1, 2));
$ node app.mjs
1 + 2 = 3

方法3:CommonJS のまま import() で読む

import 文(静的インポート)は ESM でしか書けませんが、import()(動的インポート)は CommonJS の中でも使えます。公式ドキュメントも、import() はファイルを ESM 扱いにする理由にならないと書いています。

// app.js("type": "commonjs" のまま)
(async () => {
  const { add } = await import('./math.mjs');
  console.log('1 + 2 =', add(1, 2));
})();
$ node app.js
1 + 2 = 3

方法4:require() で ESM を読む(Node.js 22.12 以降)

Node.js の公式ドキュメントでは、require() で ESM を読む機能は v22.12.0・v20.19.0 でフラグ無しで使えるようになり、v22.13.0 以降は実験的機能の警告も出なくなりました。手元の 22.23.4 では、警告なしで動きました。

$ cat req.js
const { add } = require('./math.mjs');
console.log('1 + 2 =', add(1, 2));
$ node req.js
1 + 2 = 3
$ node -p "process.features.require_module"
true

ただし、読み込む ESM(またはその先)にトップレベル await(関数の外の await)があると、別のエラーになりました。この場合は import() を使います。

Error [ERR_REQUIRE_ASYNC_MODULE]: require() cannot be used on an ESM graph with top-level await. Use import() instead. To see where the top-level await comes from, use --experimental-print-required-tla.
  From /dev/shm/t285/lab/f4/req2.js
  Requiring /dev/shm/t285/lab/f4/tla.mjs
  code: 'ERR_REQUIRE_ASYNC_MODULE'

node -e で1行だけ試すとき

node -e に渡した文字列も「あいまいな入力」なので、22.23.4 では import を含む1行がそのまま動きました。どの版でも確実に ESM として動かすなら --input-type=module を付けます。

$ node --input-type=module -e "import os from 'node:os'; console.log(os.platform())"
linux

ブラウザで出る原因と直し方(script type="module")

この節では、ブラウザのコンソールにこのエラーが出る原因と、直したあとに出やすい3つのエラーが分かります。

ブラウザでは、<script> タグに type="module" が無いと、そのファイルは普通のスクリプトとして読まれ、import 文でエラーになります。127.0.0.1 の自作サーバーで次のページを配信し、Chromium 153 で開きました。

<!-- b1.html:エラーになる -->
<p id="out">-</p>
<script src="app.js"></script>

<!-- b2.html:動く -->
<p id="out">-</p>
<script type="module" src="app.js"></script>

Playwright でページのエラーとコンソールを集めた結果です。b2 では app.js が math.js を読み込み、画面の文字が書き換わりました。

=== b1 out=-
[pageerror] SyntaxError: Cannot use import statement outside a module
=== b4 out=-          (普通の script タグの中に import を直接書いた)
[pageerror] SyntaxError: Cannot use import statement outside a module
=== b2 out=1 + 2 = 3

Chrome のコンソールでは、文言の前に「Uncaught」が付いて赤く表示されます。type="module" を付けると、次の2つが変わります。

  • file:// では動かない:HTML ファイルをダブルクリックで開くと CORS のエラーになります。
  • サーバーの Content-Type が厳しく見られる:JavaScript の MIME タイプで返さないと読み込まれません。

直したあとに出たエラー(file://・MIME・裸の指定子)

手元で再現した原文です。

=== file:///.../b2.html を直接開いた
[console.error] Access to script at 'file:///dev/shm/t285/lab/web/app.js' from origin 'null' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: chrome, chrome-untrusted, data, http, https.
[console.error] Failed to load resource: net::ERR_FAILED

=== b7:text/plain で配信したファイルを type="module" で読んだ
[console.error] Failed to load module script: Expected a JavaScript-or-Wasm module script but the server responded with a MIME type of "text/plain". Strict MIME type checking is enforced for module scripts per HTML spec.

=== b5:import confetti from 'confetti-lib'(パッケージ名だけで import)
[pageerror] TypeError: Failed to resolve module specifier "confetti-lib". Relative references must start with either "/", "./", or "../".
出たエラー 原因 直し方
blocked by CORS policy(origin 'null') file:// で開いた ローカルサーバー(npx vite・python -m http.server など)で開く
Failed to load module script … MIME type サーバーが .js を text/plain などで返した text/javascript で返すようサーバーを設定する
Failed to resolve module specifier パッケージ名だけの import はブラウザでは解決できない import map を書く・Vite などのバンドラーを使う

パッケージ名だけの import は、import map(どの名前をどのファイルに対応させるかの表)を書くと動きました。

<script type="importmap">
{ "imports": { "confetti-lib": "./vendor/confetti.js" } }
</script>
<script type="module" src="bare.js"></script>
=== b6 out=undefined
[console.log] confetti-lib loaded

Web Worker では、MDN のとおり new Worker('worker.js', { type: 'module' }) と指定します。ブラウザでの import / export と import map の書き方は、関連記事「JavaScript import/export入門|ESMとCommonJSの違い」で扱っています。

Jestで出る原因と直し方(Jest 29と30で文言が違う)

この節では、Jest でこのエラーが出る3つの場面と、Babel・transformIgnorePatterns・ESM モードの3つの直し方が分かります。

Jest は、標準ではテストファイルを CommonJS として実行します。import を書いたテストを CommonJS に変換する設定が無いと失敗します。同じテストを Jest 29.7.0 と Jest 30.5.2 で動かすと、表示が変わっていました。

// math.test.js
import { add } from './math.js';

test('add', () => {
  expect(add(1, 2)).toBe(3);
});

Jest 29.7.0 では、よく検索されるこの形でした。

FAIL a/math.test.js
  ● Test suite failed to run

    Jest encountered an unexpected token

    Jest failed to parse a file. This happens e.g. when your code or its dependencies use non-standard JavaScript syntax, or when Jest is not configured to support such syntax.
    ...
    Details:

    /dev/shm/t285/lab/jest29/a/math.test.js:1
    ({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,jest){import { add } from './math.js';
                                                                                      ^^^^^^

    SyntaxError: Cannot use import statement outside a module

Jest 30.5.2 では、同じ原因で次の文言になり、直し方が3つ示されました。

FAIL j1/math.test.js
  ● Test suite failed to run

    Must use import to load ES Module: /dev/shm/t285/lab/tools/j1/math.test.js

    The file contains ESM syntax (import/export) that could not be executed as CommonJS. Either:
      - Configure a transform (e.g. babel-jest) that compiles this file to CommonJS (see https://jestjs.io/docs/code-transformation)
      - If the file is in "node_modules", allow it to be transformed by adjusting "transformIgnorePatterns" (see https://jestjs.io/docs/configuration#transformignorepatterns-arraystring)
      - Use Node v24.9+ where Jest supports require(esm) natively (see https://jestjs.io/docs/ecmascript-modules#require-of-esm)

文言は違っても、原因と直し方は同じです。Jest 30 で「Cannot use import statement outside a module」と検索しても見つからないときは、この「Must use import to load ES Module」で探すと近い情報が見つかります。

直し方1:Babel で CommonJS に変換する(いちばん手軽)

Jest に同梱される babel-jest は、Babel の設定ファイルがあればそれを使って変換します。@babel/core と @babel/preset-env を入れ、次のファイルを置くと通りました。

npm i -D babel-jest @babel/core @babel/preset-env
// babel.config.js
module.exports = {
  presets: [['@babel/preset-env', { targets: { node: 'current' } }]],
};
PASS j2/math.test.js
  ✓ add (7 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total

直し方2:node_modules の ESM パッケージだけ変換する

自分のテストは変換できていても、import した先のパッケージが ESM だけで配布されていると、node_modules の中のファイルで止まります。Jest は標準で node_modules を変換しないためです。ESM だけのパッケージを作業用に用意して再現すると、止まった場所が node_modules の中と表示されました。

    Must use import to load ES Module: /dev/shm/t285/lab/tools/node_modules/esm-only/index.js
    ...
    > 1 | import { greet } from 'esm-only';
        | ^

transformIgnorePatterns(変換しないファイルの条件)を書き換え、そのパッケージだけ変換の対象に戻すと通りました。複数あるときは (?!pkg-a|pkg-b) のように並べます。

// jest.config.js
module.exports = {
  transformIgnorePatterns: ['/node_modules/(?!esm-only/)'],
};
PASS j3/greet.test.js
  ✓ greet (3 ms)

直し方3:Jest を ESM のまま動かす

Jest 公式ドキュメント(30.5)では、変換を止める transform: {} と、Node.js の --experimental-vm-modules を組み合わせると ESM のまま実行できると書かれています。フラグなしでは失敗し、付けると通りました(実験的機能の警告は出ます)。

// jest.config.js(package.json は "type": "module")
export default { transform: {} };
$ NODE_OPTIONS=--experimental-vm-modules npx jest
(node:12064) ExperimentalWarning: VM Modules is an experimental feature and might change at any time
PASS j4/math.test.js
  ✓ add (3 ms)

ESM モードでは jest.mock が ESM に効かず、公式ドキュメントは jest.unstable_mockModule を案内しています。モックが多いテストは直し方1が向きます。Windows で NODE_OPTIONS を付けるときは、Jest 公式ドキュメントが cross-env を使う方法を案内しています。

設定ファイル(jest.config.js)で出る場合

jest.config.js を export default で書き、package.json が "type": "commonjs" だと、テストより前に設定ファイルの読み込みで同じエラーになりました。拡張子を .mjs に変える(jest.config.mjs)と通りました。next.config.js・eslint.config.js など、他のツールの設定ファイルでも考え方は同じで、エラーの ^^^^^^ が設定ファイルを指していないかを見ます。

/dev/shm/t285/lab/tools/jc/jest.config.js:1
import os from 'node:os';
^^^^^^

SyntaxError: Cannot use import statement outside a module

TypeScriptで出る原因と直し方(tsc・ts-node・tsx・ts-jest)

この節では、TypeScript のプロジェクトでこのエラーが出る原因と、tsconfig の module の選び方、実行の道具ごとの違いが分かります。

TypeScript では、tsconfig.json の module が出力する JavaScript の形を決めます。module を ESNext にすると出力に import 文が残り、package.json が "type": "commonjs" のまま node で実行するとエラーになりました。

$ cat tsconfig.json   (抜粋)
    "module": "ESNext",
    "moduleResolution": "bundler",
$ npx tsc && head -1 dist/app.js
import { add } from './math';
$ node dist/app.js
/dev/shm/t285/lab/tools/ts1/dist/app.js:1
import { add } from './math';
^^^^^^

SyntaxError: Cannot use import statement outside a module

TypeScript 公式ハンドブックは、Node.js で動かすアプリ・ライブラリには module を node16・node18・nodenext のどれかにするよう勧め、esnext は「Node.js には使わない(バンドラー・Bun・tsx 向け)」としています。nodenext にすると、package.json の "type" に合わせて出力の形が自動で決まりました。

tsconfig の module package.json の type 出力の1行目 node で実行
ESNext commonjs import { add } from './math'; このエラー
NodeNext commonjs "use strict"(require に変換) 1 + 2 = 3
NodeNext module(拡張子なし) tsc がエラー TS2835 ビルドできない
NodeNext module(./math.js と書く) import { add } from './math.js'; 1 + 2 = 3

"type": "module" で nodenext にすると、相対パスの拡張子を省いた import を tsc が止めました。.ts のファイルを読む場合でも、import には出力後の名前(./math.js)を書きます。

src/app.ts(1,21): error TS2835: Relative import paths need explicit file extensions in ECMAScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Did you mean './math.js'?

なお TypeScript 6.0.3 では、CommonJS の出力に以前よく使われた "moduleResolution": "node10" を指定すると、次のエラーでビルドが止まりました。CommonJS で出力したい場合も、module を NodeNext にして package.json を "type": "commonjs" にする組み合わせが、手元では警告なしで通りました。

tsconfig.json(5,25): error TS5107: Option 'moduleResolution=node10' is deprecated and will stop functioning in TypeScript 7.0. Specify compilerOption '"ignoreDeprecations": "6.0"' to silence this error.

node で .ts を直接実行した場合(型ストリップ)

Node.js 22.18.0 以降は、型の注釈を取り除いて .ts を直接実行できます(公式ドキュメントでは v23.6.0・v22.18.0 で既定で有効)。このとき .ts も .js と同じ規則で ESM か CommonJS かが決まり、tsconfig.json は読まれません。"type": "commonjs" のフォルダーで node src/app.ts を実行すると、同じエラーになりました。公式ドキュメントのとおり、"type": "module" にするか、拡張子を .mts(常に ESM)にします。

ts-node・tsx・ts-jest の違い

道具 module: ESNext・type: commonjs での結果 手元での直し方
ts-node 10.9.2 何も表示されず終了コード0(app.ts が実行されなかった) module を CommonJS 系にすると「1 + 2 = 3」
ts-node(型ストリップを切った場合) TypeError: Unknown file extension ".ts" 同上
tsx 4.23.12 1 + 2 = 3(設定を変えずに動いた) なし
ts-jest 29.4.15 PASS(ESNext のままでも通った) なし

「エラーは出ないのに console.log が何も表示されない」ときも、ESM と CommonJS の食い違いを疑います。手元で設定を変えずに動いたのは tsx でした。TypeScript を Node.js で動かす道具の比較は関連記事「TypeScriptをNode.jsで実行|tsx・ts-node・型ストリップ」で、tsconfig の各項目の推奨値は関連記事「tsconfig設定一覧|TypeScript 7対応の推奨値とテンプレート」で扱っています。

直したあとに出る近いエラーの見分け方

この節では、このエラーを直した直後に出やすい別のエラーと、それぞれが何を意味するかが分かります。

ESM にそろえると、CommonJS でだけ使えたものが使えなくなります。手元で出た順に並べました。

出たエラー(原文の要点) 意味 直し方
ReferenceError: require is not defined in ES module scope ESM の中で require を使った import に書き換える・createRequire
ReferenceError: __dirname is not defined in ES module scope ESM には __dirname・__filename が無い import.meta.dirname・import.meta.filename
Error [ERR_MODULE_NOT_FOUND]: Cannot find module …/math ESM では相対パスの拡張子を省けない './math.js' と拡張子まで書く
Error [ERR_REQUIRE_ESM] require() で ESM を読めない版 Node.js 22.12 以降・20.19 以降にする・import()
Error [ERR_REQUIRE_ASYNC_MODULE] require() した ESM にトップレベル await import() で読む
error TS2835 nodenext で拡張子を省いた './math.js' と書く

__dirname のエラーは、Node.js 22.23.4 では理由まで表示されました。

ReferenceError: __dirname is not defined in ES module scope
This file is being treated as an ES module because it has a '.js' file extension and '/dev/shm/t285/lab/f2/package.json' contains "type": "module". To treat it as a CommonJS script, rename it to use the '.cjs' file extension.
// ESM での書き換え(Node.js 20.11 以降・22.16 以降は実験的機能ではない)
console.log(import.meta.dirname, import.meta.filename);
// 出力:/dev/shm/t285/lab/f2 /dev/shm/t285/lab/f2/d2.js

拡張子を省くと、正しい名前が提案されました。

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/dev/shm/t285/lab/f5/math' imported from /dev/shm/t285/lab/f5/app.js
Did you mean to import "./math.js"?

require is not defined・__dirname is not defined は、ブラウザで require を使った場合なども含めて、別記事「ReferenceError: X is not defined の原因と直し方」で詳しく扱います。パッケージそのものが見つからないエラー(Cannot find module 'express' など)は、関連記事「Node.js Cannot find moduleの原因と直し方」で扱っています。

このエラー(import を CommonJS で読んだ)

SyntaxError で1行目の import を指す

スタックに cjs/loader が出る

ESM として読ませる設定で直る

逆向きのエラー(require を ESM で使った)

ReferenceError で require・__dirname を指す

URL が file:/// で始まる

import・import.meta に書き換えて直る

OS・Node.jsの版・実行環境別の違い

この節では、Windows・Mac・Linux、Node.js の版、よく使うツールごとに、このエラーの出方が変わるかが分かります。

OS でこのエラーの出方は変わりません。変わるのは Node.js の版です。ESM と CommonJS の判定は Node.js の中で行われるため、Windows・Mac・Linux のどれでも同じ規則です。環境変数の付け方はシェルで違い、Jest 公式ドキュメントは Windows 向けに cross-env を案内しています。

版による違いは、Node.js 公式ドキュメントの履歴で確かめました。

  1. 1v20.10.0・v21.1.0:構文の自動判定がフラグ付きで追加この時点では既定で無効
  2. 2v20.11.0・v21.2.0:import.meta.dirname・filename が追加v22.16.0・v24.0.0 で実験的機能ではなくなった
  3. 3v22.7.0・v20.19.0:構文の自動判定が既定で有効"type" 無しの .js に import があっても動く
  4. 4v22.12.0・v20.19.0:require() で ESM を読める(フラグ不要)v22.13.0 で警告も出なくなった
  5. 5v22.18.0・v23.6.0:.ts の型ストリップが既定で有効.ts も "type" で ESM か CommonJS かが決まる

Jest 公式ドキュメントでは、Jest が CommonJS のコードからの require() による ESM 読み込みに対応するのは Node.js v24.9 以降と書かれています。手元の Node.js 22.23.4 では、前の節のとおり Babel などの変換が要りました。

CI と手元で結果が違うときは、CI の設定ファイルで指定している Node.js の版と、手元の node -v を並べて見ます。手元の版をそろえる道具は、関連記事「Node.js バージョン管理|nvm・fnm・mise・Volta比較と実測」で扱っています。

よくあるエラー/つまずき一覧(原文つき)

この節では、このエラーまわりで手元で出た原文と、その原因・直し方を1つの表で引けます。

表示された原文 場面 原因 直し方
SyntaxError: Cannot use import statement outside a module node app.js "type": "commonjs"・.cjs "type": "module"・.mjs
Warning: Failed to load the ES module … Make sure to set "type": "module" node(エラーの直前) 同上 表示どおりに直す
[MODULE_TYPELESS_PACKAGE_JSON] Warning: … Reparsing as ES module node(動くが警告) "type" が無い "type": "module" を書く
Uncaught SyntaxError: Cannot use import statement outside a module Chrome のコンソール type="module" が無い script に type="module"
Jest encountered an unexpected token … Cannot use import statement Jest 29 変換していない babel.config.js
Must use import to load ES Module Jest 30 変換していない・node_modules の ESM Babel・transformIgnorePatterns
TypeError: Unknown file extension ".ts" ts-node ESM の .ts を読めない tsx・module を CommonJS 系に
Access to script … blocked by CORS policy(origin 'null') file:// で開いた module は file:// で読めない ローカルサーバーで開く
  • 1^^^^^^ が指すファイルを見る自分のコード・node_modules・設定ファイルのどれかで、直す場所が変わる
  • 2node -v と npm pkg get type を控える版と "type" の2つで、Node.js の原因はほぼ絞れる
  • 3直したら require・__dirname を探すESM にそろえた直後のエラーの多くは、この2つが原因だった
  • 4CI と手元の Node.js の版をそろえる自動判定の有無で、同じコードが動いたり止まったりする

判断表:どの直し方を選ぶか

この節では、自分の状況に合う直し方を1つ選べます。

迷ったら「プロジェクト全体を ESM にしてよいか」で分けます。よいなら "type": "module"、そうでないなら .mjs か import() です。

状況 選ぶ直し方 理由
新しく作る Node.js のプロジェクト "type": "module" 1行で済み、自動判定の読み直しも無くなる
require を多く使う既存のプロジェクト import を書くファイルだけ .mjs 他のファイルに影響しない
CommonJS のまま ESM だけのパッケージを使う import()(Node.js 22.12 以降なら require も可) 書き換えが最小で済む
HTML から読み込むファイル script に type="module" ブラウザではこれ以外に方法がない
Jest のテスト(モックを多く使う) babel.config.js jest.mock がそのまま使える
Jest で node_modules の中で止まる transformIgnorePatterns 必要なパッケージだけ変換できる
TypeScript を tsc でビルドして node で動かす module: nodenext "type" に合わせて出力が決まる
TypeScript をビルドせずに動かしたい tsx 手元では設定を変えずに動いた

よくある質問

この節では、このエラーについてよく調べられる疑問に、手元の結果をもとに短く答えます。

Q. Cannot use import statement outside a module とはどういう意味ですか? A. 「モジュール(ES モジュール)の外では import 文を使えない」という意味です。import を書いたファイルが CommonJS や普通のスクリプトとして読み込まれたときに出ます。

Q. package.json に "type": "module" を書けば必ず直りますか? A. node で .js を実行して出た場合は、手元ではこれで直りました。.cjs・ブラウザ・Jest・tsc の出力には効かず、require や __dirname を使うファイルは別のエラーになります。

Q. "type" を書いていないのに、手元では動いて CI でだけエラーになります。 A. Node.js の版の違いが考えられます。v22.7.0・v20.19.0 以降は "type" が無いと構文を自動判定して ESM として読み直すため動きますが、それより前の版ではエラーになります。"type" を明示すると、版によらず同じ結果になります。

Q. .mjs と "type": "module" はどちらがよいですか? A. プロジェクト全体を ESM にするなら "type": "module"、一部のファイルだけ ESM にしたいなら .mjs です。逆に "type": "module" の中で CommonJS のファイルを残すときは .cjs にします。

Q. ブラウザで type="module" を付けたら、今度は CORS のエラーが出ました。 A. HTML ファイルを直接(file://)開いていないかを確かめます。module のスクリプトは file:// では読み込めず、手元でも origin 'null' の CORS エラーになりました。ローカルサーバーを立てて http:// で開くと動きます。

Q. Jest 30 では「Cannot use import statement」が出ません。別の問題ですか? A. 同じ問題です。手元の Jest 30.5.2 では「Must use import to load ES Module」と表示され、Jest 29.7.0 では「Cannot use import statement outside a module」でした。直し方(Babel・transformIgnorePatterns・ESM モード)も同じです。

Q. 自分のテストは通るのに、node_modules の中で止まります。 A. import したパッケージが ESM だけで配布されていて、Jest が node_modules を変換していないためです。transformIgnorePatterns でそのパッケージだけ変換の対象に戻すと、手元では通りました。

Q. TypeScript の tsconfig は module を何にすればよいですか? A. Node.js で動かすなら nodenext です。TypeScript 公式ハンドブックも Node.js には node16・node18・nodenext を勧めています。バンドラー(Vite など)を通すフロントエンドなら esnext(または preserve)と moduleResolution: bundler の組み合わせです。

Q. ts-node を実行しても何も表示されません。 A. 手元の ts-node 10.9.2(Node.js 22.23.4・TypeScript 6.0.3)では、module: ESNext と "type": "commonjs" の組み合わせで、エラーも出力も出ずに終わりました。module を CommonJS 系にすると動き、tsx は設定を変えずに動きました。

まとめ

この節では、記事の要点を5つにまとめます。

  • 原因は、import を書いたファイルが ESM ではなく CommonJS(普通のスクリプト)として読まれたこと。エラーの ^^^^^^ と cjs/loader がその証拠です。
  • Node.js は "type": "module" か .mjs、CommonJS のままなら import()。Node.js 22.12 以降は require() でも ESM を読めます。
  • ブラウザは script に type="module"。直したあとは file:// の CORS・MIME タイプ・パッケージ名の解決に注意します。
  • Jest は babel.config.js か transformIgnorePatterns、または ESM モード。Jest 30 では「Must use import to load ES Module」と表示が変わりました。
  • TypeScript は module を nodenext に。"type" を明示すると、Node.js の版や自動判定によらず同じ結果になります。

参考・出典

確認日はいずれも 2026年11月5日です。

  • Node.js「Modules: Packages」Determining module system・Syntax detection・"type":https://nodejs.org/api/packages.html
  • Node.js「Modules: CommonJS modules」Loading ECMAScript modules using require():https://nodejs.org/api/modules.html
  • Node.js「Modules: ECMAScript modules」import.meta.dirname・Mandatory file extensions:https://nodejs.org/api/esm.html
  • Node.js「Modules: TypeScript」Type stripping:https://nodejs.org/api/typescript.html
  • MDN「SyntaxError: import declarations may only appear at top level of a module」:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/import_decl_module_top_level
  • Jest「ECMAScript Modules」(30.5):https://jestjs.io/docs/ecmascript-modules
  • Jest「Configuring Jest」transformIgnorePatterns:https://jestjs.io/docs/configuration#transformignorepatterns-arraystring
  • Jest「Code Transformation」:https://jestjs.io/docs/code-transformation
  • TypeScript「Modules - Reference」module・moduleResolution:https://www.typescriptlang.org/docs/handbook/modules/reference.html

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Cannot use import statementの原因と直し方|Jest・TS」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

成長のヒミツ、 ぜんぶ丸わかり