Align nuxt-app on Node 24 - #243
Conversation
Nothing about the runtime changes. Four declarations that disagreed with it
now agree.
The production build log for main already says:
Warning: Due to "engines" ... the Node.js Version defined in your Project
Settings ("20.x") will not apply, Node.js Version "24.x" will be used
So engines.node is authoritative on Vercel — over the project setting and
over .nvmrc, which Vercel never consults. Narrowing engines to
^24.11.0 || >=26.0.0 keeps it resolving 24.x, so the deployed runtime is
untouched.
Workflows now read nuxt-app/.nvmrc instead of naming a version, so CI and
local development cannot drift apart again: one file, not three.
The Directus extension stays on 22. It has its own lockfile and Jest setup
and is frozen under the licence block, so moving its runtime would be an
untested change to a tree nobody may upgrade.
.nvmrc's v22 was not arbitrary — #174 set it to fix ERR_REQUIRE_ESM on
Node 20. That was a floor, and 24 clears it by a wider margin, so moving
forward cannot reintroduce it.
Also removes nuxt-app/.npmrc. Its two options are pnpm-only no-ops under
npm, and npm 11 (which ships with Node 24) promotes them to deprecation
warnings on every command, including in Vercel's build log.
Verified on Node 24.19.0 locally after a clean npm ci, not by pushing and
reading CI: format clean, 91 tests, lint 0 errors/127 warnings, ratchet
steady at 263, build exit 0, lockfile byte-identical.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JkKMceYAzAYLrSyC42FWTf
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Pull request overview
Aligns the Nuxt 3 app’s declared Node version (local dev + CI + docs) with the Node 24 runtime already used in production (Vercel via engines.node), and removes a now-noisy npm config file.
Changes:
- Narrow
nuxt-app’sengines.nodeto Node 24.11+ (or 26+), and switch CI workflows to read the version fromnuxt-app/.nvmrc. - Update project documentation (
AGENTS.md, dependency upgrade plan) to reflect the Node 24 alignment and rationale. - Remove
nuxt-app/.npmrc(pnpm-only options) to avoid npm 11 warnings.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| nuxt-app/package.json | Narrows the declared Node engine range to Node 24.11+ (or 26+). |
| nuxt-app/.nvmrc | Updates the Nuxt app’s Node version pin used by CI and local tooling. |
| nuxt-app/.npmrc | Removes pnpm-only config entries that generate npm 11 warnings. |
| docs/dependency-upgrade-plan.md | Records the Node 24 alignment work and updates phase/backlog items. |
| AGENTS.md | Updates repo guidance to reflect Node 24 for nuxt-app and Node 22 for the Directus extension. |
| .github/workflows/smoke_tests.yml | Switches setup-node to node-version-file: nuxt-app/.nvmrc for smoke tests. |
| .github/workflows/run_tests.yml | Keeps Directus extension on Node 22; switches Nuxt job to node-version-file: nuxt-app/.nvmrc. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @@ -1 +1 @@ | |||
| v22 | |||
| 24 | |||
There was a problem hiding this comment.
You have found a real gap, and I am leaving it open — because the fix it implies is worse than the gap. Explaining rather than just declining.
The gap is real. .nvmrc: 24 permits 24.0.0–24.10.x, which engines: ^24.11.0 forbids. A PR whose whole claim is "every declaration agrees" should not contain two that disagree.
But .nvmrc supports no ranges. There is no value meaning "≥24.11 inside major 24". The options are a major (24), a minor series (24.11 → 24.11.x only), or an exact version. Pinning 24.11.0 would hold both CI and developers on a superseded patch with no Node security releases inside the major — CI resolved 24.18.0 today and would install 24.11.0 instead. That trades a narrow, unlikely problem for a permanent one.
What actually happens: both resolvers select the newest available 24.x, so .nvmrc: 24 satisfies engines in every normal case — verified in this PR's own CI run, which picked v24.18.0 from the file. The residual risk is a developer who already has an old 24.x installed: nvm use would select it, and npm then only warns (EBADENGINE).
The only way to close it properly is engine-strict=true, which promotes that warning to a hard install failure and makes the floor genuinely binding. I have not added it here: it changes install behaviour for other people — a wrong local Node would fail npm install rather than warn — and that is outside a PR whose claim is that install behaviour is unchanged. It is one line in a new nuxt-app/.npmrc if the maintainer wants it, and it would be an .npmrc containing an option that actually does something, unlike the one this PR deletes.
Both the gap and that option are now written down in docs/dependency-upgrade-plan.md rather than left implicit.
| "engines": { | ||
| "node": "^22.19.0 || ^24.11.0 || >=26.0.0" | ||
| "node": "^24.11.0 || >=26.0.0" | ||
| }, |
There was a problem hiding this comment.
Correct, and fixed — but syncing it turned out to be more interesting than a one-line change, so worth recording how.
npm install --package-lock-only under npm 11 (the npm that ships with Node 24, i.e. what this PR puts in CI) rewrites 168 lines: it strips "dev": true from roughly fifty optional packages (@emnapi/* and similar). The same command under npm 10 changes exactly one line — the engines field you identified.
npm 10 (Node 22): 1 file changed, 1 insertion(+), 1 deletion(-)
npm 11 (Node 24): 1 file changed, 13 insertions(+), 155 deletions(-)
So that churn is npm 11's changed dev/optional classification, not a consequence of narrowing engines. I generated the sync under npm 10 to keep this PR to the field in question, because the reclassification has semantic content — npm ci --omit=dev would install a different set afterwards — and this PR's claim is that nothing about the runtime or install behaviour changes. Bundling fifty unverified reclassifications in would have made that claim false.
Logged as its own backlog item, with the note that it is already live and harmless: Vercel builds on Node 24, so production has been resolving this lockfile with npm 11 for a while without trouble.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 676d5ae679
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| "private": true, | ||
| "engines": { | ||
| "node": "^22.19.0 || ^24.11.0 || >=26.0.0" | ||
| "node": "^24.11.0 || >=26.0.0" |
There was a problem hiding this comment.
Regenerate the lockfile's root engine metadata
The corresponding root package entry in nuxt-app/package-lock.json still contains ^22.19.0 || ^24.11.0 || >=26.0.0. As a result, tools reading the committed lockfile still see Node 22 as supported, and the next npm install rewrites the lockfile solely to synchronize this field. Regenerate and commit the lockfile alongside the package.json change so the repository consistently declares Node 24.
AGENTS.md reference: AGENTS.md:L21-L22
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Same finding as the Copilot comment above, and correct — synced.
One detail worth adding for anyone reading this thread later: your prediction that "the next npm install rewrites the lockfile solely to synchronize this field" holds under npm 10, but not under npm 11. Under npm 11 — the npm that ships with Node 24, which this PR puts in CI — the same command rewrites 168 lines, stripping "dev": true from ~50 optional packages.
npm 10 (Node 22): 1 insertion(+), 1 deletion(-)
npm 11 (Node 24): 13 insertions(+), 155 deletions(-)
So I generated the sync under npm 10 to keep this PR to the field in question. The reclassification is not cosmetic — npm ci --omit=dev would install a different set afterwards — and it is logged as its own backlog item with a note to verify a production-shaped install before and after, rather than trusting it.
| uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: 22 | ||
| node-version-file: nuxt-app/.nvmrc |
There was a problem hiding this comment.
Update the setup guide to require Node 24 for Nuxt
After this workflow begins sourcing Node 24 from .nvmrc, the root README.md still tells Nuxt contributors that Node 19+ is sufficient and that CI uses v22. A developer following those setup instructions can therefore work and validate on a runtime excluded by the new engines.node range and different from CI; distinguish the Nuxt requirement of Node 24 from the Directus tree's Node 22 requirement.
AGENTS.md reference: AGENTS.md:L21-L22
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Right, and fixed. README.md:13 said:
These install instructions assume you are using Node.js version 19+ (CI uses v22, tested up to v24).
Wrong on both counts after this PR, and wrong in the file a new contributor reads before any other. It also had a **Prerequisites for Node 22+:** heading below it.
Replaced with a table, because your framing is the important part — the two trees genuinely differ now and the README should say so rather than give one number:
| tree | Node | source of truth |
|---|---|---|
nuxt-app |
24 | nuxt-app/.nvmrc, which CI reads too. engines.node requires ^24.11.0 || >=26.0.0 |
directus-cms |
22 | its workflows; deliberately left behind while the tree is frozen |
Plus a line noting Node 24 is also what production runs, since Vercel resolves it from engines.node — which is the fact that made this whole alignment a documentation change rather than a runtime one.
AGENTS.md was already updated in the first commit; the README was the one I missed.
Three review findings, all correct — and all of them declarations that a PR claiming "every declaration agrees" had missed. package-lock.json still carried ^22.19.0 || ... in packages[""].engines, so the next npm install would have rewritten it as unexplained noise in someone else's PR. Synced. README.md still said "Node.js version 19+ (CI uses v22, tested up to v24)", wrong on both counts, in the file a new contributor reads first. Replaced with a table, since the two trees genuinely differ now. The lockfile sync was produced under npm 10 on purpose. The same command under npm 11 rewrites 168 lines, stripping "dev": true from ~50 optional packages — npm 11's changed dev/optional classification, not a consequence of narrowing engines. That has semantic content (npm ci --omit=dev would install a different set), so it is logged for its own change rather than smuggled in here. Not fixed: .nvmrc's `24` is looser than engines' ^24.11.0. The reviewer is right, and the suggested fix is worse — .nvmrc supports no ranges, so pinning 24.11.0 would hold CI and developers on a superseded patch (CI resolved 24.18.0 today) with no security releases inside the major. The only real fix is engine-strict=true, which changes install behaviour for other people, so it is logged for a decision instead. Verified on Node 24.19.0 after a clean npm ci: format clean, 91 tests, lint 0 errors/127 warnings, ratchet steady at 263, build exit 0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JkKMceYAzAYLrSyC42FWTf
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 9 changed files in this pull request and generated no new comments.
Files not reviewed (1)
- nuxt-app/package-lock.json: Generated file
Suppressed comments (3)
docs/dependency-upgrade-plan.md:2452
- Backslashes inside inline code are rendered literally, so this line shows
\|\|to readers. If you want to display the semver OR operator, use||directly inside the code span.
| 2026-08-04 | Narrowed `engines.node` to `^24.11.0 \|\| >=26.0.0` after confirming from the production build log that Vercel already resolves **24.x** and that `engines` beats both the project setting and `.nvmrc`. The deployed runtime is therefore unchanged by this PR — the declarations were what disagreed with reality. |
README.md:18
- In this table cell the Node engine range is inside an inline code span, so the backslashes are rendered literally (readers will see
\|\|instead of||). GitHub-flavored Markdown doesn’t require escaping|inside inline code, so this ends up less readable and harder to copy/paste.
| `nuxt-app` | **24** | `nuxt-app/.nvmrc`, which CI reads too. `engines.node` requires `^24.11.0 \|\| >=26.0.0` |
docs/dependency-upgrade-plan.md:1637
- These engine ranges are inside inline code spans in a Markdown table; the backslashes are rendered literally, so the document shows
\|\|instead of||. Since this file already uses||unescaped in inline code elsewhere (e.g.^20.19.0 || >=22.3.0), it’s clearer to drop the escapes here too.
This issue also appears on line 2452 of the same file.
| `nuxt-app/package.json` `engines.node` | `^22.19.0 \|\| ^24.11.0 \|\| >=26.0.0` | `^24.11.0 \|\| >=26.0.0` |
Nothing about the runtime changes. Four declarations that disagreed with it now agree.
Production has been on Node 24 all along
From the live build log for
main(3bde75d):engines.nodeis authoritative on Vercel — it beats the project setting and.nvmrc, which Vercel never consults. So every declaration said 22 or 20 while 24.x was actually running.engines.node^22.19.0 || ^24.11.0 || >=26.0.0^24.11.0 || >=26.0.0nuxt-app/.nvmrcv2224run_tests.yml(nuxt-app job)node-version: 22node-version-file: nuxt-app/.nvmrcsmoke_tests.ymlnode-version: 22node-version-file: nuxt-app/.nvmrcnodeVersion20.xNarrowing
engineskeeps Vercel resolving 24.x, which is what makes this safe: the deployed runtime is untouched.The workflows now read
.nvmrcrather than naming a version. Re-syncing three declarations by hand is what produced this drift; making CI read the file removes the opportunity. Same reasoning as the format gate, one layer down.The Directus extension stays on 22 (both
run_tests.ymlandalgolia-index-maintenance.yml). Separate lockfile, separate Jest setup, and frozen under the licence block — moving its runtime would be an untested change to a tree nobody is permitted to upgrade.The
v22pin was a floor, not a ceiling.nvmrcwas not missing — it came from #174, "Pin nuxt-app to Node 22 to fix Vercel runtime crash". So I read it before moving it, and the framing matters: that crash wasERR_REQUIRE_ESM, from a lockfile regen pullingjsdom@28 → html-encoding-sniffer@6 → @exodus/bytes(ESM-only), whose CJSrequire()needs Node 22.12+ or 20.19+. Vercel was on Node 20 and every SSR function died at startup.That raised a floor. Node 24 clears it by a wider margin than 22 did, so moving forward cannot reintroduce it — and the chain isn't even present now, since Phase 5 pinned
isomorphic-dompurifyback to~2.20.0with jsdom 26.A commit titled "pin to fix crash" reads like a ceiling. Moving it unread would have been reckless.
npm 11 comes with Node 24 and gates dependency install scripts
The project's own
postinstallstill runs —nuxt prepareexecutes and regenerates.nuxt/eslint.config.mjs, whichnpm run lintdepends on. Only dependency scripts are gated, and none is load-bearing: after a cleannpm ciwith all four blocked,esbuild --versionworks (the binary comes from the@esbuild/darwin-arm64optional dependency, notinstall.js) and every gate passes.Left blocked rather than allowlisted. Blocking is the safer default, an
allowScriptsentry is a standing grant to execute arbitrary install-time code, and nothing needs it. Expect those warnings in CI logs.Also removes
nuxt-app/.npmrcIts two options are pnpm-only no-ops under npm (already analysed in Appendix B), and npm 11 promotes them to
Unknown project config … will stop working in the next major version of npmon every command — including in Vercel's build log, visible in the excerpt above. This PR is what puts npm 11 in CI, so it cleans up after itself. Verified no pnpm lockfile or reference exists anywhere first.Verification — on Node 24, not on 22
Installed 24.19.0 locally and ran everything against it after a clean
npm ci, rather than pushing and letting CI find out. A Node major is exactly where a native dependency breaks, and a CI round-trip is a slower way to learn that.prettier:checkclean · 91 tests passing ·lint0 errors / 127 warnings (identical to Node 22) · typecheck ratchet steady at 263 ·SKIP_PRERENDER_ROUTE_DISCOVERY=true npm run buildexit 0 ·npm ciunder npm 11 leftpackage-lock.jsonbyte-identical.One manual step left
The Vercel project's
nodeVersionis still20.x, now overridden twice over. It's dashboard-only — the MCP surface has no project-update call — so it needs you. The reason isn't today's runtime: ifengines.nodewere ever simplified or dropped, the project would silently fall back to Node 20, below both Nuxt's floor and therequire(esm)threshold that caused #174 in the first place.🤖 Generated with Claude Code
https://claude.ai/code/session_01JkKMceYAzAYLrSyC42FWTf