TechEarl

Fix NODE_MODULE_VERSION Mismatch in Node.js

The NODE_MODULE_VERSION mismatch error means a native addon was compiled against a different Node ABI than the one now running it. Here is what the numbers mean, the one-line fix, and the Electron, Docker, and CI variants that catch people out.

Ishan Karunaratne⏱️ 13 min readUpdated
Share thisCopied
How to fix the NODE_MODULE_VERSION mismatch error in Node.js by rebuilding native modules against the current ABI

NODE_MODULE_VERSION is the internal ABI number Node.js stamps onto every native addon (.node binary) when it compiles. When you see Error: The module ... was compiled against a different Node.js version using NODE_MODULE_VERSION X. This version of Node.js requires NODE_MODULE_VERSION Y, it means a compiled module on disk was built for one Node major and you are now running it under another. The numbers are not Node versions, they are ABI versions, and they only change across major releases. The fix is almost always to recompile the module against the Node you are running now: npm rebuild, or a clean rm -rf node_modules && npm install. Below is what the two numbers actually mean, the full ABI-to-Node mapping so you can read the error at a glance, the reliable fix paths, and the Electron, Docker, and CI variants that the plain npm rebuild does not cover.

What does the NODE_MODULE_VERSION mismatch error mean?

The error means a native Node addon was compiled against a Node ABI different from the one currently loading it. Native modules (anything that ships a .node binary built from C/C++ via node-gyp, like bcrypt, sharp, or better-sqlite3) are pinned to the ABI of the Node major they were built against. That ABI is identified by the NODE_MODULE_VERSION integer, which Node bumps on every major release. The X in the error is the ABI the module was built for; the Y is the ABI your current Node requires. To fix it, rebuild the module against your current Node with npm rebuild, or delete node_modules and reinstall so every native dependency compiles fresh.

A full error looks like this:

code
Error: The module '/app/node_modules/better-sqlite3/build/Release/better_sqlite3.node'
was compiled against a different Node.js version using
NODE_MODULE_VERSION 108. This version of Node.js requires
NODE_MODULE_VERSION 127. Please try re-compiling or re-installing
the module (for instance, using `npm install` or `npm rebuild`).
    at Module._extensions..node (node:internal/modules/cjs/loader:1340:18)

Read it as: this .node file was built for Node 18 (ABI 108) and you are now on Node 24 (ABI 127). See the table below to decode the numbers.

Jump to:

The one-line fix

In most cases this is all you need, run from the project root:

bash
npm rebuild

npm rebuild recompiles every installed package that has a build step, against the Node currently on your PATH. If a single dependency is the culprit, you can target it:

bash
npm rebuild better-sqlite3

If npm rebuild does not clear the error (stale prebuilt binaries, a lockfile pointing at the wrong prebuild, a partially-built node_modules), do the clean reinstall:

bash
rm -rf node_modules package-lock.json
npm install

That forces every native dependency to fetch the right prebuilt binary or compile from source against your current ABI. The lockfile gets regenerated, so commit it afterward if the resolved versions changed.

NODE_MODULE_VERSION to Node version table

The NODE_MODULE_VERSION integer maps to a Node major release. This is the table to keep handy when you are decoding the error number, sourced from the Node.js ABI version registry:

NODE_MODULE_VERSIONNode.js majorFirst released
83Node 142020
88Node 152020
93Node 162021
102Node 172021
108Node 182022
111Node 192022
115Node 202023
120Node 212023
127Node 222024
131Node 232024
137Node 242025

So NODE_MODULE_VERSION 115 is Node 20, 127 is Node 22, 137 is Node 24. The error always quotes two of these: the one the module was built for and the one you are running. If the "requires" number is higher than the "compiled against" number, you upgraded Node and left a stale build behind, which is the common direction.

You can confirm the ABI of your current Node without looking anything up:

bash
node -p "process.versions.modules"
# 127      (on Node 22)

