Node.js 24 Lands With V8 13.6, Default require(esm), and a Smaller Permission Model Footprint
Node.js 24 ships with V8 13.6, enables require() of ES modules by default, and promotes the permission model out of experimental status.
Node.js 24 is out, carrying the V8 13.6 engine and promoting a batch of APIs that spent the last two release cycles behind experimental flags. The headline change for most codebases: require() of native ES modules is now enabled by default, closing one of the longest-standing friction points between the CommonJS and ESM worlds.
The practical effect is that a .cjs file, or a CommonJS package, can now require() an ES module synchronously as long as that module graph contains no top-level await. Hit a top-level await and Node throws ERR_REQUIRE_ASYNC_MODULE, so dual-format packages still need to think about how their entry points resolve. This was available behind --experimental-require-module previously; it now ships on by default, which means library authors should retest interop assumptions before assuming the old failure modes still apply.
What's actually new
The permission model, introduced as experimental in earlier versions, drops the --experimental- prefix. The flag is now --permission, and the runtime can restrict file system reads and writes, child process spawning, and worker thread creation at the process boundary. This is not a sandbox in the V8 isolate sense, and the team has been explicit that it should not be treated as a hard security boundary against hostile code running in-process. It is closer to a guardrail against accidental access in untrusted dependency trees.
The built-in test runner continues to mature, with the node --test runner gaining more stable coverage reporting and glob-based test discovery. The URLPattern API is now global, matching browser behavior, which removes one more reason to reach for a routing micro-dependency.
Undici, the HTTP client that backs the global fetch, moves to version 7 in this line, bringing its own set of behavioral adjustments around connection handling and timeouts.
Breaking changes and migration path
The V8 jump to 13.6 brings the usual set of engine-level changes, but the breaking items worth flagging are the deprecations Node finally retired.
The url.parse() legacy API is further down the deprecation path, and code still leaning on it should move to the WHATWG URL constructor. Several long-deprecated fs callback signatures and older Buffer constructor patterns continue to emit warnings that teams running with --throw-deprecation in CI will need to address.
The interop change around require(esm) is the one most likely to surface in real builds. Bundlers and transpilers that special-cased the old throw behavior may now produce different output. Anyone shipping a package consumed across both module systems should run their published artifact against Node 24 directly rather than trusting the test matrix from the previous major.
For teams pinning runtimes in containers, the base image tags shift accordingly, and cold start characteristics under serverless platforms are worth re-measuring rather than assuming parity with Node 22.
The trade-off
Defaulting require(esm) to on is the kind of change that reduces friction for application authors while pushing more responsibility onto package maintainers, who now have to reason about synchronous versus asynchronous module graphs at their boundaries. The top-level await exception is the seam where that complexity lives.
The permission model lands in a similar spot: useful as a default-deny posture for scripts and CI tasks, but the framing matters. Treating it as equivalent to OS-level sandboxing or a proper isolate boundary would be a mistake, and the project documentation is careful to say so.
Adoption signal
Odd-numbered Node releases historically do not become LTS; the even-numbered successor inherits the long-term support window. Production teams that need stability will likely wait for the corresponding LTS line before migrating fleet-wide, while the more aggressive shops will adopt 24 in development now to surface interop breakage early.
The pattern across the broader JavaScript runtime space is consistent: Node, Bun, and Deno have all been converging on first-class ESM, built-in test runners, and tighter permission defaults. Node 24's choices here are less about novelty and more about catching the defaults up to where the ecosystem already expects them to be.