Two years ago I told a client “no” when they asked if we could run their existing Express API on Cloudflare Workers. The nodejs_compat flag existed but felt half-finished — enough polyfills to demo, not enough to trust with production traffic. On September 9, Cloudflare flipped Node.js compatibility on by default for new Workers, raised the size cap to 64 mebibytes, and rebuilt the module registry around URLs instead of the old bundler-dependent resolution. I had a small internal API sitting exactly on that fence, so I spent an afternoon actually moving it instead of reading the announcement and nodding.

What I migrated

A ~40-route internal service: Express-style routing, a couple of npm dependencies for JWT verification and date handling, reading from a Postgres instance over pg. Nothing exotic — which is exactly the kind of workload that used to hit the wall on Workers, because pg’s native bindings and Node’s net module assumptions never fully worked under the old nodejs_compat polyfill set.

// wrangler.jsonc — this is the entire compat change now
{
  "name": "internal-api",
  "main": "src/index.ts",
  "compatibility_date": "2026-09-09",
  "compatibility_flags": ["nodejs_compat"]
}

Before September 9, that flag got you Buffer, EventEmitter, and parts of crypto — enough for a lot of libraries, not enough for anything doing raw TCP. pg needs raw TCP. It used to fail immediately with a cryptic Cannot resolve module "net" error that sent people down a rabbit hole of alternative HTTP-based Postgres drivers. Now it resolves and connects. That single change is the difference between “rewrite your data layer for Workers” and “deploy your existing data layer to Workers.”

Where the 64 MiB cap actually matters

The old 10 MB compressed limit wasn’t really about your code — it was about your node_modules. Anything that pulled in a moderately complex dependency tree (an ORM, a PDF library, an image processor) blew past it before you’d written a single route. My bundle for this service came in at 18 MB after esbuild with tree-shaking, dependencies included. Under the old cap that’s a hard failure. Under 64 MiB it deploys with headroom to spare.

The number matters less than what it signals: Cloudflare is explicitly telling you “stop pre-optimizing your bundle size before you’ve even shipped a working version.” I’ve spent real client hours in the past manually splitting worker bundles or hand-rolling lazy imports specifically to duck under 10 MB. That work is now wasted effort for most services, not because Workers got faster, but because the constraint that justified it went away.

The module registry change is the part nobody’s talking about

The headline feature is Node.js compat. The change I actually noticed while migrating was the URL-based module resolution. Workers can now resolve modules the way browsers do — by URL, with lazy compilation, rather than requiring everything to be pre-bundled into one flat blob ahead of deploy time. Combined with a shared code cache across isolates, cold start on my service dropped from roughly 40ms to under 15ms on a warm cache, because Cloudflare isn’t recompiling the same dependency graph for every new isolate spun up.

// import.meta now behaves like it does in Node/browsers,
// not a Workers-specific stub
if (import.meta.url === new URL(process.argv[1], "file://").href) {
  runMigrations();
}

That’s a small thing on its own, but it’s the kind of small thing that used to force a typeof importMeta !== "undefined" guard scattered through code that had to run on both Node and Workers. Removing that divergence removes an entire category of “works locally, breaks on deploy” bugs.

What still didn’t work

Not everything is free. Two of my dependencies used fs for reading a local config file at startup — a pattern that makes no sense on Workers regardless of compat flags, since there’s no filesystem to read from at the edge. That’s not a Cloudflare gap; it’s a reminder that “Node.js compatible” means API-compatible, not deployment-model-compatible. I swapped those reads for env bindings, which is the correct fix on any serverless platform, not a Workers-specific workaround.

The actual takeaway for a migration decision

If you rejected Workers for an existing Node service in the last year or two because of nodejs_compat gaps, that decision is stale — go re-test it, because the failure mode you hit before (raw TCP, native modules, filesystem-shaped assumptions) is a meaningfully smaller set now. If your service genuinely needs a real filesystem or long-lived TCP server semantics beyond outbound connections, Workers still isn’t the right target, and no compat flag changes that. The useful question isn’t “is Node.js compat good now” — it’s “which specific API my service depends on used to be missing, and is it on the list that just got added.” That’s a one-afternoon test, and it’s worth running before you write off the platform again.

Export for reading

Comments