Web Development

Node 26 Is LTS and Temporal Is Finally On by Default

Node.js 26 hit Long Term Support on October 28 as Lithium, supported through April 2029. Temporal ships enabled with no flag, which is the biggest thing to happen to JavaScript dates since Date was a mistake.

Node 26 Is LTS and Temporal Is Finally On by Default
Photo by Rahul Mishra / Unsplash

Node 26 went Long Term Support on 28 October, as Lithium, in release 26.13.0. Supported through 30 April 2029.

If you only read one line of this: Temporal is on by default now, no flag. That's the biggest change to date handling in JavaScript since Date shipped as a rushed copy of java.util.Date in 1995 and we all spent thirty years working around it.

The rest is the usual LTS mix — some nice V8 additions, a pile of removals that'll bite you if you've been ignoring deprecation warnings, and a toolchain bump that'll bite your CI instead.

Temporal, finally

I'm not going to explain why Date is bad. If you've written JavaScript, you've been burned by it.

Months are zero-indexed. getYear() versus getFullYear(). Mutation in place. No timezone handling worth the name. Parsing behaviour that varied by engine for years, so the same string gave you different dates in different browsers.

I have debugged that last one. It took a day and a half and the answer was a trailing Z.

Temporal is the replacement, and as of Node 26 it's just there. No --harmony flag, no polyfill, no npm install.

Your existing Date code keeps working. Nothing was removed.

But here's the wrinkle. Libraries that feature-detect Temporal may change behaviour. A date library checking typeof Temporal !== 'undefined' and switching implementations will now take the other branch on your runtime. Silently. Nothing logs it.

That's not a bug in Node. It's just a thing that becomes true the day you upgrade.

So if you depend on a date library, read its changelog before you move. That's the single most likely source of a surprise here, and it'll surface as subtly wrong timestamps rather than a crash, which is the worst way for anything to be wrong.

V8 14.6

Node 26.0.0 moved to V8 14.6. The LTS line carries 14.6.202.34 with npm 11.20.0.

Three additions I'll actually use:

Map.prototype.getOrInsert() and getOrInsertComputed(), also on WeakMap. This kills the get-check-set dance. You've written it a hundred times:

// before
let bucket = groups.get(key);
if (bucket === undefined) {
  bucket = [];
  groups.set(key, bucket);
}
bucket.push(item);

// after
groups.getOrInsertComputed(key, () => []).push(item);

That's original demonstrative code, not lifted from the release notes — but getOrInsert and getOrInsertComputed are the real V8 14.6 method names, and getOrInsertComputed takes a callback for the default so you don't construct a value you won't use. Use the computed variant when the default is expensive; getOrInsert when it's a literal.

Iterator.concat() — chain iterators without spreading them into arrays first. If you've ever written [...a, ...b] purely to iterate once and felt bad about materialising both, this is for you.

fetch is backed by Undici 8.0.2, so if you've got a custom dispatcher or agent, retest it. Undici majors have historically moved things.

Later in the 26.x line there's an experimental node:ffi module, stream.compose marked stable, Buffer.stringLength() and http.websocketMask(). On FFI: it's an inherently unsafe API by design — you're calling into native code with no memory safety net — so treat it as a sharp tool rather than a convenience.

What's removed, and what'll bite you

This is the part to read before you bump the version in CI.

Removed / changed Do this instead
http.Server.prototype.writeHeader() writeHead()
Legacy _stream_* modules Import from node:stream
--experimental-transform-types Gone; use a real build step
module.register() Runtime-deprecated, still works
localStorage with no storage file Now returns undefined

The legacy _stream_* removal is the one I'd expect to hurt. Not because of your code, though.

Those underscore-prefixed modules have been informally deprecated for about a decade. So nobody in your codebase is importing them. But something four levels down your dependency tree, last published in 2016, might be — and it fails at require time, pointing at a package you've never heard of.

The localStorage change is small but sneaky. Returning undefined instead of throwing means a missing storage file now produces a TypeError somewhere further along, at the point you try to use the thing you got back, rather than at the point of the actual problem.

There's also a batch of deprecations moving to runtime warnings around crypto and streams. The source I was working from flagged its own DEP numbers as unverified, so I'm not going to list identifiers I can't stand behind — run your test suite with --trace-deprecation and let Node tell you which ones you're hitting. That's more reliable than any table I could write.

The toolchain bump will hit CI before it hits you

Building Node 26 from source now requires GCC 13.2 and has dropped Python 3.9.

Most people don't build Node from source, so this reads like it doesn't apply to you. It applies if you build native addons. It also applies if your CI image is an older LTS distro running the default compiler.

A container based on an older Debian or Ubuntu LTS is quite possibly below GCC 13.2. You won't see a message about GCC, either. You'll see a node-gyp failure buried in some dependency's install step, at which point you'll lose an hour.

Rebuild native addons with a clean npm ci rather than trusting an existing node_modules.

The support timeline, which is the actual reason to care

Version Status End of life
Node 26 (Lithium) LTS now — Maintenance 20 Oct 2027 30 Apr 2029
Node 24 Maintenance from 20 Oct 2026 30 Apr 2028
Node 22 Maintenance 30 Apr 2027
Node 20 End of life 30 Apr 2026 (passed)

Node 20 has been dead since April. If you're still on it you're running a runtime that gets no security fixes, which brings me to the thing that makes this upgrade less optional than it looks.

Two weeks ago, eight intermediate CA keys turned out to be recoverable from their own public certificates. Node shipped the fix in 26.12.1, 24.22.1 and 22.23.4 — three supported lines. Node 20 got nothing, because Node 20 gets nothing now.

That's what EOL means in practice. It doesn't mean you're missing features.

It means a CA compromise happens and your runtime ships a trust store that still believes in it. Permanently.

And a detail worth carrying over from that incident, since it applies to this upgrade too: after you update Node, restart the services. Resumed TLS sessions aren't re-verified, so a long-running process can keep trusting a chain it would now reject.

What this doesn't cover

I haven't run Node 26 in production and this isn't a report from doing so. It's a read of the release material with the parts flagged that I'd want flagged if somebody handed it to me.

I couldn't reach the official release notes directly from here — nodejs.org wasn't fetchable in this session — so the feature list is assembled from secondary coverage, and one of those sources explicitly marked some of its own details as unverified. Where that affected something specific, like the deprecation identifiers, I've left it out rather than guessing. Check the official changelog for anything you're going to act on.

There's no benchmark here either. V8 14.6 will be faster at some things; I haven't measured any of them and neither has anyone whose numbers I'd quote.

What I'd actually do

On Node 24 in production? Wait a bit. 26 has only just gone LTS, and 24 is supported to April 2028. There's no urgency and .0 LTS releases have a history of needing a .1. Plan it for the new year.

On Node 22? Start now, and read both the 24 and 26 changelogs rather than treating it as one hop — you're also taking npm 10 to 11, which is its own set of behaviour changes.

On Node 20? This isn't an upgrade, it's an incident waiting to be filed. You're unsupported and the eMudhra thing already demonstrated what that costs. The EOL migration framing I used for databases applies exactly: the risk isn't that it stops working, it's that it keeps working while quietly accumulating unpatched problems.

However you do it: run the suite under --trace-deprecation, grep for _stream_ and writeHeader across your tree including node_modules, and rebuild native addons clean.

And read your date library's changelog. I'd put decent money on Temporal detection being the thing that produces the one weird bug nobody can reproduce for a fortnight.

$ tail -f /forums

Discussion

Comments live on the community forums — sign in there to reply.