Why the number changes (ABI vs version)

NODE_MODULE_VERSION is the Application Binary Interface version, not the Node release version. It tracks the binary contract between Node's compiled C++ internals (the V8 engine, libuv, the internal object layout) and any native addon that links against them. When that contract changes in a way that would break an addon compiled against the old layout, the number goes up.

The number is bumped roughly once per Node major, which is why a node --version patch or minor bump (say 22.3 to 22.11) never triggers this error, but a major bump (22 to 24) always can if native modules are involved. A V8 upgrade is usually what forces the bump, since V8's internal APIs are part of the surface a native addon links against. Pure-JavaScript packages are completely unaffected; this only ever touches modules that ship a .node binary.

How you got here: the usual causes

The mismatch shows up after the running Node changes out from under a previously-built node_modules. The realistic triggers:

  • You upgraded Node (nvm, fnm, Volta, brew, the installer) and reused a node_modules that was built on the old major. This is the textbook case. See the rebuild-native-modules step in my Node update guide.
  • A version manager switched Node behind your back. cd into a project with a .nvmrc pinned to a different major than the one the modules were built on, and the auto-switch hook lands you on a mismatched ABI.
  • You committed or copied node_modules between machines or into an image that runs a different Node major. Native binaries are not portable across ABIs (or across OS/arch).
  • A teammate's lockfile pulled a prebuilt binary for a Node major that does not match yours, and the package's prebuild logic guessed wrong.
  • Electron. Electron bundles its own Node with its own ABI, distinct from your system Node. Building a native module with system Node and loading it in Electron is a guaranteed mismatch (see below).

Fix path 1: rebuild in place

When node_modules is otherwise healthy and you just changed Node major, rebuilding in place is fastest:

bash
# rebuild every package with a build step against current Node
npm rebuild

# or one specific module
npm rebuild sharp bcrypt

For yarn and pnpm the equivalents are:

bash
# yarn (classic and berry)
yarn rebuild

# pnpm
pnpm rebuild

Rebuild reuses the already-downloaded source in node_modules, so it is quicker than a full reinstall when your network or registry is slow. If it succeeds, re-run your app and the error is gone.

Fix path 2: clean reinstall

When rebuild does not fully clear it, often because a package shipped a prebuilt binary that does not match and rebuild did not overwrite it, wipe and reinstall:

bash
rm -rf node_modules package-lock.json
npm install

On Windows PowerShell:

powershell
Remove-Item -Recurse -Force node_modules, package-lock.json
npm install

This is the sledgehammer, but it is reliable: every dependency re-resolves, fetches the correct prebuild for your platform and ABI, or compiles from source. Keep the lockfile delete in the command. A stale lockfile can re-pin the same wrong prebuild and you end up exactly where you started.

Fix path 3: rebuild fails to compile

If the rebuild itself errors out (node-gyp failures, missing compiler, Python not found), the ABI mismatch is now a build-toolchain problem. Native compilation needs a C/C++ toolchain and Python 3:

bash
# Debian / Ubuntu
sudo apt-get install -y build-essential python3

# Fedora / RHEL / Rocky
sudo dnf install -y gcc-c++ make python3

# macOS
xcode-select --install

On Windows, the current Node .msi installer has an optional "Tools for Native Modules" checkbox that installs the Visual Studio C++ build tools and Python for you. Re-run the installer and tick it. Do not reach for the old npm install -g windows-build-tools package; it has been deprecated and unmaintained for years, and the bundled MSI option replaced it.

node-gyp itself is what drives the compile; if you are debugging a stubborn build, its README documents the --verbose flag and the Python/toolchain requirements per platform.

Electron has its own ABI

Electron does not use your system Node's ABI. It bundles its own Node build, and its process.versions.modules is a different number than the same Node line standalone. Compiling a native module with plain npm install (which targets system Node) and then loading it inside Electron produces the mismatch even though "the Node version looks right".

