Skip to content

chore(deps): update dependency nuxt to v4.6.2 [security] - #134

Open
renovate[bot] wants to merge 1 commit into
mainfrom
renovate/npm-nuxt-vulnerability
Open

renovate[bot] wants to merge 1 commit into
mainfrom
renovate/npm-nuxt-vulnerability

Conversation

@renovate

@renovate renovate Bot commented Oct 26, 2026

Copy link
Copy Markdown
Contributor

Note

This PR body was truncated due to platform limits.

This PR contains the following updates:

Package Change Age Confidence
nuxt (source) 4.5.2 → 4.6.2 age confidence

Nuxt: Incomplete fix for GHSA-934w-87qh-qr26: a javascript: URL prefixed with an ASCII control character executes from <NuxtLink> when pages is disabled

CVE-2026-114130 / GHSA-jjm6-p2m9-9w4x

More information

Details

Impact

An application that renders <NuxtLink :to="..."> with a value it does not control can be made to render an anchor whose href executes script in the application's origin when clicked.

This is an incomplete fix for GHSA-934w-87qh-qr26 (CVE-2026-53722). It only affects applications running without a pages/ directory, where pages resolves to false and Nuxt registers its own router in place of vue-router. Applications using vue-router are not affected.

Prefixing javascript: with an ASCII C0 control character, for example %01, meant the value was not recognised as having a protocol. It was classified as an internal route and so never reached the protocol check added in GHSA-934w-87qh-qr26, and the router used for pages: false passed it through to the rendered href unchanged. Browsers strip the leading control character and run the result as a script URL.

Both <NuxtLink custom> and the default anchor are affected, and the href is present in the server-rendered HTML.

Mitigating factors
  • The application must have pages disabled (no pages/ directory, or pages: false).
  • The application must bind untrusted input to :to.
  • A victim must click the rendered link.
Affected versions

nuxt <4.6.0, including all Nuxt 3 releases. Versions before 4.4.7 and 3.21.7 are also affected by GHSA-934w-87qh-qr26.

Patches

Fixed in nuxt@4.6.0. Nuxt 3 reached end of life on 31 July 2026 and will not receive a fix.

Anchor href values are now checked for script-capable protocols wherever they are rendered, including the router used when pages is disabled. Protocol detection also moved to the platform URL parser, so a URL is classified the same way the browser will read it.

Workarounds

Upgrade to nuxt@4.6.0 or later. If you cannot upgrade, including on Nuxt 3, validate values before binding them to :to, for example by rejecting anything that is not a path beginning with a single /.

Severity

  • CVSS Score: 2.3 / 10 (Low)
  • Vector String: CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:P/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Nuxt server islands render useHeadSafe input without sanitisation

CVE-2026-114133 / GHSA-xfq5-w38q-pc2x

More information

Details

Impact

useHeadSafe restricts head input to an allowlist of safe tags and attributes. For example, bodyAttrs is limited to class and style, event handler attributes are removed, and <script> tags are only kept for JSON types.

When useHeadSafe is called inside a server island (a .server.vue component or page, or a component rendered with <NuxtIsland>), the island renderer serialises the raw input of every head entry into the island response without the option that marks it as safe input. <NuxtIsland> then pushes that input into the page's head as a regular entry, both during server rendering and when hydrating or refetching on the client.

As a result, input that useHeadSafe would normally strip (such as bodyAttrs.onload or executable <script> content) is rendered into the page. If an application passes user-controlled data to useHeadSafe inside a server island, an attacker who controls that data can achieve stored cross-site scripting on the application's origin.

<!-- components/AuthorMeta.server.vue -->
<script setup lang="ts">
const { data: author } = await useFetch('/api/author')
// author.value.head is user-authored, e.g. { bodyAttrs: { onload: '...' } }
useHeadSafe(author.value.head)
</script>
Mitigating factors
  • Server components are experimental. They are only active when the application defines a .server.vue component or page, or enables experimental.componentIslands and renders <NuxtIsland>.
  • Exploitation requires the application to pass attacker-controlled data to useHeadSafe inside a server island. useHeadSafe called from regular (non-island) components and pages is not affected.
Affected versions

nuxt >=3.13.0, when head support for server islands was introduced. Nuxt 2 is not affected.

Patches

Fixed in nuxt@4.6.2. Nuxt 3 reached end of life on 31 July 2026 and will not receive a fix.

