Skip to main content

Solving 'NODE_MODULE_VERSION' Mismatches in Electron Native Addons

 You have just successfully compiled your C++ native module. Your unit tests pass in your local Node.js environment. You fire up your Electron application, and the render process crashes with the following stack trace:

Uncaught Error: The module '\\?\C:\path\to\addon.node' 
was compiled against a different Node.js version using 
NODE_MODULE_VERSION 115. This version of Node.js requires 
NODE_MODULE_VERSION 121. Please try re-compiling or 
re-installing the module (for instance, using `npm rebuild` 
or `npm install`).

Running npm rebuild rarely fixes this because it rebuilds against your system Node version, not the Electron Node version. Here is the architectural root cause and the definitive way to manage native compilation targets.

The Root Cause: ABI Divergence

Electron is not just a wrapper around your system's Node.js installation. It bundles its own forked version of Node.js and, critically, a specific version of the V8 JavaScript engine.

Native addons (compiled .node files) interact directly with V8 and libuv memory structures. Because C++ lacks a stable ABI (Application Binary Interface) across V8 versions, the memory layout of a V8 object in Node v20 (your system) differs from Node v20-patched (bundled in Electron).

To prevent memory corruption (segfaults), Node checks an integer compilation flag called NODE_MODULE_VERSION upon loading an addon. If the integer embedded in your binary doesn't match the runtime's integer, it throws the error above.

Even if you use N-API (Node-API), which provides ABI stability, you must still compile against the correct headers if your module relies on specific V8 internals or if the N-API version support differs between your build environment and the Electron runtime.

The Manual Solution: Explicit Target Compilation

To fix this, we must bypass the standard npm build process and invoke node-gyp with flags that point explicitly to Electron's header files, not Node's.

1. Identify the Target Version

First, determine the exact version of Electron you are running.

npx electron --version
# Output: v29.1.0

2. The Build Command

We must instruct node-gyp to:

  1. Target the specific Electron version (--target).
  2. Download headers from the Electron repository, not the Node.js repository (--dist-url).
  3. Build for the correct runtime (--runtime).

Run the following in your module's root directory:

# Replace 29.1.0 with your exact electron version
npx node-gyp rebuild \
  --target=29.1.0 \
  --arch=x64 \
  --dist-url=https://electronjs.org/headers \
  --runtime=electron

If this command succeeds, your .node binary is now ABI-compatible with the V8 engine inside Electron v29.1.0.

The Production Solution: Automated Lifecycle Hooks

Manually running node-gyp commands is brittle. In a production CI/CD environment or a team setting, you should automate this using @electron/rebuild (the modern successor to the deprecated electron-rebuild package).

1. Install Dependencies

We need node-addon-api for modern C++ bindings and @electron/rebuild to handle the header management automatically.

npm install node-addon-api
npm install --save-dev @electron/rebuild clang-format

2. Configure binding.gyp

Ensure your binding.gyp is configured to use N-API, which reduces the surface area for ABI breaks.

{
  "targets": [
    {
      "target_name": "native-addon",
      "cflags!": [ "-fno-exceptions" ],
      "cflags_cc!": [ "-fno-exceptions" ],
      "sources": [ "./src/addon.cpp" ],
      "include_dirs": [
        "<!@(node -p \"require('node-addon-api').include\")"
      ],
      "defines": [ "NAPI_DISABLE_CPP_EXCEPTIONS" ]
    }
  ]
}

3. Modern C++ Implementation

Here is a minimal, valid C++17 implementation in src/addon.cpp validating the environment.

#include <napi.h>

// A simple function to prove native execution
Napi::String Method(const Napi::CallbackInfo& info) {
  Napi::Env env = info.Env();
  return Napi::String::New(env, "Native module loaded successfully in Electron.");
}

// Initialize the addon
Napi::Object Init(Napi::Env env, Napi::Object exports) {
  exports.Set(Napi::String::New(env, "hello"),
              Napi::Function::New(env, Method));
  return exports;
}

// Register the module
NODE_API_MODULE(addon, Init)

4. NPM Scripts for Context Switching

This is the critical step. You likely need to run this module in standard Node (for unit testing) and Electron (for the app). We create separate scripts for each target.

Update your package.json:

{
  "name": "electron-native-addon",
  "version": "1.0.0",
  "scripts": {
    "build:node": "node-gyp rebuild",
    "build:electron": "electron-rebuild -f -w native-addon",
    "test": "npm run build:node && node test/index.js",
    "start": "npm run build:electron && electron ."
  },
  "dependencies": {
    "node-addon-api": "^8.0.0"
  },
  "devDependencies": {
    "@electron/rebuild": "^3.6.0",
    "electron": "^29.1.0"
  }
}

Why This Works

The electron-rebuild package essentially wraps the manual node-gyp command we analyzed earlier. It automatically detects the installed version of the electron package in your node_modules, determines the ABI version, downloads the headers from electronjs.org, and recompiles any native dependencies found in package.json.

Verifying the Fix

To verify the fix without launching the full Electron GUI, you can use the Electron executable as a REPL.

  1. Run npm run build:electron
  2. Open the Electron REPL:
./node_modules/.bin/electron
  1. Attempt to require your module inside the Electron console:
// Inside the Electron REPL
const addon = require('./build/Release/native-addon.node');
console.log(addon.hello());
// Output: Native module loaded successfully in Electron.

If you see the output string, the NODE_MODULE_VERSION matches exactly.

Summary

The NODE_MODULE_VERSION error is a safeguard, not a bug. It prevents you from loading binary code compiled for one memory layout into a runtime using another.

  1. Do not rely on npm install to build for Electron.
  2. Do use @electron/rebuild in your start script to ensure binaries match the Electron version currently installed.
  3. Do keep N-API dependencies updated to minimize breaking changes between V8 versions.