Skip to content

Troubleshooting

Fresh 🌱

Common failures when building and running Neon modules, and what to check.

Build fails with linker errors

Symptom: npm run build compiles Rust but fails at the end with linker / link.exe / ld errors.

Cause: Missing platform C/C++ build tools - Rust produces object files but cannot link the final binary.

Fix: Install the platform toolchain:

  • Windows: Visual Studio Build Tools (MSVC).
  • macOS: xcode-select --install.
  • Linux: a C compiler + make (e.g. build-essential).

See the Install Toolchain SOP.

require('.') throws "cannot find module" or "invalid ELF / Mach-O"

Symptom: Node cannot load index.node, or reports an architecture/format mismatch.

Causes & fixes:

  • The module was not built yet → run npm run build.
  • The binary was built for a different platform/arch (e.g. copied from another machine) → rebuild locally, or use a prebuild matching this platform.
  • A Node-API version mismatch → align the napi-N feature in Cargo.toml with your Node version.

A function throws TypeError unexpectedly

Symptom: Calling an exported function throws TypeError even though arguments look right.

Cause: An argument was read with a specific type (e.g. cx.argument::<JsNumber>(0)?) but JavaScript passed a different type. The type check throws.

Fix: Pass the correct JS type, or read as JsValue / use argument_opt and branch in Rust. See Functions.

Code does not compile: "Handle cannot be sent between threads safely"

Symptom: Moving a Handle or FunctionContext into std::thread::spawn or an async block fails to compile (not Send).

Cause: JavaScript handles and contexts are only valid on the main thread - by design.

Fix: Move a Root into the thread instead, and re-derive the handle on the main thread inside a Channel::send / settle_with closure with root.into_inner(&mut cx). See Async Work Pattern.

The event loop is blocked / Node hangs during heavy work

Symptom: While a Rust function runs, the Node process is unresponsive.

Cause: Heavy work is running synchronously on the main thread.

Fix: Move the work off the main thread with cx.task(...).promise(...), #[neon::export(task)], or a spawned thread that settles a promise via a Channel. See Async Tasks, Promises & Channels.

Published package is enormous

Symptom: npm publish ships hundreds of megabytes.

Cause: The Rust target/ directory got included.

Fix: Exclude target/ via the files field in package.json or .npmignore. See Build & Publish SOP.

Async export does not return a Promise

Symptom: An async fn export does not behave as a JavaScript async function.

Cause: The futures feature is not enabled.

Fix: Enable it in Cargo.toml:

toml
neon = { version = "1.1", features = ["futures"] }

When in doubt, check the four corners

Most Neon issues trace to one of: (1) missing build tools, (2) a Node-API version mismatch, (3) crossing the thread boundary with the wrong carrier, or (4) blocking the event loop. Work down that list first.