Node.js compatibility gets a hybrid upgrade on Workers
Cloudflare has announced a preview of improved Node.js compatibility for Workers and Pages. The new approach — enabled with the nodejs_compat_v2 compatibility flag — combines native runtime implementations of some Node.js APIs with build-time polyfills for others, and drops the requirement to use the node: prefix when importing Node.js modules.
That last point matters more than it might appear. While Cloudflare added polyfill support for Node.js APIs back in 2022 and followed up with native implementations the next year, existing NPM packages often import Node.js modules without the node: prefix. As a result, many packages that depend on modules like events or fs failed to load even with the nodejs_compat flag enabled.
With the new flag, packages that previously couldn’t be imported even as dependencies — including body-parser, jsonwebtoken, got, passport, md5, knex, mailparser, csv-stringify, cookie-signature, and stream-slice — now load on Workers. Support has also expanded for the async_hooks, buffer, dns, os, and events APIs, while modules such as fs and process can be imported with mocked methods.
Why Workers isn't simply Node.js
The gap between the two runtimes is foundational. Node.js was designed for services running directly on a host OS, so it includes host-facing modules like process and fs. Cloudflare Workers, by contrast, run on workerd, an open-source JavaScript/Wasm runtime engineered to run untrusted code in shared processes. Workerd exposes web-standard APIs wherever possible and uses bindings for interoperability with Cloudflare services.
For example, importing the popular PostgreSQL driver pg without Node.js compatibility enabled fails at build time with an error, because the package imports Node.js's events module, which workerd doesn't provide by default.
From polyfills to native code and back again
Cloudflare's first solution, introduced in 2022, was to use Wrangler to inject polyfills into Workers when the node_compat = true flag was set. This worked for some packages but had real limitations. Buffer operations — like copy, concat, substring searches, and transcoding — are far slower in pure JavaScript than when backed by native primitives. The same was true for Crypto, AsyncLocalStorage, and Stream modules.
The second wave, starting in 2023, implemented a subset of Node.js APIs directly in the runtime. Because these APIs were written in C++, they were faster and could support features like AsyncLocalStorage that are difficult to polyfill safely. But packages still had to import them with the node: prefix, which many real-world NPM packages didn't do. Users were left choosing between a small set of performant but hard-to-access native APIs, or a larger set of slower polyfills.
A hybrid runtime-plus-polyfill model
Improved compatibility merges both approaches. When the nodejs_compat_v2 flag is set, imports like buffer resolve to a JavaScript module in workerd backed by C++ code, while imports like net are automatically polyfilled by Wrangler using the unenv library. The node: prefix is no longer required — though importing with node:buffer still works.
The polyfills are also applied more judiciously than before. Previously, setting node_compat = true injected polyfills for every Node.js API Wrangler could provide, whether or not the code used them. With nodejs_compat_v2, Wrangler only adds polyfills for APIs actually referenced by your Worker or its dependencies, keeping bundle sizes smaller.
For Node.js APIs with neither native support nor a polyfill, unenv provides a "mocked" interface: the module and its methods load, but method calls either do nothing or throw an error like:
[unenv] <method name> is not implemented yet!
That distinction is significant. A package that depends on the fs module, for instance, can now be imported when it previously couldn't resolve at all. Methods that don't rely on unimplemented functionality work; those that do throw a clear runtime error rather than a build-time failure.
Module aliasing for gaps in coverage
For packages that still need a Node.js API with no built-in or polyfill implementation, Wrangler's module aliasing can fill the gap. A Worker that needs fs.readFile, for example, can alias the fs module in wrangler.toml to a local file with a custom implementation of the required method. Code that previously threw an unenv error runs normally once the alias is in place.
The same technique handles transitive dependencies. Some packages depend on node-fetch, which relies on unsupported http and https modules. Since Workers provide a built-in fetch() API, imports of node-fetch can be aliased to a small module that simply re-exports the native fetch implementation. The popular nolyfill package automates this kind of aliasing.
Availability and next steps
The improved behavior is currently available to users who add compatibility_flags = ["nodejs_compat_v2"] to wrangler.toml. Cloudflare plans to make the new model the default for all Workers enabling the nodejs_compat flag with a compatibility date of 2024-09-23 or later.
Cloudflare is contributing its polyfill work back to unenv, the library that powers the hybrid approach. unenv targets multiple runtimes beyond workerd and is already used by projects like Nuxt and Nitro. Users testing the preview are encouraged to report bugs and gaps in support by opening an issue on the workers-sdk GitHub repository.



