> ## Content Index
> Fetch the complete content index at: https://sudosecurity.org/llms.txt
> Use this file to discover other available public pages before exploring further.

# Node 26 Is LTS and Temporal Is Finally On by Default
- URL: https://sudosecurity.org/nodejs-26-lts-upgrade/
- Published: 2026-11-09T13:23:32.000Z
- Updated: 2026-11-09T13:23:32.000Z
- Description: 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.
- Author: Traven
- Tags: Web Development, Node.js, JavaScript, Developer Tools, TypeScript, Systems Administration

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:

```javascript
// 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](https://thehackernews.com/2026/10/eight-emudhra-ca-keys-compromised.html?ref=sudosecurity.org). 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](https://sudosecurity.org/database-eol-2026-migration-guide/) 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.