PAS7 Studio

Technology

Node.js 25: what’s actually new, what broke in the ecosystem, and whether you should upgrade

A real-world review of Node.js 25 (Current): V8 14.1 and performance, Web Storage enabled by default (breaking tests/tools), the permission model with --allow-net, portable module compile cache (now stable), require(esm) marked stable, http.setGlobalProxyFromEnv(), fs.watch got ignore, and SEA can be built in one step with node --build-sea. Developer perspectives with links + a clear verdict: upgrade now or wait for LTS.

Node.js 25 — changes, developer reactions, and whether you should upgrade

Prologue: you upgrade Node — and suddenly “localStorage” breaks your build

There are two kinds of Node upgrades. The first is “bump the version, everything works.” The second is “bump the version, and now your backend throws browser-looking errors.” Node.js 25 made a lot of teams experience the second kind.

Docusaurus looked surreal: DOMException [SecurityError]: Cannot initialize local storage without a --localstorage-file path during docusaurus build.[1] Similar symptoms showed up in the CLI world (Shopify) and in test runners (Jest/Vitest).[2][3][4]

This matters: it’s not just “a random bug in your repo.” It’s a consequence of Node 25 enabling Web Storage by default, while Node’s localStorage is not a 1:1 browser storage (file-backed, quota, shared behavior, no encryption).[7][5]

And still: it would be a mistake to reduce Node 25 to the Web Storage saga. 25.4.0 and 25.5.0 shipped genuinely mature improvements (require(esm) stable, compile cache stable, --build-sea) that reduce migration pain and improve DX — if you adopt them deliberately.[9][10][12]

A real case: docusaurus build fails on Node 25.x due to a localStorage SecurityError.[1]

Section prologue screenshot

A tiny Node 25 timeline: why 25.4 and 25.5 matter as much as “25.0.0”

Node 25 is a Current line (odd). It moves fast: first, new defaults land; then the ecosystem finds edge cases; then stabilization releases ship.[6]

  • 25.0.0: V8 14.1, performance focus, web APIs, permission model, Web Storage enabled by default.[8]

  • 25.2.0: the localStorage regression without --localstorage-file surfaces (nodejs/node #60704) and impacts toolchains.[5]

  • 25.4.0: require(esm) becomes stable, module compile cache becomes stable, ops goodies like http.setGlobalProxyFromEnv() land.[9]

  • 25.5.0: node --build-sea arrives — SEA “one step from core”, plus small but meaningful ops/monorepo changes.[12]

Quick terms so you don’t have to google every 2 minutes

Just enough vocabulary to make Node 25 readable.

  • Current vs LTS: odd major = Current, even major = LTS line. Node explicitly recommends production use Active/Maintenance LTS.[6]

  • Web Storage in Node: Node localStorage is file-backed (--localstorage-file), unencrypted, has a 10MB quota, and is shared within a server process.[7]

  • Permission Model: process permissions (--permission, --allow-net, --allow-fs-read) as a seatbelt. It’s not a sandbox and provides no guarantees against malicious code.[11]

  • require(esm): a bridge between CJS and ESM. Node 25.4.0 marks the mechanism stable (which changes migration strategy).[9]

  • Module compile cache: on-disk code cache for speeding up module graph compilation, with a portable mode.[10]

  • SEA: single executable applications. 25.5.0 makes this much easier with --build-sea.[12][13]

  • Ops QoL: proxy from env (http.setGlobalProxyFromEnv()), watcher ignore (fs.watch({ ignore: ... })).[14][15]

Node 25.0.0: what’s new “at the base”, and where the practical value is

25.0.0 is the foundation: V8 14.1, performance work, web APIs, and legacy cleanup. The real “maturity” shows up in 25.4/25.5.[8][9][12]

V8

14.1

The 25.0.0 notes highlight performance improvements, including JSON.stringify.[8]

Web Storage

enabled by default

A bold default change that triggered ecosystem regressions (tools/tests/CLIs).[8][5][4]

Security posture

permissions

Permissions are framed as “secure-by-default”, with clear constraints (not a sandbox).[11]

Legacy cleanup

deprecations

Major releases often hurt via deprecated API removal (SlowBuffer is a good example).[16]

A visual marker of the line start: Node.js 25.0.0 (Current).[8]

Section node-25-0 screenshot

Web Storage by default: why it blew up, and how it shows up in real projects

The core issue is not that Web Storage is “bad”. The issue is that the default behavior changed, and parts of toolchains started executing branches they never tested on the server.

nodejs/node #60704 describes the 25.2.0 regression: “Cannot initialize local storage without a --localstorage-file path”. Importantly, it references broken webpack/jest/html-webpack-plugin chains.[5]

Vitest issue #8757 shows another failure mode: in Node 25 localStorage is no longer undefined, which can break mocks in test environments.[4] Jest issue #15888 adds another data point: the test runner fails with the same SecurityError on Node 25.2.0.[3]

Fast incident workaround (when you don’t need storage): temporarily disable Web Storage via the flag documented in Node’s globals docs.[7]

Team lesson: even if “you don’t use localStorage”, your dependencies might. That’s why keeping Current in a CI matrix is a rational way to detect surprises early.[6]

nodejs/node #60704: Node 25.2.0 regression — touching localStorage without --localstorage-file can crash builds.[5]

Section webstorage-saga screenshot

Vitest #8757: Web Storage enabled by default in Node v25 can break tests due to changed localStorage expectations.[4]

Section webstorage-saga screenshot

Jest #15888: the runner fails with SecurityError on Node 25.2.0.[3]

Section webstorage-saga screenshot

Node 25.4.0: require(esm) becomes stable (and that changes how you migrate)

This is the kind of change that doesn’t scream in headlines, but saves weeks in repos where CJS and ESM must coexist.

1) Why it matters

