Skip to content

The Node.js 24 Features Worth Changing Your Code For

Node.js 24's changelog has dozens of entries. Here are the handful that actually change how you write day-to-day code, filtered from the ones that don't.

· · 8 min read
A close up of a network with connected wires

Quick Take

Node.js 24's changes worth actually adopting: the permission model (`--permission`) reaching stability for sandboxing file/network access, `require()` supporting synchronous ESM imports in more cases (reducing CommonJS/ESM friction), further native TypeScript support improvements, and V8 engine updates that apply automatically with no code changes needed. Most of the sixty-plus changelog entries are internal or edge-case fixes that don't change day-to-day code.

Node 24's full changelog runs past sixty entries between the initial release and its LTS promotion, most of it internal, dependency bumps, or edge-case fixes that don't touch how application code gets written. Here's the subset that's actually worth knowing, filtered down from a changelog most developers reasonably skip reading in full.

What Does Node's Permission Model Do?

node --permission --allow-fs-read="/app/data" --allow-net server.js

Node's experimental permission model, first introduced years earlier, stabilized in the Node 24 line, letting you run a script with explicit, enforced restrictions on filesystem access, network access, and child process spawning. A script started with --permission and without --allow-fs-write throws a runtime error the moment it attempts a write, rather than a script's actual capabilities being defined only by what the surrounding OS user account happens to allow.

// Check permissions at runtime, useful for a library that wants to
// behave differently when it detects restricted permissions
import { permission } from 'node:process';

if (!permission.has('fs.write')) {
  console.warn('Running with read-only filesystem access');
}

This matters most for anything running third-party or AI-generated code with reduced trust, a plugin system, a code execution sandbox, or a CI step running a dependency's install script. Previously, that isolation required a container or a separate OS user; Node's own permission model provides a lighter-weight boundary for cases where full container isolation is overkill.

Previously, sandboxing untrusted code required a container or a separate OS user. Node's own permission model now provides a lighter-weight boundary for cases where full container isolation is overkill.

Share this Post on X Bluesky

How Does require() Handle ESM Interop Now?

// This now works directly in more cases without a dynamic import() workaround
const { someExport } = require('some-esm-only-package');

require() can now load most ESM-only packages synchronously from CommonJS files, support that landed across the Node 22 to 24 line. That closes a friction point going back years: a CommonJS project needing one ESM-only dependency had to reach for await import() at the top level, or convert the whole file to ESM just to use a single package.

It doesn't apply universally. A package that uses top-level await internally is genuinely asynchronous and still needs the dynamic import path, because there is no way to make that synchronous without blocking the loop. Node throws a clear ERR_REQUIRE_ASYNC_MODULE rather than failing quietly, so you find out immediately.

For everything else, and the straightforward ESM package is the common case, the interop just works. If your codebase carries a // eslint-disable next to a dynamic import that exists only to pull in one dependency, that comment can probably go.

How Has Native TypeScript Support Improved?

node src/index.ts   # runs directly, no ts-node/tsx, no build step

Native TypeScript execution improved mainly in source maps: stack traces now point at the original .ts line rather than the stripped output. The underlying type stripping arrived in Node 22.6 and stabilized in 23.6; Node 24 refines the developer experience around it rather than adding new capability.

Of everything in this release, this is the change with the most direct effect on daily work. Running a script or a CLI tool straight against .ts source, with no compile step and no ts-node dependency, removes an entire class of tooling from small projects.

The limit is worth stating plainly, because it surprises people: Node strips types, it does not check them. Nothing validates your annotations at runtime, so tsc --noEmit stays in CI regardless. Anything needing real transformation rather than erasure, which in practice means enums and decorators, still needs a compiler.

What's in the Changelog but Not Worth Changing Code For?

  • V8 engine version bumps: newer JavaScript syntax support and JIT performance improvements apply automatically. No code change needed, no action required beyond upgrading.
  • npm version bump bundled with Node: relevant if you rely on the bundled npm specifically, most projects using a separate package manager (pnpm, Yarn) aren't affected.
  • Internal dependency updates (OpenSSL, ICU, etc.): security and correctness fixes that matter for the runtime's own safety, not something application code interacts with directly.
  • Deprecation warnings for older APIs: worth noting if your codebase uses the specifically-flagged deprecated API, otherwise safe to ignore until the next major version actually removes it.

The distinction that matters is between changes you adopt and changes that adopt you. Roughly four of the sixty-odd entries in a typical Node changelog need a decision from you; the rest arrive with the binary and improve things without asking. Reading the changelog as a to-do list is how upgrades turn into week-long projects.

