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 の対応は次のとおりです(一部)。
| Node | NODE_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:18でnpm 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 向けのバイナリを取得し、無ければソースからビルドします。先ほどのエラーが 108 と 115 のズレだったなら、Node 20 で用意し直せば両方 115 にそろいます。
ソースからビルドになる場合は、その環境に Python3・make・C++ コンパイラが要ります。 軽量イメージ(node:alpine や -slim)にはこれらが既定で入っておらず、gyp ERR! find Python のような別のエラーで二次的に詰まります(errfix でも node:20-alpine で better-sqlite3 の導入が prebuild-install || node-gyp rebuild に落ちて失敗するのを確認しました)。この環境では、ビルドツールを持つステージでビルドしてから成果物を運ぶ形にします(下記)。
Docker で避けたいのは、別の Node でビルドした node_modules を持ち込むことです。手元やホストの node_modules を COPY したり、ボリュームでマウントしたりすると、その Node とコンテナの Node が違えば同じズレを踏みます。マルチステージビルドで builder ステージから node_modules を運ぶ構成は、builder と runtime の Node(メジャー版)を同じタグにそろえてあれば問題ありません。危ないのは、ステージ間・環境間で Node の版が食い違うことです(FROM node:20 AS builder と FROM node:18-alpine を混ぜる、など)。
版を「合わせる」対処もある
リビルドではなく、実行する Node の版を、node_modules をビルドした版にそろえる方向でも直ります。CI と手元でズレているなら、.nvmrc や package.json の engines、Docker のベースイメージのタグを固定して、チーム全員とCIが同じ Node を使う状態にそろえるのが確実です。どちらの方向で直すかは、「今の Node に上げたい」のか「まだ上げられない事情がある」のかで決めます。
切り分け(うまくいかないとき)
npm rebuildしても同じエラーが出る:リビルドが別の Node を使っている可能性があります。npm rebuildを実行したnode -vと、アプリを起動するnode -vが同じか確認してください。Docker なら、ビルドと実行が同じイメージ(同じ Node)で行われているかを見ます。またnpm config get ignore-scriptsがtrueだとリビルド自体が走らないので、その設定にも注意してください。- Electron アプリで出た:Electron は、同じバージョン番号の通常 Node とも別の ABIを持ちます。シェルの通常 Node で
npm rebuildしても Electron 用にはならず、mismatch は残ったままです。process.versions.electronがあるなら、@electron/rebuild(または Electron 向けのruntime/target指定)で Electron の ABI に合わせてビルドし直します。 NODE_MODULE_VERSIONではなくinvalid ELF headerやnot 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 の直し方 が対象です。