As more packages go ESM-only, older CJS code repeatedly hits ERR_REQUIRE_ESM. A more mature bridge lets teams migrate incrementally rather than rewriting everything at once.[9][22]

2) Practical pattern: default export adapter

JS
const pkg = require('some-esm-only-package');
const api = pkg?.default ?? pkg;

module.exports = api;

This isn’t a “hack”. It’s a standard way to handle the namespace object when ESM provides a default export.[22]

3) What it signals for teams

Stabilization in Current is preparation for the broader ecosystem. Keeping LTS+Current in CI lets you capture benefits without taking prod risk.[6]

A visual marker of a “stabilization” point: Node.js 25.4.0 (Current).[9]

Section require-esm screenshot

Module compile cache: portable mode and “grown-up” docs — what it gives you in CI/containers

Compile cache is an optimization. It shines when you have many cold starts or a large module graph (tools, CLIs, workers).

The node:module docs include a very practical portability section: if absolute paths change, cache effectiveness can drop; portable mode helps reuse in more scenarios.[10]

A minimal experiment setup:

JS
import module from 'node:module';

module.enableCompileCache({ directory: '.node-compile-cache', portable: true });

If you test this in CI, manage the cache directory explicitly so it survives between runs.

Docs: “Portability of the compile cache” + portable mode with API and env examples.[10]

Section compile-cache screenshot

Node 25.5.0: `node --build-sea` makes SEA a “single-step” workflow

This is one of the most product-level changes in the line: building a single executable no longer looks like an external-injector ritual.

1) The core idea

25.5.0 introduces --build-sea and consolidates the previous SEA steps into a single step available from Node.js core.[12][13]

BASH
echo 'console.log("Hello")' > hello.js
echo '{ "main": "hello.js", "output": "sea" }' > sea-config.json
node --build-sea sea-config.json
./sea

2) Practical takeaway

For CLIs and local agents: fewer dependencies and fewer brittle release steps. But test on target OS/shells (there are reports about Windows Command Prompt quirks).[21]

3) Team advice

SEA is a distribution tool, not a security model. Don’t use it as a substitute for dependency updates and supply-chain hygiene. Keep Current in CI to catch surprises early.[6]

25.5.0: the --build-sea section and the command example in the official release notes.[12]

Section sea-build-sea screenshot

Ops QoL: proxy from env and a clean path for corporate networks

Not headline features — but they reduce “tiny custom wrappers” in projects with corporate CI constraints.

Minimal snippet:

JS
import http from 'node:http';

const restore = http.setGlobalProxyFromEnv();
// restore();

API

http.setGlobalProxyFromEnv()

Useful when proxies are configured via env (CI/enterprise networks).[14]

Tip

call on startup

Avoid calling it mid-request; do it during bootstrap of a service/CLI.[14]

Outcome

more consistent

Less chance each HTTP client ends up configured differently.

Docs: http.setGlobalProxyFromEnv([proxyEnv]) + built-in proxy support section.[14]

Section ops-qol screenshot

Monorepo pain: `fs.watch({ ignore })` as a simple way to reduce watcher noise

In large repos, watchers are their own world of pain (node_modules, .git, generated dirs). The docs describe ignore as an option that can take glob/RegExp/function/array.[15]

A minimal example for dev tools:

JS
import { watch } from 'node:fs';

watch('.', {
  recursive: true,
  ignore: ['**/node_modules/**', '**/.git/**'],
}, () => {});

Breaking changes: how Node 25 breaks deps via “legacy cleanup” (not new features)