None of the four items above belongs in a migration plan. If a V8 bump breaks something, that is a bug report rather than a task, and it will be fixed upstream faster than you can work around it. The two features from this release that do need a decision are the permission model and, for TypeScript projects, whether to drop the loader dependency. Both are reversible in a single commit, which is the useful thing to know before scheduling either.

Which Changes Need Code and Which Are Free?

FeatureRequires code changes?Why it matters
Permission model (--permission)Yes, opt-in flagsSandboxes fs/network access for untrusted or AI-generated code
require() ESM interopNo, works automatically for common casesRemoves need for dynamic import() workarounds
Native TypeScript executionNo, run .ts files directlyNo ts-node/tsx, better source-mapped stack traces
V8 engine bumpsNoNew JS syntax and JIT gains apply automatically
Bundled npm versionNo, unless using bundled npm directlyIrrelevant if using pnpm/Yarn
Deprecation warningsOnly if using the flagged APISafe to ignore until next major removes it

Only the first row of that table costs you anything, and reading it top to bottom is the fastest way to size an upgrade: five of six features arrive with the binary and need no decision from anyone. Only the first row costs you anything. The permission model is opt-in by design, because turning it on by default would break every application that reads a config file, so it is the one feature here that needs a deliberate decision and some testing.

Plan an upgrade in that order:

  1. Bump the runtime and run the existing test suite unchanged. Rows two through six should need nothing from you.
  2. Delete the workarounds the release made unnecessary: dynamic imports that only existed for interop, ts-node from scripts that just run a file.
  3. Adopt the permission model last, on one service, with flags scoped as tightly as the app tolerates. Expect two or three rounds of widening the paths before the app starts cleanly; that iteration is the point of the exercise rather than a sign it isn't working.

Two of the three features worth adopting cost nothing to try, which is a good argument for doing the upgrade in one sitting rather than scheduling it. Three terms get mixed up in Node upgrade threads and mean different things:

  • Type stripping removes annotations without checking them. That is what Node does natively.
  • Transpilation rewrites syntax into something the runtime understands, which is what enums and decorators still require.
  • Type checking validates the annotations. Node never does this, and tsc --noEmit remains your only source of truth.

How Do You Check Compatibility Before Upgrading?

npx npm-check-updates --target minor  # check which deps have Node 24-tested versions
node --version                          # confirm the target version

Check the engines field across your dependency tree for anything capping at an older Node major, then run the full test suite against Node 24 in CI before merging the version bump. Native modules are the usual holdout, since they compile against a specific Node ABI and need a release built for it. better-sqlite3, sharp, and anything wrapping a C library fall in this group, and a stale one shows up as an install-time build failure rather than a runtime error, which is at least a fast signal. Check them first rather than last, because a missing prebuilt binary can block the upgrade for weeks while everything else is ready. Most compatibility breaks in a Node major upgrade come from a native-module dependency (anything requiring node-gyp compilation) not yet supporting the new ABI, not from your own application code.

Conclusion

Reading a Node release's full changelog isn't necessary to stay current, most of what changes in any given major version is internal or transparent. The permission model reaching stability, the continued easing of CommonJS/ESM interop friction, and native TypeScript execution maturing further are the three changes in Node 24 actually worth adjusting a workflow around; the rest arrives automatically the moment you upgrade.

Frequently Asked Questions

Is Node.js 24 an LTS release?
Node.js follows a predictable release cadence: even-numbered major versions (20, 22, 24) become LTS (Long-Term Support) roughly six months after their initial release, once they've had time to stabilize. Node 24 entered LTS status in late 2026, following that same pattern, meaning it's the version to target for production deployments expecting multi-year support, rather than the newer odd-numbered releases meant for early adopters.
Do I need to change my code to benefit from Node 24's V8 engine update?
No, most V8 engine updates (newer JavaScript language features, performance improvements to the JIT compiler) apply automatically just by running on the newer Node version, no code changes required. The features worth deliberately adopting are the ones that require you to actually write different code to use, permission model stabilization, updated built-in APIs, not the transparent engine-level improvements that just make existing code faster or support newer syntax automatically.
Should I upgrade a production app straight to Node 24, or wait?
If your app is currently on Node 22 (the previous LTS), upgrading to Node 24 after it reaches LTS status is a reasonable, low-risk move for most codebases, test your CI suite against it first and check your dependencies' engines field for any that haven't caught up. If you're still on Node 18 or earlier, that's a bigger jump, worth doing but budget more time for testing given how much changed across two full major versions.