1794139327

Upgrading Node Without Breaking Your Project: What Fails First


Around 95 million. That's how many times Node 20 was still downloaded in April 2026, according to tech press roundups, in the very month the version died. End of life was April 30. Since then, anyone running Node 20 in production has stopped receiving security fixes from the project, even when coordinated batches like July's ship, which covered the 26, 24 and 22 lines and left 20 out. This isn't an alarm, it's a calendar. And the calendar has only a few dates that matter. Node 22 is in maintenance, critical fixes only, until April 30, 2027. Node 24 moved to maintenance on October 20 and lives until April 30, 2028. Node 26, released in spring, enters LTS on October 28, 2026 and runs until April 2029. In other words, the 26 line became "acceptable for production" eleven days ago. My position: for an existing project, the target is 24. Packages with native binaries tend to take weeks to catch up with a new line, and it's not worth being their guinea pig. From here on I assume 24 as the destination. #### First, find out where the project really runs There are three places with a Node installed: your machine, CI and the server. In plenty of projects it isn't the same version in all three. Run `node -v` in each and write the results down. If there's an `.nvmrc` or an `engines` field in `package.json`, check whether anyone actually respects them. Then run the test suite on the current version and keep the result. Without that starting point, you can't tell whether what fails next is the migration's fault or was already failing. #### Coming from 20 or 22, it probably just works Jumping between two consecutive LTS lines rarely breaks application code. It breaks what surrounds it. `node --trace-deprecation` shows where warnings about disappearing APIs come from, and `--throw-deprecation` turns them into errors, which is useful in a dedicated test run. One detail that helps: from 22.12 on, `require()` of ES modules works without a flag, which frees some libraries that went ESM-only and were tying you to the old version. #### Coming from 18 or earlier, don't jump Imagine an API with 214 packages in the lockfile, parked on Node 16.20 since 2022. Go straight to 24 and you bundle four years of changes into a single red error. And when it fails, there's no way to know which change is the culprit. Go through 20, then 22, then 24, with tests between each step. It feels like bureaucracy. It costs a day or two, and in exchange every step has a small set of suspects. Node 24 ships with npm 11, and it's common for the lockfile to be rewritten on install: commit that separately so you don't mix noise into the migration. #### ERR\_OSSL\_EVP\_UNSUPPORTED If your jump crosses the Node 17 boundary and you still use webpack 4 or an old build toolchain, this is the error waiting for you. Node moved to OpenSSL 3, and algorithms like md4, which webpack 4 used to generate hashes, stopped being available by default. There's the band-aid, `NODE_OPTIONS=--openssl-legacy-provider`, and it will show up in every tutorial. It saves an afternoon. The real fix is upgrading to webpack 5, or to whatever tool wraps it. Leaving the flag in the production pipeline is debt that gets forgotten and that nobody can later explain. #### Native modules, where it really breaks Every Node major version changes the ABI for native addons. The symptom is a long message saying the module was compiled against a different version, with two numbers inside: NODE\_MODULE\_VERSION 115 and 127, for instance, which is exactly the jump from Node 20 to 22. Most of the time the cause is simply that you changed Node without rebuilding dependencies. Delete `node_modules` and run `npm ci`. The ugly case is another one. Packages like sharp, bcrypt, better-sqlite3 or canvas download precompiled binaries. If the version you've pinned is too old, there's no binary for the new Node, and npm falls back to node-gyp, which needs Python and a C++ compiler. On your machine you might have them. On a slim Docker image, you don't. This is where the half-hour migration swallows the whole afternoon. And the answer is almost never to fight the compiler: it's to bump the package to a version that already publishes binaries for the target Node. Check that beforehand, package by package (`npm ls sharp`, each release's notes), not after the deploy has failed. #### The server, Docker and CI In the Dockerfile, pin the major version: `FROM node:24-bookworm-slim`. Avoid the `node:lts` tag. With the 26 line entering LTS at the end of October, anyone using it should have silently ended up pointing at a different version without deciding anything. Keep an `.nvmrc` or `.node-version` in the repository and set `engine-strict=true` in `.npmrc`, so npm refuses to install on the wrong version. With fnm or nvm you have both versions side by side, and rollback becomes one command instead of a long night. In CI, for a week, run tests on both versions at once (a matrix). If the new one passes and the old one keeps passing, switch production. Only then. A pattern that tends to show up in projects that fall behind: the `.nvmrc` said 16, CI ignored the file, the server ran something else. The file had been there since 2022. Nobody read it.

(0) Comments

Welcome to Chat-to.dev, a space for both novice and experienced programmers to chat about programming and share code in their posts.

About | Privacy | Donate
[2026 © Chat-to.dev]