@clementvial

Developer from Canada 🇨🇦
Product, infrastructure, AI, and web3.
Mostly on AWS and Cloudflare.

All notes

nodejs_compat Does Not Make a Worker Into Node

Adding the flag is the easy part:

wrangler.jsonc
{
"compatibility_date": "2025-04-01",
"compatibility_flags": ["nodejs_compat"]
}

With a compatibility date from late 2024 onward you get the second-generation implementation, which brings the node: modules plus the globals people actually reach for: Buffer, process, setImmediate. node:crypto, node:path, node:events, node:stream, node:util, node:assert and AsyncLocalStorage all behave.

Then a dependency throws in production and you find the edge.

Build time tells you nothing

This is what makes it awkward to debug. The import resolves, the bundle builds, wrangler dev starts, and the failure waits for the specific request that reaches the specific code path. A library that touches node:fs in an error handler is fine until something errors.

So the useful question isn’t “is this package on the supported list.” It’s “what does this package do when it runs.”

The execution model is the real constraint

Missing modules can be polyfilled. These can’t:

There’s no filesystem. Any library that reads a config file at startup, writes a cache to /tmp, or loads a WASM binary from disk has no path forward on Workers, only a rewrite that passes bytes in directly.

There’s no long-lived process. A Worker exists for a request. Background timers, connection pools warmed at boot, and in-memory caches you expect to persist all evaporate. Durable Objects exist precisely for the state that needs to outlive one request.

Global scope can’t do I/O. Module top level runs during startup, so a client that fetches credentials at import time fails there. Move it inside the handler.

And process.env is real, but it’s populated from your bindings and vars, not from the machine. Nothing reads a .env file at runtime.

What I do now

Before adding a dependency, I check whether it wants a disk or a lifetime. Everything else the flag handles well enough, and it handles more of it every release.