状況の説明
Node.jsをv18からv20にアップグレードしてアプリを起動したところ、以下のエラーが発生した:
Error: Cannot find module '../build/Release/addon.node'
Require stack:
- /app/node_modules/bcrypt/lib/bcrypt.js
node_modulesはそのまま残っている。設定ファイルには一切触れていない。変えたのはNode.jsのバージョンだけだ。それなのにアプリが起動を拒否する。
なぜこうなるのか
bcrypt、sharp、canvas、sqlite3、node-sassなどのパッケージは純粋なJavaScriptではない。C/C++コードが含まれており、それが.nodeバイナリとしてコンパイルされる——そしてそのバイナリは特定のNode.js ABI(Application Binary Interface)バージョンに紐付けられている。
Node.js v18はABI 108を使用する。Node.js v20はABI 115を使用する。古いバイナリは異なる方言を話しているようなもので、Nodeがロードできないため、ファイルがディスク上に物理的に存在しているにもかかわらず「モジュールが見つからない」エラーが表示される。
これがnpm installだけでは修正できない理由でもある。npmはパッケージがインストール済みと認識し、再コンパイルを完全にスキップしてしまう。
クイックフィックス — ネイティブアドオンの再ビルド
うまくいく可能性が高い順に、3つの方法を紹介する:
方法1:npm rebuild(まずこれを試す)
# プロジェクトのルートディレクトリで実行
npm rebuild
現在のNode.jsバージョンに対してすべてのネイティブアドオンを再コンパイルする。一般的なプロジェクトで30〜60秒かかる。通常はこれだけで解決する。
方法2:node_modulesを削除して再インストール
npm rebuildがエラーを出すか中途半端な状態になる場合は、クリーンな再インストールを行う:
# npm
rm -rf node_modules
npm install
# yarn
rm -rf node_modules
yarn install
# pnpm
rm -rf node_modules
pnpm install
時間はかかるが、より確実だ。すべてをゼロからコンパイルし直す。
方法3:特定のパッケージだけ再ビルド
壊れているアドオンが1つだけ?すべてを削除する必要はない。直接ターゲットを指定する:
npm rebuild bcrypt
再ビルドが失敗する場合 — ビルドツールが不足している
コンパイルのステップはnode-gypを通じて実行されるが、これにはC++コンパイラとPython 3がマシンに必要だ。これらがなければ、gyp ERR! stack Error: not found: makeやpython: command not foundといったエラーが発生する。
Ubuntu/Debian
sudo apt-get install -y build-essential python3
RHEL/CentOS/Amazon Linux
sudo yum groupinstall 'Development Tools'
sudo yum install python3
macOS
xcode-select --install
AppleのコマンドラインデベロッパーツールがインストールされO、clangとmakeが含まれる。ダウンロードサイズは約200 MBだ。
Windows
古いwindows-build-tools npmパッケージは非推奨となっており、最新のNode.jsでは動作しない。代わりに直接ソースから入手する:
- MicrosoftのウェブサイトからMVisual Studio Build Tools 2022をダウンロードする
- インストール時に**「C++によるデスクトップ開発」**ワークロードを選択する
- Python 3もインストールされていることを確認する(インストーラーにオプションとして含まれている)
その後、新しいターミナルウィンドウでnpm rebuildを再試行する。
DockerおよびCI環境
Dockerはよくある落とし穴だ。ベースイメージをnode:18-alpineからnode:20-alpineに更新しても、Dockerが古いコンパイル済みバイナリを含むキャッシュレイヤーを再利用してしまう。イメージのビルドは問題なく見えるのに、アプリが実行時に壊れる。
修正方法は、ビルドツールを用意した上でDockerfile内でクリーンインストールを行うことだ:
FROM node:20-alpine
RUN apk add --no-cache python3 make g++
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
node_modulesに名前付きボリュームを使ったDocker Composeを使用している場合、イメージを再ビルドした後もそのボリュームに古いバイナリが残っている。削除するには:
docker-compose down -v
docker-compose up --build
修正が成功したか確認する
.nodeバイナリが実際に再コンパイルされたか確認する——タイムスタンプが最新になっているはずだ:
# bcrypt
ls -la node_modules/bcrypt/lib/binding/
# sharp
ls -la node_modules/sharp/build/Release/
次に、アプリ全体を起動する前に簡単な動作確認を行う:
node -e "require('bcrypt'); console.log('bcrypt OK')"
node -e "require('sharp'); console.log('sharp OK')"
OKと表示されれば完了だ。
再発防止
根本的な原因はバージョンの不一致だ。プロジェクトのルートに.nvmrcファイルを置いてバージョンを固定しよう:
# .nvmrc
20.11.0
これでチームの全員と、すべてのCIの実行が同じバージョンを使うようになる:
nvm use # .nvmrcを自動的に読み込む
GitHub Actions:
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
ファイル1つで、「自分の環境では動く」というバージョンのズレはもう起きない。
ヒント
ネイティブアドオンの問題を追いかけているとき、再ビルド後に.nodeバイナリが実際に変わったかどうかを確認したい場合がある。ToolCraftのHash Generatorを使えば、ブラウザ上でSHA-256チェックサムを生成できる——再ビルド前後のハッシュを比較して、ファイルが単にタイムスタンプが更新されただけでなく実際に置き換わったかどうかを確認しよう。
クイックリファレンス
- 根本原因:ネイティブアドオンが古いNode.js ABIにコンパイルされており、アップグレード後は互換性がない(例:v18とv20の間でABI 108 → 115)
- 修正1:
npm rebuild - 修正2:
node_modulesを削除して再インストール - 修正3:先にビルドツールをインストール(Linuxは
build-essential、macOSはXcode CLI、WindowsはVisual Studio Build Tools)してから再ビルド - Docker:ボリュームキャッシュを削除し、イメージ内にビルドツールを含めた上で
npm ciを使ってイメージを再ビルドする - 予防策:
.nvmrcでNode.jsのバージョンを固定する