Patched releases apply Unhead's safe input filtering to useHeadSafe entries on the server before they are added to the island response, so islandResponse.head (including what render:island hooks see) only contains input that useHeadSafe allows.

Workarounds

Upgrade to nuxt@4.6.2. If you cannot upgrade, including on Nuxt 3:

  1. Call useHeadSafe with untrusted input from a regular component or page rather than from inside a server island.
  2. Alternatively, validate untrusted head input against your own allowlist before passing it to useHeadSafe inside a server island.

Severity

  • CVSS Score: 5.4 / 10 (Medium)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Nuxt: Server-side route rules (redirect, headers, proxy, ssr) are skipped for case-variant paths when routing is case-insensitive

CVE-2026-114132 / GHSA-vjh5-4x84-xrqp

More information

Details

Impact

With the default router.options.sensitive: false, Nuxt's page router resolves routes case-insensitively, so /Admin renders the same page as /admin. On the server, however, route rules were resolved with Nitro's route-rule matcher, which matches keys case-sensitively. A request whose path differs from a rule key only in letter case therefore rendered the page with none of that rule applied.

Earlier fixes (GHSA-mm7m-92g8-7m47, GHSA-hxvh-4h3w-prp9) case-folded the route rules that the Nuxt app itself reads, such as appMiddleware. This advisory covers the rules applied on the server:

  • redirect: a rule used to send visitors away from a page, such as '/admin/**': { redirect: '/login' }, was skipped, and the page and its server-fetched data were rendered instead.
  • headers: per-route response headers, including security headers such as Content-Security-Policy or X-Frame-Options, were not sent.
  • proxy: the request was not proxied.
  • ssr: false and noScripts: the page was server-rendered, or shipped scripts, despite the rule.
  • getRouteRules(event) (auto-imported, or from nuxt/server) and matchRouteRules from nuxt/server returned no rules for the case variant.
export default defineNuxtConfig({
  routeRules: {
    '/admin/**': { redirect: '/login' },
  },
})

With this configuration, /admin/secret redirects to /login, but /Admin/secret rendered app/pages/admin/secret.vue.

Mitigating factors
  • Applications that set router.options.sensitive: true in nuxt.config are not affected, because their pages are only served for the exact case of the route.
  • Access checks performed in server middleware, route middleware attached with definePageMeta, or in the page's data fetching are not affected. The issue only affects protections that rely on a server-side route rule matching the request path.
Affected versions

nuxt <4.6.2, including all Nuxt 3 releases, and @nuxt/nitro-server <4.6.2.

Patches

Fixed in nuxt@4.6.2. Nuxt 3 reached end of life on 31 July 2026 and will not receive a fix.

Nuxt now resolves server-side route rules itself, decoding and case-folding both the rule keys and the request path unless router.options.sensitive is set, and applies the result to Nitro's redirect, headers and proxy handling, the renderer, and the getRouteRules / matchRouteRules helpers.

