Error [NODE_MODULE_VERSION]

NODE_MODULE_VERSION mismatch の直し方(ネイティブ addon を別の Node 版で読み込んだとき)

FIX SUMMARY verified
Applies when
node:20native addon (better-sqlite3 等)native addon ABI mismatch across Node versions

Verified: reproduced in node:20, then the NODE_MODULE_VERSION signature was gone after the fix (exit 0).

node でアプリを起動した瞬間、require の行で次のエラーが出て落ちることがあります。

Error: The module '.../node_modules/<パッケージ名>/build/Release/<addon>.node'
was compiled against a different Node.js version using
NODE_MODULE_VERSION 108. This version of Node.js requires
NODE_MODULE_VERSION 115. Please try re-compiling or re-installing
the module (for instance, using `npm rebuild` or `npm install`).

最初のパスとファイル名は、読み込もうとしたネイティブ addon のものです(例:node_modules/better-sqlite3/build/Release/better_sqlite3.node)。原因はコードではありません。ネイティブ addon(C/C++ で書かれた .node ファイル)が、いま動かしている Node とは違う Node のバージョン向けにビルドされているときに出ます。エラー本文の NODE_MODULE_VERSION の2つの数字が、そのズレを直接示しています(この 108 と 115 は errfix が Node 18 と 20 で実測した値です)。

急いで直すなら、node_modules を今の Node でビルドし直すのが答えです。ただし、なぜ数字がズレたのかを先に押さえると、同じ環境で二度と踏まなくなります。

NODE_MODULE_VERSION の2つの数字を読む

NODE_MODULE_VERSION は Node の ABI(ネイティブモジュールの互換番号) です。Node のメジャーバージョンが上がると、この番号も変わります。

  • compiled against ... NODE_MODULE_VERSION 108 … その .nodeビルドされたときの Node の ABI。
  • This version of Node.js requires NODE_MODULE_VERSION 115いま動かしている Node が要求する ABI。

上の例なら「108 でビルドされた addon を、115 を要求する Node で読み込もうとした」という意味です。番号と Node の対応は次のとおりです(一部)。

NodeNODE_MODULE_VERSION
18 系108
20 系115

(この表は errfix が Node 18.20.4 と 20.20.2 で実測した2点です。同じメジャー版の中では番号は変わりません。他の版の番号は、実行環境で node -p process.versions.modules を見るか、Node 公式の ABI version registry を参照してください。)

ネイティブ addon は、この ABI 番号を .node ファイルに焼き込んでいます。Node は .node を読み込んだあと、初期化コードを実行する前にこの番号を照合し、一致しなければ弾きます。だから「コードは何も変えていないのに、起動した瞬間に落ちる」という出方になります。

なぜ番号がズレるのか(=どこで踏むか)

.node は「ビルドした時点の Node」に紐づきます。ビルドした Node と、実行する Node が食い違うとズレます。典型的なのは次のような場面です。

  • Docker のベースイメージを上げたnode:18npm install して作った node_modules を、node:20 のイメージで動かした(あるいはイメージだけ上げて node_modules を作り直していない)。
  • nvm や n で Node を切り替えた:前のバージョンでインストールした node_modules のまま、別のバージョンで実行した。
  • node_modules を成果物としてコピー/キャッシュした:CI でビルドした node_modules を、別の Node 版の実行環境やキャッシュから持ち込んだ。
  • CI と手元で Node の版が違う:手元(Node 18)では通るのに、CI(Node 20)でだけ落ちる。

共通しているのは 「手元では出ないのに、別の環境でだけ出る」 という形です。原因を追う前に、まず落ちている環境の node -v と、その node_modules をどの Node でビルドしたかを突き合わせてください。

直し方:今の Node で addon をビルドし直す

対処はロジックの修正ではありません。実行している Node に合わせて、ネイティブ addon をビルドし直すだけです。どれか1つで直ります。

# 1) 該当パッケージだけを、今の Node でリビルドする(プロジェクト直下で実行。sudo は不要)
npm rebuild better-sqlite3

# 2) それでも直らなければ、node_modules を作り直す
rm -rf node_modules   # このプロジェクト直下の、npm install で作り直せる node_modules だけを消す
npm install

(Windows の PowerShell なら rm -rf node_modules の代わりに Remove-Item -Recurse -Force node_modules です。)

