Node.js 26 is the current release line, and it enters LTS in October 2026. Node.js 22 reaches end of life on 30 April 2027, per the Node.js schedule. Moving now gives you months of runway. It also ships the October 21 Brotli security fix: 26.12.1 is the patched release for that line.
The breaks below come from Node's release notes and from our tests. We did not run a full production app, so your test suite is the final check. The breaks are specific, and most fail fast, so you find them in staging. This guide covers the four that matter for typical SaaS code: the removed writeHeader, the removed _stream_* modules, native addons that must be rebuilt, and Temporal turning on by default. We tested each one on Node.js 26.12.1 and 22.22.2.
What you need: shell access, your repository, a staging environment, and your test suite. Node.js 26 requires GCC 13.2 or later to build from source and drops Python 3.9 for builds. If you install with a prebuilt binary or Docker image, you can skip both.
Step 1: Check Your Current Version and Your Dependencies
node --version
npm ls --all 2>/dev/null | grep -iE "bcrypt|sharp|canvas|sqlite3|better-sqlite3|grpc|node-gyp" || trueAny package in that output may include a native addon. Step 4 covers those.
Step 2: Find writeHeader and Replace It With writeHead
http.ServerResponse.prototype.writeHeader() is removed in Node.js 26. The Node.js 26.0.0 release notes say to use writeHead().
grep -rnE "\.writeHeader\(" src/ app/ server/ lib/ --include=*.js --include=*.mjs --include=*.ts 2>/dev/nullReplace each call:
// Before (works on 22, throws on 26)
res.writeHeader(200, { 'content-type': 'application/json' })
// After
res.writeHead(200, { 'content-type': 'application/json' })We reproduced the break with a real HTTP server. On Node 22.22.2, res.writeHeader(200, …) returned ok-writeHeader. On Node 26.12.1, the same call failed with res.writeHeader is not a function. The writeHead version returned ok v26.12.1.
A dependency may call writeHeader internally. If your error comes from node_modules, upgrade that package before you change anything in your code. We did not test a specific package.
Step 3: Replace the _stream_* Modules
The _stream_readable, _stream_writable, _stream_duplex, _stream_transform, _stream_passthrough, and _stream_wrap modules are removed. Import from node:stream instead:
grep -rnE "require\(['\"](_stream_|stream/)|from ['\"]_stream_" src/ app/ lib/ --include=*.js --include=*.mjs --include=*.ts 2>/dev/null// Before (22 only)
const { Readable } = require('_stream_readable')
// After (22 and 26)
const { Readable } = require('node:stream')On Node 22.22.2, require('_stream_readable') loads. In the same test, a require('_stream_wrap') call printed a DEP0125 deprecation warning. On Node 26.12.1, require('_stream_readable') throws MODULE_NOT_FOUND. Replacing the import with node:stream runs cleanly on both.
Step 4: Rebuild Native Addons
Node.js 26 changes the native module version from 127 (Node 22) to 147. This affects addons that use the V8 API directly, which includes addons built with the older NAN helper. Addons that use Node-API (N-API) are version-stable. The current releases of bcrypt (6.0.0), sqlite3 (6.0.1), canvas (3.2.3), and better-sqlite3 (13.0.3) all depend on node-addon-api, which is the N-API wrapper. Check each package's release notes for which path it uses.
We tested two kinds of addon:
- An addon that uses Node-API (N-API): It loaded on both Node 22.23.4 and Node 26.12.1. Node-API is version-stable, so it does not need a rebuild.
- An addon that uses the V8 API directly: It loaded on Node 26.12.1 and failed on Node 22.23.4 with this message:
The module '.../v8addon.node' was compiled against a different Node.js version using
NODE_MODULE_VERSION 147. This version of Node.js requires NODE_MODULE_VERSION 127.The reverse applies: an addon built for 22 will fail on 26 with the same kind of error.
Rebuild everything that is not N-API:
npm ci # reinstalls from your lockfile on the new Node version
npm rebuild # recompiles native addons against this Node versionDo not delete your lockfile to fix an addon error. Upgrade the package that causes the error, then commit the new lockfile.
Check the addon names from Step 1. If a package has no 26-compatible release, upgrade it first. Check its release notes for "Node 26" or "N-API" before you upgrade.
We did not run npm rebuild on a full production dependency tree. The steps above are the standard path, and the test addons confirm the version rules.
Step 5: Plan for Temporal Being On by Default
The Temporal API is enabled by default in Node.js 26. It adds a global Temporal object. Your existing Date code still works. We checked new Date('2026-10-28T00:00:00Z').toISOString() on Node 26.12.1 and it returned the expected value.
What changes:
- Libraries that check for the
Temporalglobal could change code paths. We did not test any specific library. Run your test suite and look for date output changes. - Polyfills: A
Temporalpolyfill package could conflict with the built-in global. The packagestemporal-polyfill(1.0.6) and@js-temporal/polyfill(0.5.1) exist on npm. We did not test their behavior on 26. Check your dependency list for them. - You don't need to use Temporal. Keep using
Dateif your code works. Temporal is optional. Temporal is not a required change. It is a behavior change to test.
Step 6: Check the Other Removed Flags
Node.js 26 removes --experimental-transform-types. If your start script or Docker CMD uses it, remove the flag:
grep -rnE "experimental-transform-types" package.json Dockerfile* docker-compose*.yml ecosystem.config.* 2>/dev/nullOn Node 26.12.1, node --experimental-transform-types fails with bad option.
Node.js 26 also marks module.register() as deprecated. It still works, but it prints DEP0205. Plan a move to module.registerHooks() when you can. We confirmed registerHooks exists on Node 26.12.1.
Step 7: Upgrade in Staging, Then Production
Update the runtime in your Dockerfile or deployment config:
FROM node:26.12.1-bookworm-slim
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
CMD ["node", "server.js"]We could not confirm the node:26.12.1-bookworm-slim tag on Docker Hub during this check, so verify it before you build. Check the version inside the running container with node --version. Do not rely on the tag alone.
Run your full test suite on 26 in staging. Then deploy with a rollback plan: keep the Node 22 image tag, and be ready to redeploy it.
Common Mistakes
- Replacing
writeHeaderin your code but not in a dependency. Find the package that calls it, then upgrade it. - Rebuilding addons on 22 instead of 26. The rebuild must run on the target version.
- Assuming Temporal changes your
Dateresults.Datestill works. Test any library that checks for the global. - Keeping
--experimental-transform-typesin the start command. It fails on 26. - Running the check on the wrong host. Check
node --versioninside the container that serves traffic.
What We Tested
| Check | Result |
|---|---|
| Node 26.12.1 reports version | Confirmed |
res.writeHeader on a real HTTP server |
Works on 22.22.2, throws on 26.12.1 |
res.writeHead on a real HTTP server |
Works on 26.12.1 |
require('_stream_readable') |
Loads on 22.22.2, MODULE_NOT_FOUND on 26.12.1 |
require('node:stream') with Transform and Readable |
Works on 26.12.1 |
zlib.createBrotliDecompress() |
Works on 26.12.1 |
Temporal global and Temporal.PlainDate |
Present on 26.12.1, absent on 22.22.2 |
new Date output |
Unchanged on 26.12.1 |
--experimental-transform-types |
bad option on 26.12.1 |
module.register() |
Works on 26.12.1, prints DEP0205 |
| N-API addon | Loads on 22.23.4 and 26.12.1 |
| V8-API addon compiled for 26 | Loads on 26.12.1, fails on 22.23.4 with NODE_MODULE_VERSION 147 vs 127 |
Full npm rebuild on a production tree |
Not run |
| Temporal polyfill conflicts | Not tested (package names verified on npm) |
module.registerHooks() |
Exists on 26.12.1 |
require('node:stream') in a fixed app file, require('_stream_readable') in the old one |
Fixed file loads on 26; old file fails with MODULE_NOT_FOUND |
--experimental-transform-types on 26.12.1 |
bad option |
| Grep commands from Steps 2, 3, 6 | Run against sample files; they match the intended lines |
npm rebuild on a production tree |
Not run |
Docker tag node:26.12.1-bookworm-slim |
Not verified (Docker Hub not reachable) |
| Node 22 end of life (30 Apr 2027) | From the Node.js schedule read in an earlier check. Not re-fetched in this check. Azure's Node 22 retirement date (30 Apr 2027) matches. |
Sources and Further Reading
- The Node.js 26.0.0 release notes cover Temporal, removed APIs, and the Node 26 LTS timing.
- The Node.js release schedule announcement confirms Node 26 enters LTS in October 2026 and is the last release under the current model.
- The Node.js October 2026 security releases list 26.12.1 as the patched release for the Brotli RCE (CVE-2026-111497).
Test environment: Node.js 26.12.1 and 22.22.2 (npm
node-linux-x64builds), g++ for the test addons.
Got stuck, or want this shipped end-to-end for you? bitroot.club builds custom products for founders. →