Error [ReferenceError: fetch is not defined]

ReferenceError: fetch is not defined:古い Node で global fetch が無いときの直し方

FIX SUMMARY verified
Applies when
node:16.20.2-alpinecall to global fetch on Node < 18 (default)

Verified: reproduced in node:16.20.2-alpine, then the ReferenceError: fetch is not defined signature was gone after the fix (exit 0).

手元では動いていたコードを CI や本番の古い Node で走らせたら、fetch を呼んだ行で次のエラーが出て落ちることがあります。

ReferenceError: fetch is not defined
    at Object.<anonymous> (/app/index.js:1:23)

コピペ検索用に表記ゆれを挙げておきます。いずれも同じ原因です。

  • ReferenceError: fetch is not defined
  • fetch is not defined(ブラウザではなく Node 実行時に出たもの)

原因は、グローバルの fetch がフラグ無しの既定で使えるのが Node 18.0.0 からで、それより前の版では既定で fetch が定義されていないことです。ブラウザや新しい Node には最初からあるので、開発マシンでは通ります。CI や本番のベースイメージが Node 16 や 17 のままだと、その環境でだけ fetch is not defined になります。

これは典型的な版境界です。「手元では動くのに CI・古いコンテナでだけ落ちる」という食い違いは、ライブラリの入れ忘れではなく Node の版差が理由になっています。

いつから fetch が使えるのか

グローバルの fetchRequest / Response / Headers 含む)は、内部的に undici の実装を Node へ取り込む形で追加されました。フラグ無しの既定で使えるか、実験フラグが要るかは版によって分かれます。

Node の版既定の fetch実験フラグ(--experimental-fetch
16.14.x 以前無しフラグでも不可
16.15.0 〜 16.x無しフラグを付ければ有効化できる
17.0.0 〜 17.4.x無しフラグでも不可
17.5.0 〜 17.x無しフラグを付ければ有効化できる
18.0.0 以降既定で有効不要

fetch の実装は 16.15.0 と 17.5.0 でフラグ付きで入りましたが、フラグ無しでそのまま使えるのは 18.0.0 からです。この記事のように何も付けずに node index.js する限り、17.x までは既定で落ちます。ドキュメント上で「実験的」の扱いが外れたのは 21.0.0 ですが、これは使えるか落ちるかの境界(18.0.0)とは別の話です。

再現(最小構成)

package.json の有無やモジュール形式に関係なく、fetch を参照した時点で落ちます。次の1ファイルを置きます。

// index.js
const res = await fetch('https://example.com');
console.log(res.status);

これを Node 16 で node index.js すると ReferenceError: fetch is not defined が出て、終了コードは 1 になります。ネットワークに到達する前、fetch という名前を解決できない段階で落ちるので、通信の可否とは無関係です。Node 18 以降なら fetch は定義済みなので、この ReferenceError は出ません。

解決

やりたいことによって直し方が分かれます。

1. Node を 18 以降に上げる

fetch を使い続けるなら、実行する Node(CI・本番の Docker ベースイメージを含む)を 18.0.0 以降fetch がフラグ無しで入った版)に上げます。DockerfileFROM node:16 のような古い固定や、CI のバージョン設定(GitHub Actions の actions/setup-nodenode-version など)を見直してください。本番では、その時点でサポート中の LTS に揃えるのが安全です(EOL を過ぎた版はセキュリティ修正が止まります)。メジャー版をまたぐときは、他の依存パッケージが新しい版に対応しているかを併せて確認してください。

2. ポリフィルを入れる(古い Node を残す場合)

古い Node をサポート対象に残すなら、fetch 相当を提供するパッケージを使います。Node の fetch の実体でもある undicinode-fetch が広く使われています。

// index.js(undici の fetch を使う)
import { fetch } from 'undici';

const res = await fetch('https://example.com');
console.log(res.status);

グローバルの fetch を前提に書かれた別のコードにも通したいときは、globalThis.fetch へ代入して橋渡しできます。

import { fetch } from 'undici';

if (typeof globalThis.fetch !== 'function') {
  globalThis.fetch = fetch;
}

注意として、undici の新しめの版(v6 以降)は Node 18 以降を engines に要求します。つまり fetch が無くて困る主な対象=Node 16 以前では、最新の undici はインストールできません。Node 16 以前をポリフィルするなら、node-fetch(v2 系)を選ぶか、古い Node でも動く undici の旧版(v5 系)を明示的に指定してください。ここで入れる undici は npm パッケージで、Node 組み込みの fetch が内部に持つ undici とは別物です。

3. --experimental-fetch で有効化する(16.15〜17.x に固定せざるを得ないとき)

版を 16.15〜16.x か 17.5〜17.x に固定せざるを得ない事情があるなら、--experimental-fetch を付けて起動すると fetch が現れます。

node --experimental-fetch index.js

このフラグで有効化できるのは実装が入った 16.15.0 以降と 17.5.0 以降で、16.14 以前や 17.0〜17.4 では付けても現れません。いずれも一時しのぎで、これらの版はすべて EOL なので、恒久策としては 1(Node を上げる)か 2(ポリフィル)を選んでください。

切り分け(似ているが原因が違うもの)

  • fetch is not defined がブラウザ側で出た:この記事は Node 実行時の話です。ブラウザの fetch は別物で、主要ブラウザには存在します。スタックトレースに /app/index.js のようなファイルパスや node:internal/... が出ているなら Node 側の問題です。
  • 同じ版境界で別の API が落ちるfetch と同じく「新しい Node に組み込まれたグローバル」は、古い版で is not definedis not a function になります。たとえば structuredClone(Node 17+)、crypto.randomUUID(14.17/16+)、AbortController(15+)、Blob(グローバルは 18+)などです。エラーの出方は違っても、原因は「その版にその API がまだ無い」で共通します。対処も同じで、Node を上げるかポリフィルを入れるかの二択になります。structuredCloneReferenceError: structuredClone is not defined の直し方crypto.randomUUIDTypeError: crypto.randomUUID is not a function の直し方 にまとめています。
  • fetch は動くが ExperimentalWarning が出る:Node 18 の初期(18.0.0〜18.12.x)では、fetch() の初回呼び出し時に ExperimentalWarning: The Fetch API is an experimental feature. This feature could change at any time が標準エラー出力へ1度だけ出ます。この警告は 18.13.0 で削除されたので、それ以降の 18.x や Node 20 では出ません。ドキュメント上で「実験的」の扱いが外れた 21.0.0 とは別のタイミングです。警告を抑えたいだけなら --no-warnings(Node 20.11/21.3 以降なら種別を絞れる --disable-warning=ExperimentalWarning)で消せますが、--no-warnings はほかの警告も一緒に隠れる点に注意してください。
  • 組み込みモジュールを node: 付きで書いて落ちた:症状は違いますが「新しい書き方を古い Node が解釈できない」という同じ版境界です。Cannot find module 'node:fs' のパターンは node: プレフィックスが古い Node で通らないときの直し方 を参照してください。

検証(machine-verified)

この修正は node:16.20.2-alpine のバージョン固定コンテナ内で再現し、修正後に ReferenceError: fetch is not defined のシグネチャが消えることを機械で確認しています。

verify — run-case.mjs
$ node run-case.mjs node/fetch-not-defined
● reproduce ReferenceError: fetch is not defined present ✓
● apply fix exit 0
● re-run ReferenceError: fetch is not defined gone ✓
PASS verified · node:16.20.2-alpine · signature gone