Skip to main content
node

What Node 24 LTS Means for Frontend-Heavy Teams

Node 24 LTS ships with native fetch, WebSocket, improved ESM, and a permission model. Here's what frontend teams building full-stack apps should care about.


6 min read

Node 24 entered LTS in October 2025. If you're running Next.js with a Node backend, or a separate Express/Fastify API server, there are concrete things you can remove from your package.json and concrete things you can stop polyfilling. The permission model is worth understanding even if you don't use it yet. The V8 bump matters if you write any code that used to require a build step. Here's what actually changes.


Native fetch: what you can remove

Node 18 shipped an experimental fetch. Node 21 unflagged it. Node 24 ships it as fully stable and spec-compliant. For most teams, this means removing node-fetch, cross-fetch, or isomorphic-fetch from your dependencies.

Check your package.json:

# Find fetch polyfills you can remove
grep -E "node-fetch|cross-fetch|isomorphic-fetch|whatwg-fetch" package.json

If you're on a Next.js project, next has been polyfilling fetch internally for server components since Next 13. With Node 24 LTS, that polyfill resolves to the native implementation rather than a bundled one. You shouldn't need explicit fetch imports in your API routes.

The behavior difference from node-fetch v2 that trips teams up: native fetch streams the response body by default, and calling .json() consumes the body. If you have code that calls response.json() and then response.text() on the same response (which didn't fail in some node-fetch versions due to buffering), it will fail on native fetch. Audit your fetch call sites for double-body consumption.

Native WebSocket client

Node 24 includes a stable WebSocket global — a proper browser-compatible WebSocket client, no package required. This matters if you're writing server-side code that connects to WebSocket services: real-time data feeds, internal services, or WebSocket-based APIs.

Before Node 24:

import WebSocket from "ws"; // had to install `ws`

const socket = new WebSocket("wss://feed.example.com/live");
socket.on("message", (data) => { ... });

After Node 24:

// No import needed — WebSocket is global
const socket = new WebSocket("wss://feed.example.com/live");
socket.addEventListener("message", (event) => { ... });

Note the API difference: native WebSocket uses addEventListener (browser-style events), while ws uses .on() (Node EventEmitter style). If you migrate, you'll need to update the event binding syntax. The ws package also has features Node's native implementation doesn't yet match — specifically, some server-side WebSocket server scenarios still need ws. But for client connections in a Node service, the native global is now enough.

Remove ws from your package.json if you're only using it as a client:

// Before
"dependencies": {
  "ws": "^8.16.0"
}

// After — check you're actually using it as a server too before deleting

The ESM story in Node 24

ESM module resolution has been progressively stabilized since Node 12. Node 24 brings two meaningful improvements:

Import attributes are stable. You can now import JSON files with the with syntax:

import config from "./config.json" with { type: "json" };

No more createRequire workarounds or dynamic fs.readFileSync + JSON.parse for config files.

The --experimental-require-module flag is now on by default. This means require() can now load synchronous ES modules in Node 24. If you've been wrestling with packages that only ship ESM but your tooling uses CommonJS — this closes a lot of those compatibility gaps. The practical effect: fewer packages that break your build because they went ESM-only.

For Next.js projects: Next.js's bundler (Turbopack/webpack) handles ESM internally, so these Node changes mostly matter for your non-bundled code — scripts, API servers, CLI tools, test runners running directly in Node.

In your package.json, verify your top-level type field:

{
  "type": "module"
}

If you're not using "type": "module", your .js files are still CommonJS. Node 24 doesn't force a migration, but if you're starting a new service, default to "type": "module" and use .cjs for the rare CommonJS exception.

The permission model

Node 24 brings the --allow-fs and --allow-net flags out of experimental. This is a filesystem and network capability model for Node processes:

# This process can only read from /var/data, cannot write, cannot make network calls
node --allow-fs-read=/var/data server.js

# This process can make network calls to one host only
node --allow-net=api.internal.example.com worker.js

For most production Next.js apps, you're not running Node directly with these flags — your platform (Vercel, Railway, EC2 behind a process manager) handles the process. But there are two scenarios where this is genuinely useful:

Build scripts and data-processing scripts that only need to read from a specific directory. Running them with --allow-fs-read=./data --allow-fs-write=./dist makes any accidental file access outside those paths throw immediately rather than silently succeed.

Serverless function sandboxing in self-hosted environments. If you're running Node functions without a managed platform, the permission model gives you a native (no-dependency) way to constrain what a function can touch. Previously this required containers or process sandboxing at the OS level.

V8 upgrade and what syntax it unlocks

Node 24 ships V8 12.4. The practical syntax additions that are now available without a transpilation step:

Promise.try() — wraps synchronous code that might throw into a promise chain cleanly:

// Before: wrap sync code in a promise manually
const result = await new Promise((resolve, reject) => {
  try {
    resolve(parseSomething(input));
  } catch (e) {
    reject(e);
  }
});

// After: Promise.try handles sync throws natively
const result = await Promise.try(() => parseSomething(input));

RegExp.escape() — escapes a string for use in a regular expression, no manual escaping utility needed:

const userInput = "fee.fi.fo.fum";
const pattern = new RegExp(RegExp.escape(userInput)); // dots escaped correctly

Iterator helpers (Iterator.prototype.map, .filter, .take) — lazy iteration over generators and iterables without converting to arrays first. Useful for processing large datasets in streaming pipelines.

What to actually do when you upgrade

  1. Remove node-fetch, cross-fetch, isomorphic-fetch if present. Test that your fetch call sites don't double-consume response bodies.
  2. Audit ws usage — remove if you only use it as a client, keep if you use the WebSocket server.
  3. Add "type": "module" to new services. Migrate existing services opportunistically.
  4. Update your .nvmrc or engines field in package.json:
    { "engines": { "node": ">=24.0.0" } }
    
  5. Run your test suite — the ESM require() changes can surface latent compatibility assumptions.

The LTS guarantee means security patches through April 2027. Upgrading now is the stable choice.

What doesn't change

TypeScript still needs compilation. JSX still needs a transform. Bundling for the browser is still necessary. Node 24 doesn't collapse the frontend build pipeline — it trims the edges. The meaningful wins are fewer polyfill packages, better ESM compatibility for Node-specific scripts and tools, and a WebSocket global that removes a common dependency. For a team running 20+ Node services, that's real reduction in dependency surface area. For a solo developer on one Next.js app, it's incremental improvement with a clear upgrade path.