npm rebuild は、該当パッケージを今の Node に合わせて用意し直します。ビルド済みバイナリ(prebuilt)を配る パッケージなら今の Node 向けのバイナリを取得し、無ければソースからビルドします。先ほどのエラーが 108115 のズレだったなら、Node 20 で用意し直せば両方 115 にそろいます。

ソースからビルドになる場合は、その環境に Python3・make・C++ コンパイラが要ります。 軽量イメージ(node:alpine-slim)にはこれらが既定で入っておらず、gyp ERR! find Python のような別のエラーで二次的に詰まります(errfix でも node:20-alpinebetter-sqlite3 の導入が prebuild-install || node-gyp rebuild に落ちて失敗するのを確認しました)。この環境では、ビルドツールを持つステージでビルドしてから成果物を運ぶ形にします(下記)。

Docker で避けたいのは、別の Node でビルドした node_modules を持ち込むことです。手元やホストの node_modulesCOPY したり、ボリュームでマウントしたりすると、その Node とコンテナの Node が違えば同じズレを踏みます。マルチステージビルドで builder ステージから node_modules を運ぶ構成は、builder と runtime の Node(メジャー版)を同じタグにそろえてあれば問題ありません。危ないのは、ステージ間・環境間で Node の版が食い違うことです(FROM node:20 AS builderFROM node:18-alpine を混ぜる、など)。

版を「合わせる」対処もある

リビルドではなく、実行する Node の版を、node_modules をビルドした版にそろえる方向でも直ります。CI と手元でズレているなら、.nvmrcpackage.jsonengines、Docker のベースイメージのタグを固定して、チーム全員とCIが同じ Node を使う状態にそろえるのが確実です。どちらの方向で直すかは、「今の Node に上げたい」のか「まだ上げられない事情がある」のかで決めます。

切り分け(うまくいかないとき)

  • npm rebuild しても同じエラーが出る:リビルドが別の Node を使っている可能性があります。npm rebuild を実行した node -v と、アプリを起動する node -v が同じか確認してください。Docker なら、ビルドと実行が同じイメージ(同じ Node)で行われているかを見ます。また npm config get ignore-scriptstrue だとリビルド自体が走らないので、その設定にも注意してください。
  • Electron アプリで出た:Electron は、同じバージョン番号の通常 Node とも別の ABIを持ちます。シェルの通常 Node で npm rebuild しても Electron 用にはならず、mismatch は残ったままです。process.versions.electron があるなら、@electron/rebuild(または Electron 向けの runtime / target 指定)で Electron の ABI に合わせてビルドし直します。
  • NODE_MODULE_VERSION ではなく invalid ELF headernot a valid Win32 application が出る:これは ABI ではなく、バイナリの形式そのものが受理されていません。多くは OS・CPU アーキテクチャのズレ(Linux 用の .node を macOS で読む、arm64 でビルドしたものを x64 で読むなど)ですが、ダウンロード失敗で中身が壊れた・別物のファイルになっている場合もこの形になります。まず file <その .node> で中身を確かめ、アーキテクチャのズレならリビルド、壊れたファイルなら取得し直します。
  • Node-API(旧称 N-API)の addon なのに出た:その .node が Node-API(napi_* / node-addon-api)だけで作られているなら、ABI が安定していてこの版ズレでは落ちません。それでも出るなら、その .node が旧来の方式でビルドされているか、別の原因(OS・アーキテクチャのズレ、ファイル破損)です。エラーに出た .node 単位でビルド方式を確認してください。
  • prebuilt binary(ビルド済みバイナリ)を使うパッケージprebuild-install などは、インストール時の Node に合わせてビルド済みバイナリを取得します。インストール時と実行時で Node が違うと、取得済みのバイナリの ABI がずれるのです。この場合も npm rebuild か再インストールでそろえます。
  • require で落ちるが、メッセージが NODE_MODULE_VERSION ではなく ERR_PACKAGE_PATH_NOT_EXPORTED:これはネイティブ addon の ABI ではなく、パッケージが exports で公開していないサブパスを読んだときのエラーです。同じ「入れた依存が読めない」でも原因が違うので、ERR_PACKAGE_PATH_NOT_EXPORTED の直し方 を見てください。また、そもそも npm install が peer dependency の衝突で止まるなら ERESOLVE unable to resolve dependency tree の直し方 が対象です。

検証(machine-verified)

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

verify — run-case.mjs
$ node run-case.mjs node/node-module-version-mismatch
● reproduce NODE_MODULE_VERSION present ✓
● apply fix exit 0
● re-run NODE_MODULE_VERSION gone ✓
PASS verified · node:20 · signature gone