The fix is to rebuild native modules against Electron's ABI, not Node's. The standard tool is @electron/rebuild:

bash
npm install --save-dev @electron/rebuild

# rebuild native deps against the installed Electron's ABI
npx electron-rebuild

Run that whenever you install a native module or bump the Electron version. The Electron native-modules guide covers the headers-download and the electron-rebuild flags. Modules built with N-API (see below) are the exception, they load in both Electron and Node without a rebuild.

Docker: do not mount host node_modules

The most common Docker version of this error is a bind mount leaking the host's node_modules into the container. Your host machine builds native binaries for the host ABI/arch (say macOS arm64), then a compose file mounts the project directory, including node_modules, into a node:22 Linux container. The container loads binaries built for the wrong platform and ABI and throws the mismatch.

Two reliable fixes. First, never mount host node_modules. Mask it with an anonymous volume so the container keeps its own:

yaml
services:
  app:
    build: .
    volumes:
      - .:/app
      - /app/node_modules   # anonymous volume shadows the host node_modules

Second, run npm install inside the image build so the modules compile against the container's Node, and pin the base image so it does not drift:

dockerfile
FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
CMD ["node", "server.js"]

With npm ci running in the build, the .node binaries are built for the container's exact Node and platform. For more on pinning the base image and the layer order behind this, see How to update Node.js across Docker base images.

CI: pin Node so the cache matches

In CI the mismatch usually comes from a restored cache built on a different Node than the current job. If you cache node_modules (rather than only the npm cache) and the runner's Node major changes, the restored native binaries no longer match. Two guards:

Pin the Node version explicitly so it cannot float, and cache the npm download cache rather than the compiled node_modules:

yaml
- uses: actions/setup-node@v4
  with:
    node-version: 22          # pinned, not "latest"
    cache: 'npm'              # caches ~/.npm, not node_modules
- run: npm ci                 # compiles native deps fresh against this Node

cache: 'npm' keys the npm download cache on package-lock.json and leaves the compile step to run fresh every job, so the binaries always match the runner's Node. If you must cache node_modules for speed, add the Node version to the cache key so a Node bump invalidates it. To keep CI on the same Node your developers use locally, read it from a pinned file with node-version-file: '.nvmrc'.

N-API modules skip the whole problem

The long-term fix is upstream: modules built against Node-API (N-API) are ABI-stable across Node majors. N-API is a stable C interface that does not change when V8 or the internal object layout changes, so an N-API addon compiled once keeps loading across Node 18, 20, 22, 24 without a rebuild. Many popular native packages have migrated: bcrypt, better-sqlite3, sharp, and others now ship N-API builds, which is why you hit this error far less often than five years ago.

You cannot force a module to be N-API, that is the maintainer's choice, but when you are picking between two native dependencies, N-API support is a real tiebreaker. It means your node_modules survives a Node major upgrade without the rebuild dance.

See also

FAQ

Sources

Authoritative references this article was fact-checked against.

TagsNode.jsJavaScriptnode-gypNative ModulesABInpmElectronDocker

Found this useful? Pass it on.

Copied

Ishan Karunaratne

Tech Architect · Software Engineer · AI/DevOps

Tech architect and software engineer with 20+ years building software, Linux systems, and DevOps infrastructure, and lately working AI into the stack. Currently Chief Technology Officer at a healthcare tech startup, which is where most of these field notes come from.

Keep reading

Related posts

How to Find and Delete Files Safely with find -delete

find -delete removes every matched file with no confirmation and no undo. The safe pattern is to write the command with -print first, eyeball the list, then swap -print for -delete. Plus the directory-depth-first trap, when to use -exec rm instead, and the find -delete vs xargs rm -f tradeoff.

Fix SSH "Host Key Verification Failed"

Why SSH warns that the remote host identification has changed, when it is safe to clear, and the one command that removes the stale known_hosts entry: ssh-keygen -R.