Major Node releases often hurt not via brand-new APIs but via deprecation finalization. SlowBuffer is a clean example.[16]

1) What happened to SlowBuffer

Node’s deprecations docs state SlowBuffer was removed and recommend Buffer.allocUnsafeSlow(size).[16]

2) How it looks “in the wild”

There’s a real case of a backend crashing on Node 25.x due to a dependency chain touching SlowBuffer.prototype.[17]

3) Quick pre-upgrade scan

BASH
rg -n "\bSlowBuffer\b" .

Then pnpm why / npm ls to identify the legacy dependency chain.

Upgrade playbook: how to adopt Node 25 for value (not pain)

A short “battle-tested” checklist teams actually use.

  • 1) Keep prod on LTS, add Node 25 in CI (LTS + Current matrix).[6]

  • 2) Run tests with Web Storage disabled to quickly isolate the source: NODE_OPTIONS="--no-experimental-webstorage".[7]

  • 3) If it broke, look where it already broke: nodejs/node #60704, Vitest #8757, Jest #15888.[5][4][3]

  • 4) If you want 25.4/25.5 features, adopt them first in a separate experiment (CLI/worker), not by flipping prod runtime in one go.[9][12]

  • 5) For monorepos, tune watchers: fs.watch({ ignore }) reduces noise and load.[15]

  • 6) For corporate CI behind proxies, try http.setGlobalProxyFromEnv() as a simple global approach.[14]

Verdict: should you upgrade to Node.js 25 now?

No ideology: Node 25 is great as a “future breakage detector” in CI, and it’s useful for certain products (CLIs/agents). But for production backends, LTS often wins on stability alone.

Production backend

Mostly wait / stay on LTS

Node recommends production run on Active/Maintenance LTS.[6]

CI / compatibility

Add it now

Node 25 surfaces dependency regressions (Web Storage) and legacy cleanup (SlowBuffer) without prod incident risk.[5][16]

CLIs / agents

Upgrade is reasonable

--build-sea (25.5.0) + 25.4.0 maturity are real DX wins. Test on target OS/shells.[12][21]

Mentally

Current is allowed to bite

Current exists so the ecosystem can adapt. If it breaks your day, keep it in CI and keep prod on LTS.[6]

Sources

Sources in order of appearance (position in list = footnote number [n]).

FAQ

Is Node.js 25 an LTS release?

No. It’s Current (odd major). Node explicitly states production apps should use Active or Maintenance LTS.[6]

Why did Web Storage blow up so loudly?

Because the default changed. `localStorage` became available without an experimental flag, but Node’s semantics differ (file, 10MB, shared). Tooling assumptions broke.[7][5][4]

How do I unblock CI fast if localStorage breaks everything?

Temporarily disable Web Storage via `NODE_OPTIONS="--no-experimental-webstorage"` or a runtime flag. It’s documented in globals and referenced as a workaround in regression threads.[7][5]

Can I use SEA with --build-sea already?

Yes, but test on target OS/shells. There’s a Windows Command Prompt specific issue.[21] For config/process details see SEA docs.[13]

In one sentence: take the value of Node 25 without taking its prod risk head-on

The healthiest strategy for most teams: keep prod on LTS, run Node 25 in CI as an early regression detector, and adopt 25.4/25.5 features (require(esm), compile cache, build-sea) first in isolated experiments (CLI/worker). You get the DX upside without “the day localStorage broke the build”.

Related Articles

Explore more useful articles

growthFebruary 15, 2026

AI SEO / GEO in 2026: Your Next Customers Aren’t Humans — They’re Agents

Search is shifting from clicks to answers. Bots and AI agents crawl, cite, recommend, and increasingly buy. Learn what AI SEO / GEO means, why classic SEO is no longer enough, and how PAS7 Studio helps brands win visibility in the agentic web.

Read →
telegram-media-saverJanuary 8, 2025

Automatic Tagging & Search for Saved Links

Integrate with GDrive/S3/Notion for automatic tagging and fast search via search APIs

Read →
servicesJanuary 1, 2025

Bot Development & Automation Services

Professional Telegram bot development and business process automation: chatbots, AI assistants, CRM integrations, workflow automation.

Read →
backend-engineeringFebruary 15, 2026

Bun vs Node.js in 2026: Why Bun Feels Faster (and How to Audit Your App Before Migrating)

Bun is shipping a faster, all-in-one JavaScript toolkit: runtime, package manager, bundler, and test runner. Here’s what’s real (with benchmarks), what can break, and how to get a free migration-readiness audit using @pas7-studio/bun-ready.

Read →

Professional development for your business

We create modern web solutions and bots for businesses. Learn how we can help you achieve your goals.