Tecnologia
Node.js 25: cosa c’è davvero di nuovo, cosa si è rotto nell’ecosistema e se conviene aggiornare
Recensione “dal mondo reale” di Node.js 25 (Current): V8 14.1 e performance, Web Storage abilitato di default (con rotture su test/tooling), permission model con --allow-net, module compile cache portabile (ora stabile), require(esm) marcato stabile, http.setGlobalProxyFromEnv(), fs.watch con ignore, e SEA costruibile in un solo step con node --build-sea. Punti di vista di developer con link + verdetto chiaro: aggiornare ora o aspettare LTS.

Prologo: aggiorni Node — e all’improvviso “localStorage” fa saltare la build
Ci sono due tipi di upgrade Node. Il primo è “alzo la versione, tutto ok”. Il secondo è “alzo la versione e ora il backend mi spara errori da browser”. Node.js 25 ha fatto vivere a molti team il secondo scenario.
Con Docusaurus sembra surreale: DOMException [SecurityError]: Cannot initialize local storage without a --localstorage-file path durante docusaurus build.[1] Sintomi simili compaiono anche nel mondo CLI (Shopify) e nei test runner (Jest/Vitest).[2][3][4]
Qui il punto è: non è un “bug random nel tuo repo”. È una conseguenza del fatto che Node 25 ha abilitato Web Storage di default, ma localStorage su Node non è identico al browser (file-backed, quota, comportamento shared, niente cifratura).[7][5]
Eppure sarebbe un errore ridurre Node 25 alla saga di Web Storage. 25.4.0 e 25.5.0 portano miglioramenti maturi (require(esm) stabile, compile cache stabile, --build-sea) che riducono il dolore delle migrazioni e migliorano DX — se li adotti in modo controllato.[9][10][12]
Caso reale: docusaurus build fallisce su Node 25.x per localStorage SecurityError.[1]
Mini-cronologia di Node 25: perché contano anche 25.4 e 25.5 (non solo “25.0.0”)
Node 25 è una linea Current (odd). Si muove veloce: prima arrivano nuovi default, poi l’ecosistema trova edge case, poi escono release di stabilizzazione.[6]
• 25.0.0: V8 14.1, focus performance, web API, permission model, Web Storage abilitato di default.[8]
• 25.2.0: emerge la regressione
localStoragesenza--localstorage-file(nodejs/node #60704) che colpisce toolchain.[5]• 25.4.0: require(esm) diventa stabile, module compile cache diventa stabile, arrivano “ops goodies” come
http.setGlobalProxyFromEnv().[9]• 25.5.0: arriva
node --build-sea— SEA “un solo step dal core”, più piccole ma utili modifiche ops/monorepo.[12]
Terminologia rapida (così non devi googlare ogni 2 minuti)
Solo i concetti indispensabili per capire Node 25 senza perdersi nei flag.
• Current vs LTS: odd major = Current, even major = LTS. Node raccomanda esplicitamente Active/Maintenance LTS per production.[6]
• Web Storage in Node:
localStoragesu Node è file-backed (--localstorage-file), non cifrato, con quota 10MB, e shared nel processo server.[7]• Permission Model: permessi di processo (
--permission,--allow-net,--allow-fs-read) come “cintura”. Non è un sandbox e non protegge da malicious code.[11]• require(esm): ponte tra CJS ed ESM. 25.4.0 segna il meccanismo come stabile (cambia la strategia di migrazione).[9]
• Module compile cache: code cache su disco per accelerare la compilazione del grafo moduli, con modalità
portable.[10]• SEA: single executable applications. In 25.5.0 diventa molto più semplice con
--build-sea.[12][13]• Ops QoL: proxy da env (
http.setGlobalProxyFromEnv()), ignore nei watcher (fs.watch({ ignore: ... })).[14][15]
Node 25.0.0: cosa cambia “alla base” e dove sta la utilità pratica
25.0.0 è la base: V8 14.1, performance, web API e pulizia legacy. La vera “maturità” si vede in 25.4/25.5.[8][9][12]
Web Storage
Cambio di default che ha causato regressioni nell’ecosistema (tool/test/CLI).[8][5][4]
Security posture
Permissions come parte “secure-by-default”, con limiti chiari (non sandbox).[11]
Legacy cleanup
I major release spesso fanno male per rimozioni di API deprecated (SlowBuffer).[16]
Segnale visivo dell’inizio linea: Node.js 25.0.0 (Current).[8]
Section node-25-0 screenshotWeb Storage di default: perché ha “esploso” e come appare nei progetti reali
Il problema non è che Web Storage sia “cattivo”. Il problema è che è cambiato il default, e parte del tooling ha iniziato a eseguire rami non testati su server.
nodejs/node #60704 descrive la regressione 25.2.0: “Cannot initialize local storage without a --localstorage-file path”, e collega toolchain rotti (webpack/jest/html-webpack-plugin).[5]
Vitest #8757 mostra un altro failure mode: in Node 25 localStorage non è più undefined e questo può rompere i mock.[4] Jest #15888 aggiunge un altro caso: test runner che fallisce con SecurityError su Node 25.2.0.[3]
Workaround rapido (se non ti serve): disabilitare temporaneamente Web Storage con il flag documentato nelle globals docs.[7]
Lezione per i team: anche se “non usi localStorage”, le dipendenze potrebbero farlo. Tenere Current in CI è un modo sano per intercettare sorprese.[6]
nodejs/node #60704: regressione 25.2.0 — localStorage senza --localstorage-file può far fallire la build.[5]
Vitest #8757: Web Storage abilitato di default può rompere i test per via delle nuove aspettative su localStorage.[4]
Jest #15888: fallimento con SecurityError su Node 25.2.0.[3]
Node 25.4.0: require(esm) diventa stabile (e cambia la migrazione)
Una di quelle modifiche che non fa rumore, ma ti salva settimane in repo dove CJS ed ESM devono convivere.
1) Perché conta
2) Pattern pratico: adattatore per default export
const pkg = require('some-esm-only-package');
const api = pkg?.default ?? pkg;
module.exports = api;È un modo standard per gestire il namespace object quando ESM espone un default export.[22]
3) Cosa significa per i team
Stabilizzare in Current prepara la strada all’ecosistema. LTS+Current in CI ti fa prendere valore senza rischio prod.[6]
Marker visivo della fase “stabilizzazione”: Node.js 25.4.0 (Current).[9]
Section require-esm screenshotModule compile cache: modalità portable e documentazione più chiara — cosa dà in CI/container
La compile cache è un’ottimizzazione. È utile dove hai molti cold start o un grafo moduli grande (tool, CLI, worker).
Le docs node:module hanno una sezione concreta sulla portabilità: se cambiano gli absolute path, l’efficacia può scendere; portable aiuta a riusare la cache in più scenari.[10]
Esempio minimo:
import module from 'node:module';
module.enableCompileCache({ directory: '.node-compile-cache', portable: true });In CI, gestisci esplicitamente la directory cache per non perderla tra i run.
Docs: sezione sulla portabilità + modalità portable con esempi API/env.[10]
Node 25.5.0: `node --build-sea` rende SEA un workflow “in un solo step”
Una delle modifiche più “product”: costruire un single executable non sembra più un rituale con injector esterno.
1) Idea base
2) Takeaway pratico
Per CLI/agenti: meno dipendenze e meno step fragili. Ma testa su OS/shell target (ci sono segnalazioni su Windows Command Prompt).[21]
3) Consiglio team
SEA è per la distribuzione, non sostituisce un modello di sicurezza. Continua a curare dipendenze e supply chain. Tieni Current in CI per intercettare sorprese.[6]
25.5.0: sezione --build-sea con esempio comandi nelle release notes ufficiali.[12]
Ops QoL: proxy da env e un percorso pulito per reti enterprise
Non è una headline feature, ma riduce “micro wrapper” custom nei progetti con CI aziendale dietro proxy.
Snippet minimo:
import http from 'node:http';
const restore = http.setGlobalProxyFromEnv();
// restore();API
Utile quando il proxy è configurato via env (CI/reti enterprise).[14]
Tip
Evita chiamate nel mezzo delle request; fallo nel bootstrap di service/CLI.[14]
Risultato
Meno probabilità che ogni client HTTP venga configurato in modo diverso.
Docs: http.setGlobalProxyFromEnv([proxyEnv]) + sezione built-in proxy support.[14]
Dolore monorepo: `fs.watch({ ignore })` per ridurre rumore e carico
In repo grandi, i watcher diventano un problema (node_modules, .git, output generati). Le docs descrivono ignore come opzione che accetta glob/RegExp/function/array.[15]
Esempio minimo:
import { watch } from 'node:fs';
watch('.', {
recursive: true,
ignore: ['**/node_modules/**', '**/.git/**'],
}, () => {});Breaking changes: Node 25 rompe dipendenze con “legacy cleanup” (non con nuove feature)
I major release spesso fanno male per finalizzazione deprecations. SlowBuffer è l’esempio perfetto.[16]
1) Cosa è successo a SlowBuffer
Le deprecations docs indicano che SlowBuffer è stato rimosso e suggeriscono Buffer.allocUnsafeSlow(size).[16]
2) Come appare in produzione
C’è un caso reale: backend che crasha su Node 25.x perché una dependency chain tocca SlowBuffer.prototype.[17]
3) Quick scan prima dell’upgrade
rg -n "\bSlowBuffer\b" .Poi pnpm why / npm ls per trovare la catena legacy.
Upgrade playbook: come adottare Node 25 per valore (non per dolore)
Una checklist breve e realistica per i team.
• 1) Prod su LTS, Node 25 in CI (matrice LTS + Current).[6]
• 2) Esegui i test con Web Storage disabilitato per isolare velocemente:
NODE_OPTIONS="--no-experimental-webstorage".[7]• 3) Se si è rotto, guarda dove si è rotto già: nodejs/node #60704, Vitest #8757, Jest #15888.[5][4][3]
• 4) Se ti interessano le feature 25.4/25.5, provale prima in un esperimento separato (CLI/worker), non in prod “in un colpo”.[9][12]
• 5) Per monorepo, aggiusta i watcher:
fs.watch({ ignore })riduce rumore e load.[15]• 6) Per CI dietro proxy, prova
http.setGlobalProxyFromEnv()come soluzione globale semplice.[14]
Verdetto: conviene aggiornare a Node.js 25 adesso?
Niente ideologia: Node 25 è ottimo come “detector” in CI, ed è utile per alcuni prodotti (CLI/agenti). Per i backend in produzione, LTS spesso vince per stabilità.
Backend in produzione
Node raccomanda Active/Maintenance LTS per production.[6]
CI / compatibilità
Node 25 fa emergere regressioni (Web Storage) e cleanup legacy (SlowBuffer) senza rischio incidenti in prod.[5][16]
CLI / agenti
--build-sea (25.5.0) + maturità 25.4.0 sono un vero boost DX. Testa su OS/shell target.[12][21]
Mentalità
Current esiste per far adattare l’ecosistema. Se rompe la giornata, tienilo in CI e lascia prod su LTS.[6]
Sources
Fonti in ordine di apparizione (posizione in lista = numero [n]).
• Docusaurus issue #11545: build broken on Node 25.2.0 due to localStorage SecurityError Leggi fonte ↗
• Shopify community: shopify-cli fails on Node 25.2.0 due to localStorage SecurityError Leggi fonte ↗
• Jest issue #15888: Jest fails with localStorage error on Node 25.2.0 Leggi fonte ↗
• Vitest issue #8757: Node v25 breaks tests with Web Storage API Leggi fonte ↗
• nodejs/node issue #60704: Regression 25.2.0 — Cannot initialize local storage without --localstorage-file path Leggi fonte ↗
• Node.js Releases: Current vs LTS policy (production guidance) Leggi fonte ↗
• Node.js Globals docs: Web Storage semantics + disable flag Leggi fonte ↗
• Node.js 25.0.0 release notes (V8 14.1, Web Storage default, performance highlights) Leggi fonte ↗
• Node.js 25.4.0 release notes (require(esm) stable, compile cache stable, ops improvements) Leggi fonte ↗
• Node.js node:module docs: module compile cache + portability Leggi fonte ↗
• Node.js Permissions docs: seat belt approach (not a sandbox) Leggi fonte ↗
• Node.js 25.5.0 release notes (--build-sea, other notable changes) Leggi fonte ↗
• Node.js Single Executable Applications docs (--build-sea flow and config) Leggi fonte ↗
• Node.js HTTP docs: http.setGlobalProxyFromEnv() Leggi fonte ↗
• Node.js Deprecations docs: SlowBuffer removed + migration note Leggi fonte ↗
• twentyhq/twenty issue: crash on Node 25 due to SlowBuffer removal via dependency chain Leggi fonte ↗
• nodejs/Release issue #1113: cadence discussion (annual majors / LTS duration) Leggi fonte ↗
• nodejs/node issue: --build-sea fails in Windows Command Prompt (shell-specific behavior) Leggi fonte ↗
• Joyee Cheung: require(esm) in Node.js — from experiment to stability Leggi fonte ↗
• Joyee Cheung: Improving SEA building (why build moved into Node.js core) Leggi fonte ↗
FAQ
No. È Current (odd major). Node dice chiaramente che in production dovresti usare Active o Maintenance LTS.[6]
Perché è cambiato il default. `localStorage` è diventato disponibile senza flag sperimentali, ma su Node la semantica è diversa (file, 10MB, shared). Il tooling ha rotto assunzioni.[7][5][4]
Disabilita temporaneamente Web Storage con `NODE_OPTIONS="--no-experimental-webstorage"` o tramite flag runtime. È documentato nelle globals e citato come workaround.[7][5]
Sì, ma testa su OS/shell target. C’è un issue specifico su Windows Command Prompt.[21] Per dettagli vedi le SEA docs.[13]
In una frase: prendi il valore di Node 25 senza portarti il rischio in prod
Strategia più sana: prod su LTS, Node 25 in CI come detector di regressioni, e adozione graduale delle feature 25.4/25.5 (require(esm), compile cache, build-sea) in esperimenti isolati (CLI/worker). DX su, rischio giù.