Things that do not work in nexide today and will bite a Next.js production deploy if you rely on them. Each item is a deliberate trade-off (V8-only runtime, no full Node platform), not a missing-feature backlog item.
Nexide implements a substantial subset of the N-API ABI directly against V8 — enough for many Next.js–adjacent addons to load and run. What currently works:
- Values & types:
napi_get_*/napi_create_*for primitives, strings (UTF-8), objects, arrays, dates, BigInt, externals. - Properties: get/set named, define properties with attributes, prototype chain.
- Functions & classes:
napi_create_function,napi_define_class(with property descriptors — methods, values, static),napi_wrap/napi_unwrap,napi_new_instance,napi_call_function,napi_get_cb_info. - Errors:
napi_throw_*,napi_create_error, type/range error variants, pending exception propagation,napi_fatal_error/napi_fatal_exception. - Buffers & typed arrays:
napi_create_buffer*,napi_create_typedarray,napi_create_arraybuffer, finalizers (with caveats — backing-store deleters fired off-thread by V8 currently passnapi_env=NULL). - References:
napi_create_reference,_ref/_unref/_value/_delete. - Promises & deferreds:
napi_create_promise,napi_resolve_deferred,napi_reject_deferred— single-shot resolvers driven from JS or N-API thread callbacks. - BigInt:
napi_create_bigint_int64/_uint64/_wordsand the matchingnapi_get_value_bigint_*getters. - Async work:
napi_create_async_work/_queue/_cancel/_delete— execute runs on tokio's blocking pool, complete is trampolined back to the V8 thread (the engine pump is woken so callbacks fire even on otherwise-idle isolates). - Threadsafe-functions:
napi_create_threadsafe_function,napi_call_threadsafe_function,_acquire/_release/_get_context/_ref/_unref— calls from any thread are funnelled through the engine pump and dispatched under a real V8 scope. Cross-thread wake-up is wired so the pump comes out of idle as soon as a worker thread pushes a callback.
Confirmed working end-to-end (see e2e/prisma-sqlite/):
@prisma/clientlibrary engine (libquery_engine-*.dylib.node) against SQLite — full N-API path: tsfn-driven async query pipeline, promise-based connect / query, BigInt cursors,process.dlopen, Node-style subpath imports (#main-entry-point).
What still doesn't work:
sharp—next/imagedoesn't need it (nexide's native/_next/imageoptimizer covers the built-in route). Custom loaders that callrequire('sharp')directly still fail.canvas(node-canvas) and other addons that link non-trivial third-party C++ libraries (Cairo, Pango, …) — the surface they consume is much wider than what's implemented above.- Anything using Node's
uv_*/ libuv API directly (some database drivers, FFI bridges) — N-API lives entirely above libuv, so addons that bypass it are out of scope.
For pure-JS swap-ins still recommended where they exist:
bcryptnative →bcryptjs.better-sqlite3/sqlite3→ use@prisma/client(works, see above), an HTTP-fronted SQLite (Turso, D1), or a pure-JS driver.
require('node:http2') loads, exposes constants, but createServer
and connect throw. Most deps probe via
try { require('http2') } catch {} and fall back to HTTP/1.1, so this
is usually transparent. gRPC clients that hard-require HTTP/2 will not
work.
require('node:worker_threads') resolves but new Worker(...) throws.
Next.js uses workers for ISR background revalidation; in nexide that
path is serialised onto the request loop. For most workloads this is
invisible; if you have heavy CPU-bound revalidation, scale horizontally
instead.
require('node:inspector') loads (so deps probing it don't crash) but
the DevTools wire protocol is not implemented. CPU profiles and heap
snapshots must be captured externally (e.g. via the host process).
Not shipped. These are server-side primitives nexide replaces with
native equivalents (multi-process scaling via SO_REUSEPORT instead of
cluster, OpenTelemetry instead of trace_events, etc.) or that are
simply not used by the Next.js server runtime.
Next.js standalone bundles every dependency as CommonJS via webpack, so
import statements that survive bundling are extremely rare. Pure-ESM
packages that ship .mjs and rely on dynamic import() at runtime are
not currently routed through nexide's resolver. Workaround: pin the
package to a version that still provides a CJS build, or pre-bundle it
into your app code via webpack's transpilePackages.
Stack frames currently show positions inside the bundled chunk
(/.next/server/chunks/3660.js:2:78064) rather than original sources.
Wire your error reporter (Sentry, Datadog) with the build's .map files
the same way you would for stock Node.
NODE_EXTRA_CA_CERTS is honoured by the outbound https polyfill but
proxy-related env vars (HTTP_PROXY, HTTPS_PROXY, NO_PROXY) are not
threaded through fetch / https.request yet. Set them on a sidecar
proxy if you need outbound proxy support.
SIGUSR2 is not handled (pino/winston rotation hooks are silently
no-ops). Rotate via the orchestrator (Kubernetes log driver, journald)
instead of inside the app.
If you hit a limitation that isn't on this list — open an issue with the
exact MODULE_NOT_FOUND / runtime error and a minimal repro. Everything
above started life as a production bug report.