Error [ReferenceError: structuredClone is not defined]

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

FIX SUMMARY verified
Applies when
node:16.20.2-alpinecall to global structuredClone on Node < 17

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

オブジェクトを深くコピーしようと structuredClone を呼んだら、古い Node で次のエラーが出て落ちることがあります。

ReferenceError: structuredClone is not defined
    at Object.<anonymous> (/app/index.js:2:16)

コピペ検索用の表記ゆれです。いずれも同じ原因です。

  • ReferenceError: structuredClone is not defined
  • structuredClone is not defined(Node 実行時に出たもの)

原因は、グローバルの structuredClone が組み込まれたのが Node 17.0.0 からで、それより前の版には存在しないことです。ブラウザや新しい Node には最初からあるので、開発マシンでは通ります。CI や本番のベースイメージが Node 16 のままだと、その環境でだけ structuredClone is not defined になります。

これは典型的な版境界です。Node 16 は広く使われた LTS だったため、「手元の Node 18・20 では動くのに、FROM node:16 のコンテナや古い CI でだけ落ちる」という食い違いが起きやすいのがこのエラーの厄介なところです。

いつから structuredClone が使えるのか

グローバルの structuredClone は Node 17.0.0 で追加されました。それより前の版には無く、参照した時点で ReferenceError になります。

Node の版グローバル structuredClone挙動
16 以前無しReferenceError: structuredClone is not defined
17.0.0 以降有りそのまま使える

Node 16 は 2023 年 9 月に EOL(サポート終了)を迎えましたが、Docker イメージや CI 設定に固定として残っていることは珍しくありません。境界が LTS の切れ目(16→17)にあるぶん、他の版境界より遭遇しやすい部類です。

structuredClone は何をするものか

structuredClone は、構造化複製(structured clone)アルゴリズムでオブジェクトを深くコピーします。ネストしたオブジェクトや配列はもちろん、Map / Set / Date / ArrayBuffer、さらに循環参照までコピーできます。この対応範囲の広さが、後述の代替策(とくに JSON の往復)を選ぶときの判断材料になるので、先に押さえておきます。

再現(最小構成)

次の1ファイルを置きます。深いコピーができているかを、コピー先を書き換えて元が変わらないことで確かめる構成です。

// index.js
const original = { nested: { value: 1 } };
const cloned = structuredClone(original);

cloned.nested.value = 2;
console.log(original.nested.value, cloned.nested.value);

これを Node 16 で node index.js すると ReferenceError: structuredClone is not defined が出て、終了コードは 1 になります。Node 17 以降なら 1 2 と表示され、original が書き換わっていない(=深いコピーができている)ことがわかります。同じコード・同じマシンでも、Node の版だけで結果が変わります。

解決

1. Node を 17 以降(実務では 18 以降の LTS)に上げる

structuredClone を使い続けるなら、実行する Node(CI・本番の Docker ベースイメージを含む)を上げます。API が入ったのは 17.0.0 ですが、16 も 17 もすでに EOL なので、本番ではその時点でサポート中の LTS へ揃えます。DockerfileFROM node:16 や、CI のバージョン設定を見直してください。メジャー版をまたぐときは、他の依存パッケージが対応しているかを併せて確認します。

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

古い Node をサポート対象に残すなら、構造化複製を再現するパッケージを使います。@ungap/structured-clone が広く使われており、Map / Set / Date / 循環参照といった値もコピーできます(ArrayBuffer の transfer など一部は対象外)。

// index.js(ポリフィルを使う)
import structuredClone from '@ungap/structured-clone';

const original = { nested: { value: 1 } };
const cloned = structuredClone(original);

cloned.nested.value = 2;
console.log(original.nested.value, cloned.nested.value);

3. JSON の往復で済ませる(コピー対象が JSON で表せる場合のみ)

コピーしたい値が「文字列・数値・真偽値・配列・素のオブジェクト」だけでできているなら、JSON.parse(JSON.stringify(value)) で深いコピーになります。

const cloned = JSON.parse(JSON.stringify(original));

ただしこれは structuredClone の完全な代わりにはなりません。Date は文字列に化け、Map / Set は空オブジェクトになり、undefined や関数を持つプロパティは消え、循環参照があると TypeError で落ちます。素の JSON データだと確信できる場合の割り切りとして使い、そうでないなら 1 か 2 を選んでください。

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

  • 同じ版境界で別のグローバル API が落ちるstructuredClone と同じく「新しい Node に組み込まれたグローバル」は、古い版で is not defined になります。fetch(Node 18+)、crypto.randomUUID(14.17/16+)、AbortController(15+)、Blob(グローバルは 18+)などが該当します。原因は「その版にその API がまだ無い」で共通し、対処も Node を上げるかポリフィルを入れるかの二択です。fetchReferenceError: fetch is not defined の直し方crypto.randomUUIDTypeError: crypto.randomUUID is not a function の直し方 を参照してください。
  • structuredClone は呼べるが DataCloneError が出る:これは版の問題ではありません。関数・Symbol・DOM ノードなど、構造化複製アルゴリズムがコピーできない値を渡すと DOMException [DataCloneError]: ... could not be cloned になります。コピー対象から複製不可の値を外してください。
  • ブラウザ側で structuredClone is not defined が出た:ブラウザの structuredClone は比較的新しい実装(主要ブラウザで 2022 年ごろ)なので、古い環境では未定義になり得ます。この記事は Node 実行時の話です。スタックトレースにファイルパスや node:internal/... が出ているなら Node 側の問題です。
  • node: 付きの組み込みモジュールで落ちた:症状は違いますが「新しい書き方を古い Node が解釈できない」という同じ版境界です。Cannot find module 'node:fs'node: プレフィックスが古い Node で通らないときの直し方 を参照してください。

検証(machine-verified)

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

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