When routing is case-insensitive, patched releases change some behaviour:

  • Route rules also apply to case variants of server API routes. For example, /API/x now matches a /api/** proxy or headers rule.
  • Rule keys that differ only in letter case now resolve to the same path on the server, and the later key takes precedence.

The following are not changed by this fix:

  • cache and swr response caching is attached to server routes by Nitro's router, which matches case-sensitively. A case variant is still rendered with its route rules applied, but its response is not cached.
  • Rules that a deployment preset compiles into platform configuration (for example Vercel's config.json or Netlify's _redirects and _headers) are matched by the platform. Once a request reaches the Nuxt server, the rules are applied as described above.
  • If sensitive is set only in app/router.options.ts, the server follows the nuxt.config value. In that configuration route rules can only apply to more paths, never fewer.
Workarounds

Upgrade to nuxt@4.6.2. If you cannot upgrade, including on Nuxt 3:

  1. Set router: { options: { sensitive: true } } in nuxt.config, so pages are only served for the exact case of their route.
  2. Alternatively, enforce access control in server middleware or route middleware rather than relying on a redirect route rule, and set security headers for every response rather than per route.

Severity

  • CVSS Score: 6.3 / 10 (Medium)
  • Vector String: CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Release Notes

nuxt/nuxt (nuxt)

v4.6.2

Compare Source

v4.6.2 is the next patch release.

👉 Changelog

compare changes

🩹 Fixes
  • nitro: apply useHeadSafe rules to server island head (493565e99)
  • nitro-server: read router params from the path h3 matched (010f6b43d)
  • nitro: match server route rules case-insensitively unless routing is sensitive (f6cfee52c)
  • nuxt: resolve rooted paths through the URL parser (#​36617)
  • nuxt: resolve empty route types in shared context (#​36576)
  • nitro: preserve server tsconfig with relocated build dir (#​36494)
  • nuxt: fix scroll offset for IDs starting with numbers (#​36524)
  • kit: do not exclude module directories with a dot in their name from app tsconfig (#​36331)
  • nuxt: detect vapor slots with rawVaporSlotKey from vue (#​36614)
  • nuxt: keep middleware layout when restoring deferred prerendered route (#​36560)
  • schema: provide default value for experimental.typescriptPlugin (#​36561)
  • vite: only replay compile error overlays to newly loaded pages (#​36611)
  • nuxt: re-export AnyFetchPath for emitted server route types (#​36610)
  • vite: warm up the dev ssr entry from a nitro plugin (#​36615)
  • nitro: avoid unused h3 imports in nuxt/server runtime (#​36609)
  • vite-server: scope asset url helpers to the build (#​36591)
  • nuxt: resolve nuxt/app to the app's runtime (#​36587)
  • kit: lowercase filename-derived handler method on nitro v2 (#​36567)
  • nuxt,nitro: normalise string/array appMiddleware (#​36565)
  • nitro: pass response headers to render:response listeners (#​36583)
  • vite: resolve inlined vue to its bundler build in dev (#​36569)
  • nuxt: don't start a page transition with no page to leave (#​36535)
  • nuxt: preserve error state on aborted or cancelled navigation (#​36546)
  • vite,nitro: link null-byte virtual css through /@id/ in dev (#​36549)
  • kit: resolve module dependencies from the declaring module (#​36542)
  • nitro: only disable h3 + nitro presets when nitroAutoImports is off (#​36548)
  • nitro,vite: don't access unspecified dependencies (#​36541)
  • nitro: don't inline deps under absolute transpile dirs (#​36540)
  • vite-server: respect spa loading template location (#​36536)
💅 Refactors
  • webpack,rspack: inline friendly errors plugin (#​36568)
📖 Documentation
  • remove DeepWiki badge (#​36616)
  • rename H(TTP) to HTTP (#​36559)
  • mention proxy route rule (4a529be68)
  • reference definePlugin and defineNitroPlugin in server plugin tip (64b8e0334)
  • open a PR to list a module (e551df267)
  • clarify testing environments, runners, and config (#​32963)
  • align spa loading template references (#​36552)
  • explain how to disable iOS Safari phone number detection (#​36539)
  • fix outdated internal links (#​36550)
  • explain how to restore nitro auto-imports with compat v5 (7499a445b)
  • add spa-loading-template documentation (#​36537)
🏡 Chore
✅ Tests
  • wait for scroll to settle in route-scroll e2e (ca784deb7)
🤖 CI
  • comment nuxt.com preview links on docs pull requests (#​36594)
  • regenerate kit inlinedDependencies in autofix (4b6da3a29)
  • remove docs deploy triggers (917bdf218)
🎉 New Contributors
❤️ Contributors

v4.6.1

Compare Source

v4.6.1 is the next patch release.

👉 Changelog

compare changes

🔥 Performance
  • nitro: build the portable event's web view on first access (cffb182d6)
🩹 Fixes
  • vite: match vite 8.1+ optimizer messages for deps hint (#​36525)
  • nuxt: accept any baseURL when fetch response type is named (#​36523)
  • nitro: resolve nitropack/types declarations under nodenext (242e1971e)
  • nuxt: allow false in setPageLayout types (#​36506)
  • nuxt: align Serialize object types with JSON (#​36503)
  • vite: don't abort dep scan on virtual imports in installed packages (#​36477)
  • nuxt: update publishConfig config export extension (64fa191cb)
  • kit: install the package a missing subpath belongs to (#​36507)
  • vite-server: use the configured outDir as the public directory for non-ssr environment (#​36501)
  • kit,nitro: match dev handler paths on both nitro majors (#​36515)
  • nuxt: preserve onServerPrefetch for useId hydration (#​36430)
  • vite-server: include the request in dev error reports (#​36489)
  • nitro: register each dev server handler once (#​36493)
  • nitro: pass RequestEvent to nuxt dev server handlers (e6beceea2)
  • nuxt: type the global $fetch with nuxt's TypedFetch (#​36492)
  • vite-server: work with deploy target plugins in dev (#​36491)
  • schema: ignore .wrangler directory (#​36487)
  • vite-server: pass the path to the error page render (#​36488)
  • nuxt,vite: disable dev error broadcast if BroadcastChannel throws (#​36480)
  • kit: align semantic version regexp with nightly version format (#​36479)
  • nuxt: export types reachable from async data composables (#​36478)
  • vite: detect enclosing string delimiter in inline style urls (#​36476)
  • schema: type the nuxt dev server handler variant (452052793)
  • nitro: inline nuxt runtime when resolved to windows paths (21657c6fc)
  • nitro: resolve unhead from nuxt in server bundle (dc1e2d6e6)
💅 Refactors
  • kit: use package-manager-detector workspace + deno handling (1563a6d25)
📖 Documentation
  • document dev server handlers and nuxt/server scope (7c36a1352)
  • fix nightly examples (#​36486)
🏡 Chore
✅ Tests
  • resolve temporary fixture paths on macos (30742865c)
  • update test expectation (1f22a114d)
🤖 CI
🎉 New Contributors
❤️ Contributors

v4.6.0

Compare Source

v4.6.0 is the next minor release.

📣 Some news

🖥️ Nuxt CLI v4

Alongside the release of Nuxt v4.6, today also brings a new major release of the Nuxt CLI: @nuxt/cli v4. It ships as a dependency of nuxt, so you'll get it automatically when you upgrade.

Most of what's new is in nuxt dev:

  • an interactive terminal UI, showing URLs, startup progress and keyboard shortcuts, with panels to dive into error logs, routes, network requests and more
  • the dev server gives you more info, such as why it reloaded or restarted, which nuxt.config keys changed, where the time went during a slow start or build, and how long each module took to set up
  • a CLI-level error channel rendered with my-bad (see below), powering things like automatically reloading when a syntax error in nuxt.config.ts is fixed
  • a lock file in .nuxt/, which lets a second nuxt dev (say, one started by an agent) take over or defer to the one you started, and powers new nuxt curl and nuxt task commands that talk to the running server

There's also a new nuxt docs "<query>" search, and nuxt preview --takeover can replace a running preview server. And in general nuxt/cli is a lot smaller and starts a lot faster:

v3.37 v4.0
@nuxt/cli install size 13.1 MB 3.5 MB -73%
@nuxt/cli dependencies 70 31 -56%
nuxt dev: first paint 330 ms 50 ms 6.6x faster
nuxt dev: port bound 338 ms 104 ms 3.2x faster
nuxt dev: memory at rest (Linux) 630 MB 440 MB -30%
nuxt-dev

Although this is a major version, none of the changes should be breaking for Nuxt v4 users: we require Node.js v22.21+, v24.11+ or v26+, we drop nuxt init and only support npm create nuxt@latest, and Nuxt 2 and @nuxt/bridge are no longer supported.

👉 Check out the full Nuxt CLI v4 release notes for everything that's changed.

💡 A server-agnostic Nuxt

The biggest thing about this release is our move towards making Nuxt server-agnostic.

I feel that freedom of choice is very much a fundamental value of the web, and one that unites the whole Nuxt team.

You can use pages/ (with vue-router) or not. You can use Vite, webpack or Rspack to bundle your code. You can pick from dozens of providers to deploy to, pick any image or font provider, choose any database adapter. In every case, the framework is the same.

The server side was different. #app composables imported h3 types, server code imported from h3 and nitropack, and every module that touched the server was tied to whichever major version of those packages Nuxt happened to depend on.

This has become particularly clear as we have been upgrading to new majors of h3 and nitro, which ship breaking changes with a cascading effect throughout the whole ecosystem.

👉 This release changes that.

Alongside explicitly defining our public API in nuxt/kit (which now does not refer to external packages), Nuxt now specifies our own types for the request event, route rules and typed $fetch, and we expose an import surface (nuxt/server) for the server utilities that will be needed by most apps.

This is the culmination of work we started almost a year ago, making it possible to use any server builder with Nuxt, not just Nitro (#​33462).

Of course, under the hood, nuxt/server is still powered by Nitro by default - though we are also announcing a second, experimental implementation, @nuxt/vite-server, which allows pure-Vite server builds using the Vite Environment API.

[!IMPORTANT]
We believe that Nitro is still the right choice for almost everyone.

🌟 We see a number of key benefits for nuxt/server.

  1. it smooths the upgrade to Nuxt 5, which moves to Nitro v3 and h3 v2. Server code written against nuxt/server on 4.6 runs unchanged there, so a module can ship one file for both.
  2. it decouples Nuxt from the Nitro release cycle. Because we 'own' the API, we can adapt to breaking changes in Nitro or h3 without requiring a future major - and we can release Nuxt majors without having to wait for upstream releases.
  3. it makes Nuxt's code more maintainable. It helps us preserve the separation of concerns between our API - /app and /server - and the bundler + server that you ultimately want to build your app.

... and there are a number of other benefits too, from a single type surface to being able to iterate more quickly on features.

Finally, I want to say a special thank-you to @​pi0, whose relentless focus on server agnosticism and work on h3, Nitro and web-standard server primitives over the last few years is what makes a portable RequestEvent possible at all. Thank you, Pooya. ❤️

Almost every feature in this release is already in the Nuxt 5 branch, and most of the remaining Nuxt 5 defaults can be tested today with future.compatibilityVersion: 5 (more details below!).

[!TIP]
Nuxt 3 reached end-of-life on July 31, 2026, so there is no 3.x release alongside this one. If you're still on v3, the upgrade guide is waiting for you.

👀 Highlights

🤷 If you've read this far I'm afraid I have bad news for you: there's a lot more still to say! Nuxt 4.6 is one of our biggest minor releases, with over 420 commits since v4.5.2.

... so, you might want to grab a coffee! ☕️

🧩 nuxt/server

It has been asked for for a long time, and it now exists (#​36275)!

nuxt/server is a new import source for server code: handlers, middleware and utilities - a complement to nuxt/app. Where nuxt/app is for the part of your application that also runs in the browser, nuxt/server is for the part that only runs on the server.

import { defineEventHandler, getQuery } from 'nuxt/server'

export default defineEventHandler((event) => {
  const { name } = getQuery<{ name?: string }>(event)
  return { message: `Hello, ${name ?? 'world'}!` }
})

The utilities use web standards and are typed against a portable RequestEvent:

event.req         // Request
event.url         // URL
event.res         // { status, statusText, headers }
event.res.headers // Headers
event.context     // per-request context

Under @nuxt/nitro-server they are backed by Nitro and h3, but you never import from either.

So the same handler runs under Nitro v2, Nitro v3 or @nuxt/vite-server, and a module that imports from nuxt/server doesn't need a peer dependency on h3 or nitropack.

We think this will make a big difference in smoothing out the upgrade to Nuxt v5 and Nitro v3.

There is a typing benefit too. We no longer hoist h3 or Nitro types into your app to type useRequestEvent, $fetch or route rules, which removes a source of type conflicts when versions differ (#​36212, #​36214, #​36293).

The surface is small, and covers what published modules and user code typically need: defineEventHandler, createError/isNuxtError, request URL, headers, query, body (plain and validated with any Standard Schema library or a function), cookies, redirects, response status, getRouterParam(s), getRequestIP, handleCors, getRouteRules, useRuntimeConfig, useAppConfig and sessions.

import { defineEventHandler, readValidatedBody } from 'nuxt/server'
import { z } from 'zod'

export default defineEventHandler(async (event) => {
  const user = await readValidatedBody(event, z.object({ name: z.string() }))
  return { created: user.name }
})

We encourage you to use web APIs (event.req.headers, for example), or raise an issue if there's functionality you're missing from nuxt/server 🙏

If you do need to step outside nuxt/server for a particular handler, don't worry! Nothing has been taken away: import defineEventHandler and the helpers you need from h3 or nitropack/runtime as before, and that handler works exactly as it did on Nuxt 4.5.

import { defineEventHandler, readMultipartFormData } from 'h3'

export default defineEventHandler(async (event) => {
  const parts = await readMultipartFormData(event)
  return { received: parts?.length ?? 0 }
})

[!IMPORTANT]
On Nuxt 4, the auto-imported defineEventHandler, getQuery, readBody and the rest are still h3's own helpers, which take a different shape of event. Import from nuxt/server explicitly, including defineEventHandler to use the new server runtime. If you mix the two, you'll see a NUXT_E8012 error which should tell you which import to change.

A few helpers also behave differently from their h3 v1 namesakes: sendRedirect returns the response rather than sending it, createError takes status and statusText, and response headers are set through event.res.headers. The upgrade guide has a table of the differences.

Nothing in this release requires a migration. But if you have server code you'd like to make portable ahead of Nuxt 5, this is the best way.

👉 Read the server imports guide.

🔐 appSecret and sessions

Nuxt now has a root application secret: runtimeConfig.appSecret, set with NUXT_APP_SECRET (#​35874, thanks to @​onmax). Modules and server features derive purpose-specific secrets from it with deriveSecret(purpose), so NUXT_APP_SECRET is the only secret you need to configure.

openssl rand -base64 32

In development Nuxt generates and persists one if none is configured (and warns the first time a derived secret is used). Builds never generate one.

The first thing to use it is a set of session helpers in nuxt/server (#​36358). Sessions are sealed into a cookie with iron, so there's no server-side storage to configure:

import { defineEventHandler, useSession } from 'nuxt/server'

export default defineEventHandler(async (event) => {
  const session = await useSession<{ visits: number }>(event)
  await session.update(data => ({ visits: (data.visits ?? 0) + 1 }))
  return { visits: session.data.visits }
})

[!NOTE]
As a reminder, runtime config keys prefixed with app (runtimeConfig.app, runtimeConfig.appSecret) are reserved for Nuxt.

🎯 Typed $fetch, rebuilt

$fetch and useFetch have been typed from your server routes for a long time. But the types were derived from Nitro's InternalApi interface, and past a few hundred routes they hit TypeScript's instantiation limit with the familiar TS2589: Type instantiation is excessively deep and possibly infinite.

We've rebuilt typed fetch on top of fetchdts (#​36238). Nuxt compiles your server routes into a route tree with an exact-match table for static paths and accessors specialised to your route set. Resolution cost now scales with call sites, not with route count:

routes before after
100 1,397,361 instantiations / 0.77s 51,558 / 0.26s
300 5,774,425 / 3.49s (TS2589) 51,558 / 0.16s
1000 12,831,467 / 7.44s (TS2589) 51,558 / 0.20s
3000 (200 call sites) 71,875,148 / 53.63s (TS2589) 101,678 / 0.62s

Peak memory for the same runs dropped from 946 MB to 140 MB. 🔥

Plus, the route set also carries the body, query and headers a handler validates, so calls are checked more tightly than before:

// server/api/users.post.ts validates { title: string, count: number }
await $fetch('/api/users', { method: 'post', body: { title: 'a', count: 1 } })
await $fetch('/api/users', { method: 'post', body: { title: 'a', count: 'no' } })
//                                                                ^ not assignable to number
await $fetch('/api/users', { method: 'post' })
//           ^ body is required

On Nuxt 4 this is opt-in, because there are small changes to type inference, and hand-written ServerRoutes augmentations need a small rewrite. It is the default in Nuxt 5.

export default defineNuxtConfig({
  experimental: {
    routeTypedFetch: true,
  },
})

There's also a new experimental.strictRouteTypes option to reject calls to paths that don't exist (otherwise these just return unknown), and an 'isomorphic' mode that types your pages as GET routes too if you want to be able to $fetch from the Vue renderer with type safety.

👉 Read more in the experimental features docs.

🐛 Better errors in development with my-bad

Server-side errors in development used to look like this:

ReferenceError: foo is not defined
    at Object.<anonymous> (/_nuxt/app/pages/index.vue:1:1)

There was no source position or code frame, and the Youch iframe we rendered could not show you the frame in your own source either. Both are now fixed (#​36258, nuxt/cli#1518).

Dev SSR stack traces are now mapped before anything reads the error, and the Youch overlay has been replaced with my-bad. It renders into your app's own error page as an overlay (or as a standalone page when the app can't render one), with the mapped stack trace and a code frame from your source, and the same report is printed in your terminal. We are still working with @​atinux, @​HugoRCD and @​antfu to make these pages nicer still.

my-bad-ssr-expanded-dark my-bad-ssr-copymenu-dark

With Nuxt CLI v4, there is a single live error channel at the CLI level. It survives worker restarts, so (for example) a syntax error in nuxt.config.ts will live-reload the page once you fix it. Each error is rendered once rather than at every layer it passes through, and the same channel streams build progress and app logs to the dev panel.

🎨 A new loading screen, 404 and error pages

@​HugoRCD has redrawn the loading screen you see while the dev server starts. It is now a WebGL2 particle field that traces a mountain range behind the Nuxt lockup (#​36178), using a single shader and a single draw call. Without WebGL2 it falls back to the static lockup, and with prefers-reduced-motion the animation stops. Try hovering over it. 🏔️

Hugo also gave the built-in 404 and error pages a neutral palette and lighter type (#​36255), and @​MirkoJa added a back button to the 404 page (#​35688).

404-page-dark
⚡️ Vue Vapor support

Nuxt now supports Vue 3.6's Vapor Mode in interop mode (#​35759). Your app root stays on the virtual DOM, and you can opt individual components or pages into Vapor by adding the vapor attribute to <script setup>:

export default defineNuxtConfig({
  vue: {
    vapor: true,
  },
})
<script setup vapor lang="ts">
const count = ref(0)
</script>

<template>
  <button @click="count++">
    count is {{ count }}
  </button>
</template>

Routing, useAsyncData, layouts and most built-in components keep working unchanged. Along the way we made the auto-import loader, useAsyncData, definePageMeta and slot inspection Vapor-aware, and we now have a Vapor test suite so we can track what's supported.

[!NOTE]
This requires Vue ^3.6.0-rc.2 or newer. See the known limitations and try it in a fresh project first.

🧩 Addons for useFetch and useAsyncData

@​cernymatej has added an addons option to the createUseFetch and createUseAsyncData factories (#​35797). An addon can declare custom call-site options, adjust the merged options, wrap the handler with middleware, and extend what the composable returns, and it can be reused across as many custom instances as you like.

const refreshOnFocus = defineUseFetchAddon({
  setup: (options: UseFetchAddonOptions<{ refreshOnFocus?: boolean }>) => {
    if (import.meta.server || !options.refreshOnFocus) { return }

    return (asyncData) => {
      const focused = useWindowFocus()
      watch(focused, focused => focused && asyncData.refresh())
      return { focused }
    }
  },
})

export const useApiFetch = createUseFetch({ baseURL: '/api', addons: [refreshOnFocus] })
<script setup lang="ts">
const { data, focused } = await useApiFetch('/todos', { refreshOnFocus: true })
</script>

This resolves a long list of feature requests for useAsyncData and useFetch (refresh on focus, polling, retries, auth headers and more) without making the core composables opinionated about any of them.

👉 Read about defineUseFetchAddon and defineUseAsyncDataAddon.

🚀 Performance

There is a lot of performance work in this release.

  • <NuxtLink> renders 58% faster on the server (#​36015). Internal links are now rendered as a plain <a> with no useLink, no computed and no reactive state. Rendering 200 links went from 1.36ms to 0.57ms, which is 1.4x faster than a bare <RouterLink>.
  • Early 404s (#​36117). With experimental.early404, page routes are compiled into a static matcher at build time and requests that can't match any page skip creating the Vue app, running plugins and middleware entirely. On an app with 30 pages and ~23ms of boot work per render, a JSON 404 went from 37.1ms to 0.3ms.
  • Templates declare their dependencies (#​35875). The virtual file templates used in Nuxt's build used to regenerate on every file change. They now declare what invalidates them, so editing a component regenerates 0 of 49 core templates instead of all of them, and the builder:watch hook settles in 0.6ms instead of 36ms.
  • Cookie jars (#​36114). useCookie parses the cookie header once per request (or once per microtask on the client) instead of on every call. A nice side effect: a cookie set in a plugin during SSR can now be read by a later useCookie in a page.
  • ssr: false pages are tree-shaken from the server bundle (#​35836, thanks to @​Austin1serb).
  • Serializable definePageMeta keys are extracted at build time (#​35919), behind experimental.extractSerializablePageMeta.
  • Faster dev server warm-up: Vite's server module graph is warmed before the client (#​36154), the client graph is crawled during warm-up (#​35870), and warm-up yields to your first navigation so it never competes with a real request (#​36412).
  • Smaller client bundle: unctx is no longer shipped to the browser and defu is skipped for a single app.config (#​36371).
  • Fewer dependencies: @nuxt/kit no longer depends on c12, untyped, confbox, pkg-types, ufo or mlly, and jiti and giget are now optional peers loaded only when needed (#​35936, #​35943, #​35946, #​36071, #​36073, #​36083).
  • Lazy impound tracing for builds (#​36172), virtual module hooks filtered by id (#​36077), isVue checks migrated to plugin filters (#​36116) and an flru prerender cache (#​36340).

Put together, on our benchmark machine (arm64 Linux, Node 24.15, medians of 5 runs):

v4.5.2 v4.6.0
@nuxt/kit install size 6.4 MB 2.1 MB -67%
@nuxt/kit transitive dependencies 36 22 -39%
nuxt build, starter 4.4 s 3.6 s -18%
nuxt build, 200 pages / 200 components / 50 routes 11.6 s 10.3 s -11%
SSR throughput, page with 300 <NuxtLink>s 110 req/s 140 req/s +26%

Dev server start-up (spawn to first HTML) on the same machine is about 12% faster on a starter app, with the second request served in roughly half the time, though the CLI major changed alongside so not all of that is Nuxt.

📦 Lighter payloads

useAsyncData and useFetch accept a serialize: false option to keep data out of the __NUXT_DATA__ payload (#​35779). That's most useful inside components that never hydrate, and experimental.stripNeverHydratedData applies it automatically to data fetched within hydrate-never component trees.

Nuxt also warns in development when a page's payload exceeds 100 kB (#​35777) and when a route rendered with noScripts relies on client-side JavaScript (#​35780), and noScripts pages keep their non-script resource hints and attach their styles correctly (#​35803, #​36356, #​36359).

<NuxtLink> now prefetches server-page islands (#​35808), and @​atinux made prefetch hints throttled and prioritised so a page full of links doesn't flood the network (#​36261, #​36324). Building on that, route chunks, layouts, middleware, payloads, islands and resource hints all now go through one client prefetch scheduler with per-kind concurrency caps, deduplication by key, and cancellation of in-flight work when you navigate away (#​36391).

🔮 Nuxt 5 features, today

Most of what's new in Nuxt 5 is already in 4.6, either as the default or behind a flag. future.compatibilityVersion: 5 turns on the Nuxt 5 defaults in one go, and every one of them can be enabled (or disabled) individually.

export default defineNuxtConfig({
  future: {
    compatibilityVersion: 5,
  },
})

Newly gated behind the flag in this release:

  • Typed pages (experimental.typedPages) (#​35789)
  • Typed $fetch (experimental.routeTypedFetch), described above
  • Case-sensitive routing, matching Nitro (#​35650, thanks to @​Mateleo)
  • Early return from navigateTo (experimental.navigateToEarlyReturn): navigateTo in <script setup> short-circuits the rest of the setup, so redirects and 404s don't throw on missing data (#​36115)
  • Serializable page meta extraction (experimental.extractSerializablePageMeta)
  • Client-side payload extraction (experimental.payloadExtraction: 'client')
  • Normalized page names, clearNuxtState resetting to defaults, experimental.watcher: 'builder', and no auto-imported server-only head composables
  • No baseUrl in generated tsconfigs (#​36040, thanks to @​oritwoen)
  • Inline error rendering (experimental.inlineErrorRendering): when a server render fails, error.vue is rendered in the same request with a plain try/catch, instead of re-entering the server over an internal request to /__nuxt_error. Headers and cookies the failed render had already set are kept, error renders no longer pass through Nitro middleware and route rules a second time, and render:html fires with the original event (#​36399).

The upgrade guide now lists exactly what the flag changes on Nuxt 4.

🧪 Experimental: @nuxt/vite-server

Since v4.2 server.builder has been configurable. This release adds a second server builder: @nuxt/vite-server. It builds a Nuxt app with Vite alone (#​36218, #​36279, #​36288).

This is all you need to do

[!IMPORTANT]
✂ PR body was truncated to here.


Configuration

📅 Schedule: (in timezone Europe/Warsaw)

  • Branch creation
    • At any time (no schedule defined)
  • Automerge
    • At any time (no schedule defined)

🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.

♻ Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about this update again.


  • If you want to rebase/retry this PR, check this box

This PR was generated by Mend Renovate. View the repository job log.

@renovate
renovate Bot requested a review from oritwoen as a code owner October 26, 2026 08:46
@coldtea-pr-lens

Copy link
Copy Markdown

Note

renovate[bot] opened this pull request, so PR Lens left it undrawn. Comment @pr-lens draw to draw it

github.comment.notice: false in .github/pr-lens.yml turns this note off

@renovate
renovate Bot force-pushed the renovate/npm-nuxt-vulnerability branch from f09da62 to f3a15f6 Compare October 27, 2026 01:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants