22 de agosto de 2026

Node 26: qué se rompe al actualizar y cuándo te conviene el salto (guía LTS)

Foto de Marco Orta Marco Orta | 12 min de lectura
Compartir
Ilustración de un hexágono de Node.js atravesando una puerta de versión con engranajes antiguos cayendo a un lado
Tabla de Contenidos

    Node 26 salió el 5 de mayo de 2026 y entra en LTS en octubre. Es un salto tranquilo — la lista de cosas que se rompen es corta y casi toda avisada desde hace años — pero tiene una particularidad que ninguna versión anterior tuvo: es la última del modelo de dos lanzamientos al año. A partir de Node 27, el proyecto pasa a un lanzamiento anual y todas las versiones son LTS. La distinción par/impar, que llevaba una década decidiendo qué versión podías usar en producción, desaparece.

    Eso convierte a esta actualización en algo más que un bump de versión: es el último tren del calendario viejo, y conviene tomarlo sabiendo qué hay dentro. Aquí va lo que se rompe de verdad, ordenado por impacto, y al final el calendario completo para decidir cuándo moverte.

    Primero: dónde estás

    El esfuerzo del salto depende de tu punto de partida:

    • Desde Node 24 (el LTS actual): el escenario cómodo. Los cambios incompatibles son pocos y muy localizados; para la mayoría de proyectos la migración es trabajo de checklist, no de refactor.
    • Desde Node 22: también razonable, pero con más frentes — te comes de golpe lo que cambió en 24 (V8, permisos, glibc mínima) más lo de 26. Node 22 sale de mantenimiento en abril de 2027, así que tienes fecha límite real.
    • Desde Node 25: las versiones impares nunca fueron para producción y esta fue la última que existirá. Salta a 26 sin pensarlo.

    El calendario que cambia: Node 27 estrena modelo

    El anuncio oficial del proyecto lo resume así:

    AspectoHasta Node 26Desde Node 27
    Lanzamientos mayores al año21
    Versiones LTSSolo las paresTodas
    Versiones impares “efímeras”Desaparecen
    Canal alphaNo existía6 meses, admite cambios semver-major
    Soporte total por versión36 meses36 meses (igual)

    Las fechas concretas que te afectan:

    • Node 26: LTS en octubre de 2026, fin de vida en abril de 2029.
    • Node 27: canal alpha desde octubre de 2026, lanzamiento en abril de 2027, y también será LTS.
    • Node 24: sigue en LTS; fin de vida en abril de 2028.

    La consecuencia práctica: ya no habrá que memorizar qué versión “cuenta”. Cada release de ahora en adelante es utilizable en producción, y el ritmo de una al año reduce el número de migraciones que tu equipo hace por década. Si mantienes librerías, la novedad que te toca es el canal alpha: seis meses para probar los cambios incompatibles antes del lanzamiento.

    Impacto alto

    1. Los módulos internos _stream_* ya no existen

    La eliminación con más metralla, porque no rompe tu código: rompe el de dependencias viejas. Los módulos internos _stream_readable, _stream_writable, _stream_duplex, _stream_transform, _stream_passthrough y _stream_wrap — deprecados desde Node 12 — se eliminaron del todo. Cualquier require('_stream_readable') lanza MODULE_NOT_FOUND.

    Ningún paquete mantenido en los últimos años hace eso, pero los ecosistemas arrastran fósiles. Para saber si tienes uno:

    grep -rn "_stream_" node_modules --include="*.js" -l | head
    

    Si aparece algo, las salidas son dos: actualizar ese paquete a una versión que use la API pública (node:stream), o —si está abandonado— sustituirlo. El polyfill readable-stream cubre la mayoría de los casos donde el paquete es tuyo y solo necesitas una migración mecánica.

    2. Undici 8: el fetch global se pone estricto

    Node 26 embarca Undici 8.0, la implementación detrás del fetch global. Trae validación de cabeceras más estricta y cambios en el manejo de redirecciones. Si tu código construye peticiones con cabeceras generadas dinámicamente —de una base de datos, de input de usuario, de otra API— lo que antes pasaba en silencio puede lanzar TypeError ahora.

    El patrón que más aparece en la práctica:

    // Si algún valor lleva \n o \r (p. ej. un token copiado con salto de línea),
    // Undici 8 lo rechaza en vez de sanearlo en silencio.
    const res = await fetch(url, {
      headers: { Authorization: `Bearer ${token}` },
    });
    

    La corrección correcta no es capturar el error: es sanear el valor en el borde, donde entra al sistema (token.trim() al leerlo). Que la petición reviente antes de salir es el comportamiento bueno — con Undici 7 ese token con \n viajaba y fallaba en el servidor de enfrente, que es mucho más difícil de depurar.

    3. Addons nativos: recompilación obligatoria

    NODE_MODULE_VERSION sube a 147, así que todo addon compilado (bcrypt, sharp, better-sqlite3, canvas…) necesita recompilarse o traer prebuilds para la nueva ABI. El error es inconfundible:

    Error: The module was compiled against a different Node.js version using NODE_MODULE_VERSION 137.
    

    npm rebuild lo resuelve en local; en CI y Docker basta con regenerar la imagen sobre la base nueva. El detalle que sí puede frenarte: los requisitos de compilación subieron — GCC 13.2 como mínimo y Python 3.9 dejó de estar soportado en el toolchain de build. Si compilas addons en una imagen vieja de Debian/Alpine, actualiza la imagen antes de pelearte con el error equivocado.

    Impacto medio

    4. module.register() queda deprecado en runtime

    La API asíncrona de hooks de módulos — la que usan loaders de instrumentación, transpilación al vuelo y APM — emite ahora una deprecación en runtime. El reemplazo señalado es la API síncrona module.registerHooks().

    Esto te afecta sobre todo vía terceros: agentes de observabilidad (Datadog, New Relic, OpenTelemetry) y herramientas tipo tsx viven en esa capa. La acción no es reescribir nada tuyo: es actualizar esas dependencias a la versión que ya migró antes de subir producción a 26.

    5. writeHeader() eliminado del servidor HTTP

    http.Server.prototype.writeHeader() era un alias sin documentar de writeHead(). Se eliminó. Si aparece en tu código, el cambio es literal, letra por letra:

    // Antes
    res.writeHeader(200, { 'Content-Type': 'application/json' });
    // Ahora
    res.writeHead(200, { 'Content-Type': 'application/json' });
    

    Un grep -rn "writeHeader" src/ te dice en segundos si te toca. Frameworks (Express, Fastify, Nest) usan la API documentada desde siempre; esto muerde código HTTP escrito a mano hace muchos años.

    6. Paquetes type: "module": adiós a la excepción CJS sin extensión

    En paquetes declarados como ESM, cargar un archivo CommonJS sin extensión dependía de una excepción histórica del resolvedor que Node 26 elimina. Si en un paquete "type": "module" tenías algún require de una ruta sin .cjs/.js explícito, ahora falla. La corrección es poner la extensión — que es lo que la resolución de ESM siempre pidió.

    Impacto bajo

    • localStorage sin archivo asociado devuelve undefined en vez de lanzar. Si probabas la disponibilidad con try/catch alrededor de localStorage, invierte la comprobación: ahora es un simple if.
    • QuotaExceededError pasa a ser una interfaz derivada de DOMException. Solo afecta a código que comparaba el error por constructor o por code numérico.
    • Varias APIs de crypto avanzan en el ciclo de deprecación — una llega a fin de vida y otras pasan a avisar en runtime. Si no ves warnings con --pending-deprecation hoy, no te toca.
    • assert acepta mensajes con formato printf en los errores de aserción. Es una adición, pero cambia la salida de texto: si tu CI parsea mensajes de assert (mala idea, ya que estás ahí), revísalo.

    Lo que NO se rompe, aunque lo hayas leído

    • La API Temporal no rompe Date. Temporal llega activada por defecto en Node 26, pero es una adición: todo tu código con Date sigue funcionando igual (de mal). Migrar es opcional y gradual — tengo una guía completa de Temporal en Node 26 con la tabla de conversión desde date-fns y dayjs.
    • Ejecutar TypeScript directamente ya es estable. node app.ts funciona sin flags: el type stripping dejó de ser experimental. Lo único que “rompe” es el flag --experimental-transform-types, que fue eliminado — si lo tenías en un script de npm, quítalo y ya.
    • V8 14.6 solo suma. Map.prototype.getOrInsert(), Iterator.concat() y el resto de features de Chromium 146 son adiciones sin coste de migración.

    El upgrade, en la práctica

    El orden que menos sustos da:

    1. Instala 26 junto a tu versión actual, no encima. Con fnm o Volta es un comando y puedes volver atrás en segundos.
    2. npm rebuild (o borra node_modules y reinstala) para regenerar los addons nativos contra la ABI 147.
    3. Corre la suite con las deprecaciones a la vista:
      node --pending-deprecation --trace-deprecation ./node_modules/.bin/vitest run
      
      Las de module.register() y crypto salen aquí, con stack trace al culpable.
    4. grep de los fósiles: _stream_, writeHeader(. Dos minutos que te ahorran una sorpresa en producción.
    5. Actualiza los agentes (APM, telemetría, loaders) a las versiones que declaren soporte de Node 26 — es la capa que más tarda.
    6. Revisa el campo engines de tu package.json y el de tus dependencias críticas: npm ls --depth=0 seguido de un vistazo a los avisos de EBADENGINE en la instalación.

    ¿Cuándo actualizar?

    Proyecto nuevo: empieza en 26 hoy. No hay razón para arrancar en 24 algo que vivirá años.

    Producción en 24: la ventana sensata es octubre de 2026, cuando 26 entre en LTS — con 24 soportado hasta abril de 2028, no hay prisa, pero tampoco motivo para esperar al final: cuanto más tarde, más lejos está el conocimiento fresco de esta lista.

    Producción en 22: muévete ya. Abril de 2027 parece lejos hasta que deja de parecerlo, y el salto 22 → 26 es más grande que el 24 → 26.

    Para seguir:

    Preguntas frecuentes

    Compartir

    Buscar

    Etiquetas

    IA Tutorial Laravel PHP JavaScript Desarrollo Web Buenas Prácticas Migración Laravel 13 SEO Claude Herramientas Seguridad OpenAI MCP