Node 26 is LTS: what breaks when you move off Node 24
published
TL;DR
Node.js 26.13.0 entered Long Term Support on October 28, 2026 under the codename Lithium, with updates promised through April 2029. Node 24 moved to Maintenance a week earlier and still has until April 2028, so nothing is on fire. But an upgrade from 24 is not a no-op: 26 deletes http.Server.prototype.writeHeader(), the six _stream_* internal modules and the --experimental-transform-types flag, turns module.register() and a handful of crypto patterns into runtime warnings, bumps NODE_MODULE_VERSION so native addons need a rebuild, and ships Linux binaries that require libatomic.
The problem
Every October an even-numbered Node release becomes LTS and the previous one slides into Maintenance. This year the dates from the release schedule look like this:
| Line | Codename | Active LTS | Maintenance | End of life |
|---|---|---|---|---|
| 22 | Jod | 2024-10-29 | 2025-10-21 | 2027-04-30 |
| 24 | Krypton | 2025-10-28 | 2026-10-20 | 2028-04-30 |
| 26 | Lithium | 2026-10-28 | 2027-10-20 | 2029-04-30 |
Maintenance means security and critical fixes only, so most teams on 24 will move to 26 sometime in the next year. The 26.13.0 release post is just a metadata flip: it changes process.release to report LTS and includes no other changes from 26.12.1. Everything that can break you landed back in 26.0.0 in May, which is where the list below comes from.
What is gone
These were deprecated for a while and are now removed outright. They throw or fail to resolve; there is no flag that brings them back.
| Removed in 26 | Error you get | Replacement |
|---|---|---|
http.Server.prototype.writeHeader() | TypeError: res.writeHeader is not a function | res.writeHead(status, headers) (#60635) |
_stream_readable, _stream_writable, _stream_duplex, _stream_transform, _stream_passthrough, _stream_wrap | ERR_UNKNOWN_BUILTIN_MODULE | the public node:stream exports (#60657) |
--experimental-transform-types | node: bad option | strip-only TypeScript, or tsx for enums and parameter properties (#61803) |
The stream one bites through dependencies more than through your own code. Old polyfill shims and a few stream utilities still do require('_stream_readable') because that worked for a decade. Nothing in 26 resolves it, so the failure surfaces at startup with a module name you never typed.
The TypeScript flag is the subtle one. Type stripping has been on by default since 22.18 and stable since 24.12, and 26 keeps that. What 26 removes is the transform mode that also rewrote enum, runtime namespace blocks, parameter properties and import aliases into JavaScript. Per the TypeScript docs, those constructs now fail with ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX on every Node 26, and the documented way to run them is a loader such as node --import=tsx file.ts. We covered the strip-only behaviour in Node runs TypeScript now, until you write an enum; the difference is that on 24 the flag was an escape hatch, and on 26 it is not.
Two more removals that only matter at build time: NODE_MODULE_VERSION moved to 147, so every native addon compiled against 24 needs a rebuild (or a fresh prebuilt binary), and the build itself now requires GCC 13.2 and drops Python 3.9.
What now warns at runtime
Runtime deprecations do not break anything today, but they print on first use and are the list of what the next major removes. From the deprecations page:
- DEP0205,
module.register()became a runtime deprecation in 26.0.0. The replacement ismodule.registerHooks(), which runs hooks synchronously on the main thread instead of in a separate worker. If you ship a custom loader, this is the one to migrate first. - DEP0182, short GCM auth tags, reached end-of-life. Decrypting with an authentication tag shorter than 16 bytes now requires
authTagLengthoncrypto.createDecipheriv(); without it the tag is rejected. - DEP0201: passing
options.typetoDuplex.toWeb()warns; usereadableType. - DEP0203 and DEP0204: passing a WebCrypto
CryptoKeyintonode:cryptofunctions, or a non-extractable one intoKeyObject.from(), warns and is slated to throw later.
What is on by default now
- Temporal no longer needs a flag.
Temporal.Now.plainDateISO()just works, and so does the clamping behaviour we wrote up in Temporal clamps February 30 to February 28. If you polyfilled it, check that the polyfill steps aside when the global exists. - V8 14.6 adds
Map.prototype.getOrInsert(),getOrInsertComputed(), theWeakMapequivalents andIterator.concat(). The eager-default trap ingetOrInsertis covered in Map.getOrInsert’s default is eager. - Undici 8 sits under
fetch(). localStoragereturnsundefinedunless Node is started with--localstorage-file, instead of throwing or returning a stub.
The Linux surprise
The 26.13.0 notes repeat a line that has been easy to miss: since Node 25, the official Linux binaries are linked against libatomic. Teams that jump from 24 to 26 skip the release that introduced it. On a minimal container image the symptom is the binary refusing to start with a missing shared library, so add the package to the base image (libatomic1 on Debian and Ubuntu, libatomic on Alpine and the RPM distros) before the version bump, not after the first failed deploy.
What to do
Before changing the version, find the hard failures with three greps across your code and your lockfile’s dependencies:
grep -rn "writeHeader(" src
grep -rnE "_stream_(readable|writable|duplex|transform|passthrough|wrap)" src node_modules --include='*.js' -l
grep -rn "experimental-transform-types" package.json .npmrc Dockerfile .github
Then:
- Rebuild or reinstall every native addon against 26 (
npm rebuildis enough when prebuilds exist for ABI 147). - Run the test suite once with
NODE_OPTIONS=--throw-deprecationso the new runtime deprecations fail loudly instead of scrolling past. - Add
libatomicto Linux images. - Update the
enginesfield.">=24"stays honest if you support both lines;">=26"once you rely on Temporal orgetOrInsertwithout a polyfill. - Keep the CI matrix at
24and26. Node 24 is in Maintenance, not dead, and dropping it the week 26 went LTS is premature for a library.
Caveats
- This is the last transition under the odd/even model. Node 27 opened its alpha window this week and will be LTS itself in October 2027; see Node 27 ends the odd/even release model for what that does to your matrix.
- The removals above are the ones the 26.0.0 release notes call out. Semver-major behaviour changes in the same release (readable streams reading one buffer at a time, assertion messages accepting printf-style formats, PKCS#8 export of ML-KEM and ML-DSA keys defaulting to seed-only) can change observable output without throwing, and only a test run finds those.
- Nothing here applies to 24.22.2, which shipped the same day as 26.13.0 and is a normal Maintenance release.