Nuxt server islands accept props via the /__nuxt_island/ endpoint. When an application has a server island component that forwards props directly into Vue's dynamic component resolution (<component :is>, resolveDynamicComponent, or h()), an attacker can pass a plain string value (rather than a component definition) to instantiate any globally-registered Vue component or any native HTML element.
For example:
{"as":"SomeGlobalComponent"}
...resolves and renders SomeGlobalComponent if it is globally registered, even though the attacker should only be able to drive props for the island's declared component. Similarly, { "as": "iframe" } renders an <iframe> element.
Unlike the primary RCE vector (GHSA-9473-5f9j-94wq), this does not require vue.runtimeCompiler to be enabled. A plain string prop is sufficient to trigger component resolution. The template/render key guard that addresses the RCE vector does not block plain string values.
Some component libraries expose a polymorphic as / asChild prop that forwards its value into <component :is>; @nuxt/ui (via reka-ui) is a widely used example. An application is affected if such a component receives the attacker-controlled value inside a server island. Note this does not require explicit prop forwarding: island props the island component does not declare fall through as attributes onto its single root element, so an island whose root is a reka-ui / @nuxt/ui component receives the attacker's as value implicitly. Unlike the RCE vector, no vue.runtimeCompiler is required, which makes this vector reachable in more configurations. These libraries are not themselves vulnerable; they are noted only because they commonly provide the dynamic-component sink. Installing @nuxt/ui does not by itself register any component as a server island: the application must define the island (a .server.vue file).
Mitigating factors
Exploitation requires a server island component that puts an attacker-controlled value onto a dynamic-component path (<component :is>, resolveDynamicComponent, h(), or a polymorphic as / asChild prop), either explicitly or via attribute fallthrough when the island's root is such a component.
Reachable components are limited to what is actually in the island app's global registry. In a default pages-enabled app that is RouterView and RouterLink (both registered globally by the pages router plugin, which runs even in component islands), plus any components/global/ component and any component a module registers globally. RouterLink in particular renders an attacker-influenced <a> (and, because an island that declares no props forwards all props, to and other RouterLink props ride the same fallthrough). Vue built-ins (Transition, KeepAlive, Teleport, Suspense) and Nuxt auto-imports (ClientOnly, NuxtLink, NuxtPage, etc.) are NOT in the island app's global registry and cannot be resolved this way; an unresolved name instead renders as a native HTML element (the element-injection half of this issue).
Declaring the props an island accepts, or setting inheritAttrs: false on it, prevents an undeclared as from falling through to a polymorphic root and neutralizes this vector.
Arbitrary JavaScript execution is not possible through this vector (no template/render compilation).
Island component names are constrained to the build-time component registry; an attacker cannot resolve arbitrary components.
Affected versions
Nuxt >=3.1.0 <3.21.10 and >=4.0.0 <4.5.1, with component islands active. The island prop-forwarding behavior has existed since server islands were introduced in v3.1.0, and this vector does not depend on vue.runtimeCompiler. Nuxt 2 is not affected.
Note the patch (below) closes the implicit attribute-fallthrough path, which is the majority case. An island that explicitly forwards an untrusted prop into dynamic component resolution (or forwards it under a prop name other than as) remains the application's responsibility in every version; see Workarounds.
Patches
Fixed in nuxt@4.5.1 and nuxt@3.21.10. Patched releases reject a top-level as island prop (HTTP 400 at the /__nuxt_island/ endpoint). This closes the implicit path: island props an island does not declare fall through as attributes onto its single root, so a top-level as would otherwise reach a polymorphic root component's as prop (the reka-ui / @nuxt/ui convention) and drive dynamic component resolution without the author binding it. Rejecting the top-level as prop blocks that fallthrough while leaving nested data and other prop names untouched.
The framework deliberately does not attempt to block every case: it cannot safely tell a string used as data from one used as a component selector, and it has no island-local hook into Vue's h() or resolveDynamicComponent(). An island that explicitly forwards an untrusted value into <component :is> / h() / resolveDynamicComponent(), or that forwards it under a different polymorphic prop name, is therefore not covered by the patch and must follow the guidance below. The Nuxt documentation now warns against this.
Workarounds
Upgrade to nuxt@4.5.1 or nuxt@3.21.10. That upgrade also removes the related object-prop RCE (GHSA-9473-5f9j-94wq). In addition, in any version:
Do not forward island props into <component :is>, resolveDynamicComponent, or h(). Map an untrusted discriminator through a closed allowlist of imported component definitions instead of passing the raw prop value.
Declare the props an island accepts, or set inheritAttrs: false on it, so request input cannot fall through to a polymorphic root component.
Avoid registering sensitive components globally that could leak information if instantiated by an attacker.
When a Nuxt dev server is bound to a network-reachable interface (for example nuxt dev --host for on-device testing), the default-enabled Chrome DevTools workspace endpoint GET /.well-known/appspecific/com.chrome.devtools.json returns the absolute project root (workspace.root, i.e. rootDir) and a persistent per-project workspace UUID.
GHSA-rq7w-g337-39qq added a gate (isLocalDevRequest) intended to restrict this endpoint to local requests, but that gate is header-based: it trusts request metadata rather than the connected peer address. A request with no Sec-Fetch-Site, Origin, and Referer headers (normal for a non-browser client such as curl) is treated as local, and the Host allow-list is compared against the attacker-supplied Host header. As a result, any unauthenticated host that can reach the dev server on the LAN can retrieve the project's absolute filesystem path and workspace UUID, for example with curl -H 'Host: localhost' http://<dev-host-lan-ip>:3000/.well-known/appspecific/com.chrome.devtools.json.
This is information disclosure only: there is no file read, file write, or code execution reachable from the endpoint. It requires the dev server to be reachable beyond loopback and experimental.chromeDevtoolsProjectSettings to be enabled (it defaults to true). Production builds are unaffected, because the endpoint is registered only as a development handler.
Patches
Fixed in nuxt@4.5.1 and nuxt@3.21.10. The endpoint now additionally requires the connected TCP peer to be a loopback address, verified from the socket rather than from request headers, so a non-loopback LAN client is rejected regardless of the Host, Origin, Referer, or Sec-Fetch-* headers it sends. The shared header-based check is left unchanged, so the CSRF / same-origin behaviour that other dev handlers rely on is preserved. After this fix, Chrome DevTools workspace auto-mapping only works when the browser reaches the dev server over loopback (localhost / 127.0.0.1 / ::1), which matches the feature's intent (the browser and dev server sharing a filesystem).
Workarounds
Do not bind the dev server to a non-loopback interface on an untrusted network, or restrict access to the dev port with a firewall.
Disable the feature by setting experimental.chromeDevtoolsProjectSettings: false in nuxt.config.
Nuxt server islands accept props via the /__nuxt_island/ endpoint. When vue.runtimeCompiler: true is enabled (off by default) and the application has a server island component that forwards props into Vue's dynamic component resolution (<component :is>, resolveDynamicComponent, or h()), an attacker can inject a template key into the island props to achieve server-side remote code execution in the Nitro process.
{"as":{"template":"<attacker-controlled>"}}
Vue's runtime template compiler compiles and executes the attacker-controlled template in the server process. The same primitive also works on the client side when the runtime compiler is active there, though the server-side path is the primary concern.
Some component libraries expose a polymorphic as / asChild prop that forwards its value into <component :is>; @nuxt/ui (via reka-ui) is a widely used example. An application is affected if such a component receives the attacker-controlled value, provided vue.runtimeCompiler is also enabled. Note this does not require the island author to explicitly forward a prop: island props that the island component does not declare fall through as attributes onto its single root element (standard Vue attribute inheritance), so an island whose root is a polymorphic component receives the attacker's as value implicitly. These libraries are not themselves vulnerable; they are noted only because they commonly provide the dynamic-component sink.
Common configurations that satisfy the preconditions
The flaw is in Nuxt core. The library below is not itself vulnerable; it is noted because it commonly provides the dynamic-component sink an application might inadvertently expose.
@nuxt/ui (via its underlying reka-ui primitives) exposes a polymorphic as / asChild prop that is forwarded into Vue's dynamic-component resolution. Installing @nuxt/ui does not by itself register any component as a server island. Exploitation requires the application to define an island component (a .server.vue file) whose rendered output puts the attacker-controlled value on such a component's as / asChild prop; with vue.runtimeCompiler: true, the attacker-controlled template is then compiled and executed. Because undeclared island props fall through as attributes to the single root, this can happen without any explicit binding: an island whose root is a reka-ui / @nuxt/ui component is enough. An example vulnerable island (the as value falls through to UButton, no explicit forwarding needed):
The island URL hash (/__nuxt_island/<Name>_<hash>.json) is a deterministic (unsalted) content hash, not an authentication token. It provides integrity relative to the URL but is not a security boundary: an attacker who knows the component name and desired props can compute a valid hash.
Mitigating factors
vue.runtimeCompiler is off by default in Nuxt. The vast majority of Nuxt applications are not affected.
Exploitation requires a second precondition: the application must have a server island component that puts an attacker-controlled value onto a dynamic-component path (<component :is>, resolveDynamicComponent, h(), or a polymorphic as / asChild prop). This can occur explicitly or via attribute fallthrough when the island's root is such a component.
SSG / static deployments are largely unreachable via this vector (no server process to exploit).
Island component names are constrained to the build-time component registry; an attacker cannot resolve arbitrary components.
Affected versions
Nuxt >=3.4.0 <3.21.10 and >=4.0.0 <4.5.1, and only when vue.runtimeCompiler: true and component islands are active. Earlier versions did not allow the Vue compiler to be enabled in the server bundle (the compiler dependencies have been mock-aliased on the server since v3.0.0-rc.1), so the runtime-compilation path is not reachable. Nuxt 2 is not affected (no server islands).
Patches
When vue.runtimeCompiler is enabled, island requests whose decoded props contain a template key at any depth are rejected with an HTTP 400 and a diagnostic suggesting the author rename the prop or disable the runtime compiler. The guard is gated on the runtime compiler being enabled, so the default configuration (compiler off) is unaffected and legitimate props that merely contain a template field (for example CMS content) continue to render. A render key is not rejected: island props arrive as JSON, so a render value can only be an inert string, which Vue ignores.
Fixed in nuxt@4.5.1 and backported to nuxt@3.21.10.
Workarounds
Upgrade to nuxt@4.5.1 or nuxt@3.21.10. If you cannot immediately upgrade, you can mitigate by:
Ensure vue.runtimeCompiler is set to false (the default).
Do not forward island props into <component :is>, resolveDynamicComponent, or h() without sanitization.
As a defense-in-depth measure, deploy a WAF rule on /__nuxt_island/ that URL-decodes and JSON-parses the props value and blocks any object property named template or render in the decoded island props, inspecting both query and body for all methods. Note: this only covers direct and browser-originated island requests; initial-SSR internal island renders do not transit the edge and a WAF alone does not close the vector.
The internal island renderer endpoint (/__nuxt_island/...) decodes and hashes attacker-controlled request input before it validates the URL-resident hash. An unauthenticated POST /__nuxt_island/<name>_<anything>.json with a large JSON body (for example ~4.6 MB / 150k keys) is fully read, destr-parsed, and run through ohash before the request is rejected with a 400. Because Nitro runs on a single event loop, this both wastes CPU on the doomed request and delays every concurrent request. A low request rate is enough to degrade or stall the server. No valid hash and no authentication are required.
Patches
Fixed in nuxt@4.5.1 and nuxt@3.21.10. The island handler now enforces a raw body-size cap (413) and a JSON nesting-depth cap (400) before parsing or hashing, so oversized or deeply nested input is rejected cheaply.
Workarounds
Put a small request-body limit in front of /__nuxt_island/ at your reverse proxy / edge (islands legitimately send only a compact props payload), or disable server components if unused.
An unauthenticated attacker can crash a Nuxt server that renders any island / server component containing a v-for over a prop (for example v-for="n in count" or a <slot v-for>). Because the island URL hash is a non-secret digest of the request, the attacker can compute a valid hash for arbitrary props and send the iterated prop as a large integer. The server then expands the v-for to that many nodes during SSR, allocating memory proportional to the attacker's number. Reporter figures: count=8000000 produced a 142.9 MB response; count=40000000 (and items=4000000 on a slot list) produced an out-of-memory crash of the worker from a single ~130-byte request. Both the plain v-for path (Vue's ssrRenderList) and the slot path (vforToArray) are affected.
Patches
Fixed in nuxt@4.5.1 and nuxt@3.21.10. Island/server-component v-for sources are now clamped to a maximum iteration count (MAX_VFOR_LENGTH = 100000) at the render boundary, covering the plain path, the <slot v-for> element, and the vforToArray slot-props helper. Combined with the body-size cap (GHSA-9pgf-384g-p7mv), a single island render can no longer allocate without bound regardless of which v-for path is used or whether the prop arrives as an integer or an array.
Workarounds
Avoid v-for directly over an unclamped prop in server components, or clamp the count in the component (v-for="n in Math.min(count, 1000)"). A body-size limit in front of /__nuxt_island/ only mitigates array-shaped inputs, not the integer-amplification case.
Nuxt matches route rules case-insensitively by default (mirroring vue-router's default sensitive: false routing). The fix for GHSA-mm7m-92g8-7m47 / CVE-2026-53721 lowercased the lookup path before matching route rules, but the route-rule keys compiled into the matcher were left verbatim. As a result, any route rule whose key contains an uppercase character (for example /Admin, /Dashboard/**, or the rules Nuxt derives from PascalCase/camelCase page files such as pages/Admin.vue) never matches, because every lookup is folded to lowercase while the key stays mixed-case.
vue-router still serves the page case-insensitively, so the page renders with none of its Nuxt route-rule protections applied. The most serious consequence is an authorization bypass: an appMiddleware rule used as an auth gate (routeRules: { '/Admin/dashboard': { appMiddleware: 'auth' } }) is dropped, and /Admin/dashboard, /admin/dashboard, and /ADMIN/dashboard all render the protected page (and its SSR-fetched data) to an unauthenticated visitor instead of redirecting to login. The same gap drops Nuxt's other app-side route-rule behaviours for mixed-case keys, including the client redirect middleware, the app-side ssr: false decision, prerender, and payload handling.
Patches
Fixed in nuxt@4.5.1 (4.x) and nuxt@3.21.10 (3.x). The route-rule matcher now case-folds the compiled keys the same way it folds the lookup path, so key and lookup normalisation are symmetric. Both sides are gated on router.options.sensitive: with sensitive: true (case-sensitive routing) configured casing is preserved on both sides.
Scope note: server-emitted per-route headers, server redirect, and proxy are matched by Nitro's own case-sensitive route-rule matcher, not by Nuxt's app-level matcher. They are unchanged by this advisory. The fix covers the app-level protections Nuxt owns (appMiddleware, appLayout, the client redirect middleware, the app ssr decision, prerender, and payload).
Workarounds
If you cannot upgrade immediately, any one of:
Key all routeRules (and name your page files) in lowercase, so the keys already match the folded lookup path.
Set router: { options: { sensitive: true } } so routing and route-rule matching are both case-sensitive and exact (requests must then use the exact casing).
Enforce the sensitive protections server-side independently of route rules (for example a server middleware that checks auth), which does not rely on case-insensitive route-rule matching.
When a page is covered by routeRulescache / swr / isr, Nuxt enables runtime payload extraction and serves /<page>/_payload.json. On affected versions the renderer stored the SSR payload in the shared cache:nuxt:payload storage under a path-only key (no cookie, authorization, or cache.varies dimension) and, on a later payload request, returned the cached entry before route middleware / page guards ran again.
As a result, once any authenticated user warms a protected, cached page, a subsequent GET /<page>/_payload.json from an unauthenticated client or a different authenticated user receives the first user's payload: the full SSR data for that route, including anything loaded via useFetch / useAsyncData (for example /api/me: profile, tenant, billing, token-like values). The HTML response stays correctly varied and protected; only the extracted payload leaks. Both cross-user (A warms, B receives A) and unauthenticated disclosure are exploitable. cache.varies does not mitigate it, because the payload cache ignores varies.
Introduced when runtime payload extraction landed for cached routes (#34410); the regression is specific to the 4.x line, where the runtime cache:nuxt:payload storage was added and the import.meta.prerender gate on the payload-cache read/writes was dropped. The 3.x line shipped the same feature with the gate intact and is not affected.
Patches
Fixed in nuxt@4.5.1. Runtime payload-cache reads and writes are again confined to prerendering (import.meta.prerender); at runtime, /<page>/_payload.json follows the normal render path so route middleware, routeRules.appMiddleware, and page guards run for the current request. main / v5 and the 3.x line already had this property, so 3.x is not affected.
Workarounds
Set experimental.payloadExtraction: false (reporter-validated): the standalone /_payload.json endpoint returns 404 and the page still serves a 200 with an inline payload.
Do not apply cache / swr / isr to authenticated pages that render user-specific SSR data.
As defense-in-depth, require authentication for /**/_payload.json at a proxy / CDN.
After upgrading, purge any CDN / platform cache that may already hold protected payloads.
⚠️This is a security release. We recommend upgrading as soon as possible with npx nuxt upgrade --dedupe.
It fixes server-side RCE and unauthorized component instantiation via server island props, a route rule authorization bypass, server component DoS, cross-user payload disclosure on cached pages, and dev server path disclosure. Refreshing your lockfile also pulls in @nuxt/devtools@3.3.1, which fixes a separate critical development-only RCE.
If you already upgraded for the earlier route rule advisory (CVE-2026-53721), you still need this release: one of the fixes addresses a regression introduced by that fix.
If you use the cache, swr or isr route rules, purge any CDN or edge cache after upgrading; a leaked _payload.json may already be cached upstream.
A good chunk of this release is (hopefully) invisible plumbing for Nuxt 5. We've moved onto the latest major versions of several core dependencies (unhead v3, unctx v3, and Vite 8), switched the framework's own build over to tsdown, and introduced a stable nuxt/* build output contract with dev exports so that type-checking in the Nuxt monorepo works without a build step (#35463, #35605).
Much of this is working to shrink the gap between v4 and v5 internally, so that the migration will be as boring as possible.
[!TIP]
If you want to test some of the breaking changes of Nuxt v5, you can already opt in with future.compatibilityVersion: 5. Keep an eye on the Upgrade Guide for details as they land.
With the release of Nuxt v4.5, our focus as a team will turn to stabilising Nuxt v5 and creating compatibility utilities to make the upgrade as smooth as possible.
Nuxt 3 End-of-Life
Nuxt 3 reaches end-of-life on July 31, 2026, so this is one of the last few 3.x releases we'll ship. If you're still on v3, now is a great time to move across. Most people told us the v3 to v4 upgrade was smooth, and we've kept the upgrade guide up to date.
Alongside v4.5.0 we're publishing a maintenance patch for the 3.x line (v3.21.9) with the compatible bug fixes and smaller improvements from this release backported. The headline items here (Vite 8, Rspack 2, unhead v3, unctx v3) are major upgrades and stay v4-only, so 3.x remains stable as it approaches end-of-life.
👀 Highlights
Nuxt 4.5 is a big one. This release ships three major upgrades to the build layer (Vite 8, Rspack 2, and a brand new Rsbuild-powered pipeline for the Rspack builder), an experimental SSR streaming mode, a handful of new composables and conventions, and a lot of groundwork that brings us closer to Nuxt 5.
There's a lot here, so grab a coffee. ☕️
⚡️ Vite 8
Nuxt now runs on Vite 8 (#34256). This brings faster cold starts, the latest Rolldown-powered internals, and many upstream improvements from the Vite team.
For most apps this is a transparent upgrade. If you have custom Vite plugins or config, it's worth skimming the Vite migration guide to check for anything that affects you.
[!WARNING]
Vite 8 is a major version bump. If you depend on Vite directly (custom plugins, vite.config tweaks, or ecosystem plugins that pin a Vite version), make sure those are compatible before upgrading in production.
🦀 Rspack 2 and Rsbuild
If you use the Rspack builder, this release is a substantial upgrade. We've moved to Rspack 2 (#34929), which is faster and lighter, and rebuilt the builder on top of @rsbuild/core (#35489).
The public surface stays the same. You still opt in with builder: 'rspack' and the existing rspack:* hooks continue to work:
Under the hood, though, a lot has changed for the better:
The dev server now runs in middleware mode via Rsbuild, replacing webpack-dev-middleware and webpack-hot-middleware (#35575).
We use an Rspack-specific Vue loader for correct SSR scoped-style ids and stricter ESM resolution (#35566).
[!NOTE]
This is the foundation for first-class Rsbuild support. We kept the builder named rspack for now so nothing breaks, but the internals are now Rsbuild all the way down.
🌊 Experimental SSR Streaming
This is one I'm particularly excited about. You can now enable SSR streaming to dramatically improve Time to First Byte (#34411). Instead of buffering the whole rendered page and sending it in one go, Nuxt flushes the HTML shell (your <head>, styles, preload hints, and entry scripts) immediately, then streams the body as Vue renders it.
Streaming is automatically disabled for bots and crawlers so search engines still receive fully-rendered HTML. You can tune which user agents count as crawlers, and you can opt individual routes out:
There's one thing worth understanding before you turn it on. Because streaming commits the HTTP status and headers with the very first byte, anything that mutates the response after rendering has begun (a setResponseStatus() in a <script setup>, a cookie write during middleware, and so on) can't reach the client. Nuxt handles the common cases for you: routes with redirect, cache, isr, swr, noScripts, or ssr: false rules automatically fall back to the buffered renderer, and in development we log a warning naming any dropped mutations so nothing fails silently.
[!WARNING]
SSR streaming is experimental and off by default. It could be great for content-heavy routes where TTFB matters, but test it against your app's response-mutating logic (status codes, headers, cookies) before shipping it widely. The experimental features docs cover the fallback rules and caveats in detail.
This one I'm really happy about. Nuxt now has a stable error code system (#35429). Warnings and errors raised during build and at runtime now carry a stable code (like NUXT_E1001 or NUXT_B5001), a short explanation of why it happened, and a concrete fix to try.
Every code is greppable and bookmarkable, and the ones that need more than a one-line fix link straight to a dedicated docs page. For example, the classic "a composable was called outside a Nuxt context" now surfaces as NUXT_E1001 with the why/fix inline and a docs page explaining the context rules and how to use runWithContext().
To keep production output lean, the verbose why/fix text is stripped from production builds, leaving just the stable code.
This is the foundation for much better error messages across Nuxt, and we'll keep migrating existing warnings and errors onto it over the coming releases. If you've ever squinted at a cryptic Nuxt message, this is for you.
🎨useLayout Composable
There's a new useLayout composable for reading the layout that's been resolved for the current route (#35623). Previously there was no clean, reactive way to ask "which layout is this page using?" from within a component.
Nuxt now supports named views through a filename convention (#35123). If a parent page renders more than one <NuxtPage> outlet, you can give each outlet a name and provide a sibling page file for it using the name@view.vue convention:
Navigating to /parent/child renders child.vue into the default outlet and child@sidebar.vue into the sidebar outlet. This has actually been possible in Vue Router for a long time; this release wires it up to Nuxt's file-based routing.
[!NOTE] definePageMeta is read from the default route file only, and per-view rendering modes aren't supported (the parent page's mode applies to the default view).
You can now gate data fetching with a reactive enabled option (#33260). While enabled is false, every execution is blocked (the initial fetch, execute/refresh, and watch triggers), and if you flip it from true to false mid-flight, the in-flight request is cancelled without clearing your existing data.
<scriptsetuplang="ts">constquery=ref('')const{data}=awaituseFetch('/api/search',{query:{q:query},// Only fetch once the user has typed something
enabled:()=>query.value.length>2,})</script>
This is perfect for dependent or conditional queries, where you don't want to fire a request until some precondition is met. It pairs naturally with a getter or a ref, so it stays reactive.
When you use <NuxtLink> with the custom prop, Nuxt no longer attaches prefetch handlers for you, because it can't know how you've structured your markup. To make that ergonomic, the slot now exposes everything you need to wire prefetching up yourself (#34539):
You get prefetch to trigger it, prefetched to know whether it's already happened (great for a prefetched class), and shouldPrefetch to respect the user's connection and config.
Here's another one we'd love you to try. When you prefetch a link to a route with payload extraction, Nuxt already primes the destination's data and chunks. With the new opt-in experimental.prefetchPreloadTags (#35144), it also forwards the destination's <link rel="preload"> and modulepreload hints (whatever the page sets via useHead, or via modules like @nuxt/image's <NuxtImg preload>) into the current document, downgraded to rel="prefetch" so they don't compete with the resources the user is looking at right now.
The practical effect is that heavy above-the-fold assets on the next page (a hero image, a critical script) start downloading while the user is still on the current one, so the navigation feels instant. It's off by default while we gather feedback, so please give it a spin and tell us how it behaves on your app.
🌐import.meta.envName
The resolved Nuxt environment name is now available at runtime as import.meta.envName for both Vite and webpack/Rspack builds (#34844). This is the value set by --envName (or the resolved default), so you can branch on it in your app code:
Nuxt now publishes diagnostics-channel traces for its server-side subsystems (#35191). It's unopinionated: we emit nuxt.render, nuxt.island, nuxt.data, and nuxt.plugin channels following the untracing naming convention, and you can build OpenTelemetry (or anything else) on top. It works in Node, Deno, Bun, and Cloudflare Workers.
// nuxt.config.ts
exportdefaultdefineNuxtConfig({// Turn on Nuxt's own channels
tracingChannel: true,})
You can also enable it granularly. In Nuxt v5, there will be additional Nitro-level channels:
[!NOTE]
Channel names, payload shapes, and option keys may still change while the untracing registry settles.
📦 Dependency Upgrades
unhead v3
Nuxt's head management now runs on unhead v3 (#34793). It's smaller, uses a synchronous engine internally, and ships better type-safety for useHead out of the box. This also unblocks SSR streaming.
[!WARNING] unhead v3 introduces type-narrowing for useHead, which can be a breaking type change if you were relying on the looser v2 types. The runtime behaviour is compatible for the vast majority of apps, and promise input (deprecated in v2) is no longer supported. If you hit type errors after upgrading, they're almost always genuine tightening rather than regressions.
unctx v3
We've moved to unctx v3 (#35541), which resolves a class of long-standing async context issues (#33644). This is part of the composable-context reliability work that continues into v5.
And more
We've also updated magic-string to v1, Babel to v8, and pulled in the latest Rolldown across the board. Running nuxt upgrade --dedupe (see below) is the easiest way to pull these through cleanly.
🛠️ Nuxt CLI
This release bundles some improvements from @nuxt/cli v3.36 and v3.37:
nuxt module remove to uninstall a module and clean up its config (nuxt/cli#1306), the natural companion to nuxt module add.
Non-interactive nuxt init for scripting and CI (nuxt/cli#1341), plus it now respects the template's own package manager instead of prompting (nuxt/cli#1330).
Type-check onboarding: nuxt typecheck now offers to install vue-tsc and typescript for you if they're missing (nuxt/cli#1316).
Golar support for type-checking: nuxt typecheck can now use Golar as an alternative to vue-tsc (nuxt/cli#1362). It's picked up automatically if it's installed (or a golar.config.* file exists), and you can force a checker with --checker=vue-tsc or --checker=golar:
nuxt typecheck --checker=golar
Layer-aware dev server: the dev server now reloads on changes to local layer nuxt.config files (nuxt/cli#1345).
🧩 TypeScript Plugin and Named Layout Slots
Nuxt's experimental TypeScript plugin is powered by @dxup/nuxt, and this release bundles a newer version of it. If you haven't tried it, turning on experimental.typescriptPlugin gives you a set of editor niceties: renaming an auto-imported component updates every usage, plus go-to-definition for glob imports, Nitro routes, definePageMeta, runtimeConfig, and typed route names.
New in the bundled version is an opt-in runtime feature: named layout slots (KazariEX/dxup#20). You can write a top-level named-slot template in a page and have it forwarded into the matching named slot of the active layout. That lets a page inject content into its layout's slots, which file-based layouts don't otherwise allow.
<!--pages/about.vue--><scriptsetuplang="ts">definePageMeta({layout:'center'})</script><template><template#side="{ one }">This"{{ one }}"comesfromthelayoutslot.</template><div>Aboutpage</div></template>
As always, a lot of work has gone into making Nuxt faster and steadier.
One you can opt into today is the shared file watcher (#35143). Vite already runs a chokidar-based watcher, so Nuxt can piggy-back on it instead of spinning up a second one, using less memory and fewer file handles. This becomes the default with compatibilityVersion: 5, but you can turn it on now and let us know how it goes:
There are also some other good performance improvements that don't need to be opted into:
Faster dev startup: dev-only Nitro and pages work is now deferred and streamlined (#35381, #35383).
Leaner production builds: the island-renderer chunk is skipped entirely when you use no islands (#35456), and plugin handling is tree-shaken out of prod builds (#35278).
Improved island hashing: more stable island and key hashing, with getIslandHash and hashKey now exposed (#35583).
Concurrency and I/O wins across Kit path resolution and Nitro inline styles (#35511, #35514).
A couple of other nice quality-of-life fixes:
$fetch is now auto-imported in user code, which fixes edge cases with top-level $fetch.create under Rolldown's output format (#35581).
Nuxt now honours system HTTP_PROXY / HTTPS_PROXY environment variables in the builder environment (#35183).
HMR now works for defineNuxtComponent in JSX (#35620).
⚠️ Heads-Up Before Upgrading
As mentioned above, this release includes three major dependency bumps. For most apps they're transparent, but they're worth a moment of attention:
Rspack 2: if you use builder: 'rspack', the internals are now Rsbuild-based, so custom Rspack config may need review.
unhead v3: possible breaking type changes from stricter useHead typing.
⬆️ Upgrading
Our recommendation for upgrading is to run:
npx nuxt upgrade --dedupe
# or, if you are staying on the 3.x line
npx nuxt@latest upgrade --dedupe --channel=v3
This will deduplicate your lockfile and help ensure you pull in updates from the other dependencies Nuxt relies on, particularly across the unjs ecosystem (which matters more than usual this release, given the major bumps).
[!TIP]
Check out our upgrade guide if you're upgrading from an older version.
Thank you to all of the many contributors to this release. This was a big one, and it wouldn't happen without you. 💚
If you want to rebase/retry this PR, check this box
This PR contains the following updates:
| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [nuxt](https://nuxt.com) ([source](https://github.com/nuxt/nuxt/tree/HEAD/packages/nuxt)) | [`4.4.8` → `4.5.1`](https://renovatebot.com/diffs/npm/nuxt/4.4.8/4.5.1) |  |  |
---
### Nuxt: Unauthorized Component Instantiation via Server Island Props
[CVE-2026-71318](https://nvd.nist.gov/vuln/detail/CVE-2026-71318) / [GHSA-48hr-524c-v5w3](https://github.com/advisories/GHSA-48hr-524c-v5w3)
<details>
<summary>More information</summary>
#### Details
##### Impact
Nuxt server islands accept props via the `/__nuxt_island/` endpoint. When an application has a server island component that forwards props directly into Vue's dynamic component resolution (`<component :is>`, `resolveDynamicComponent`, or `h()`), an attacker can pass a plain string value (rather than a component definition) to instantiate any globally-registered Vue component or any native HTML element.
For example:
```json
{ "as": "SomeGlobalComponent" }
```
...resolves and renders `SomeGlobalComponent` if it is globally registered, even though the attacker should only be able to drive props for the island's declared component. Similarly, `{ "as": "iframe" }` renders an `<iframe>` element.
Unlike the primary RCE vector (GHSA-9473-5f9j-94wq), this does **not** require `vue.runtimeCompiler` to be enabled. A plain string prop is sufficient to trigger component resolution. The `template`/`render` key guard that addresses the RCE vector does not block plain string values.
Some component libraries expose a polymorphic `as` / `asChild` prop that forwards its value into `<component :is>`; `@nuxt/ui` (via `reka-ui`) is a widely used example. An application is affected if such a component receives the attacker-controlled value inside a server island. Note this does not require explicit prop forwarding: island props the island component does not declare fall through as attributes onto its single root element, so an island whose root is a `reka-ui` / `@nuxt/ui` component receives the attacker's `as` value implicitly. Unlike the RCE vector, no `vue.runtimeCompiler` is required, which makes this vector reachable in more configurations. These libraries are not themselves vulnerable; they are noted only because they commonly provide the dynamic-component sink. Installing `@nuxt/ui` does not by itself register any component as a server island: the application must define the island (a `.server.vue` file).
##### Mitigating factors
- Exploitation requires a server island component that puts an attacker-controlled value onto a dynamic-component path (`<component :is>`, `resolveDynamicComponent`, `h()`, or a polymorphic `as` / `asChild` prop), either explicitly or via attribute fallthrough when the island's root is such a component.
- Reachable components are limited to what is actually in the island app's global registry. In a default pages-enabled app that is `RouterView` and `RouterLink` (both registered globally by the pages router plugin, which runs even in component islands), plus any `components/global/` component and any component a module registers globally. `RouterLink` in particular renders an attacker-influenced `<a>` (and, because an island that declares no props forwards all props, `to` and other `RouterLink` props ride the same fallthrough). Vue built-ins (`Transition`, `KeepAlive`, `Teleport`, `Suspense`) and Nuxt auto-imports (`ClientOnly`, `NuxtLink`, `NuxtPage`, etc.) are NOT in the island app's global registry and cannot be resolved this way; an unresolved name instead renders as a native HTML element (the element-injection half of this issue).
- Declaring the props an island accepts, or setting `inheritAttrs: false` on it, prevents an undeclared `as` from falling through to a polymorphic root and neutralizes this vector.
- Arbitrary JavaScript execution is not possible through this vector (no `template`/`render` compilation).
- Island component names are constrained to the build-time component registry; an attacker cannot resolve arbitrary components.
##### Affected versions
Nuxt `>=3.1.0 <3.21.10` and `>=4.0.0 <4.5.1`, with component islands active. The island prop-forwarding behavior has existed since server islands were introduced in v3.1.0, and this vector does not depend on `vue.runtimeCompiler`. Nuxt 2 is not affected.
Note the patch (below) closes the implicit attribute-fallthrough path, which is the majority case. An island that *explicitly* forwards an untrusted prop into dynamic component resolution (or forwards it under a prop name other than `as`) remains the application's responsibility in every version; see Workarounds.
##### Patches
Fixed in `nuxt@4.5.1` and `nuxt@3.21.10`. Patched releases reject a top-level `as` island prop (HTTP 400 at the `/__nuxt_island/` endpoint). This closes the implicit path: island props an island does not declare fall through as attributes onto its single root, so a top-level `as` would otherwise reach a polymorphic root component's `as` prop (the `reka-ui` / `@nuxt/ui` convention) and drive dynamic component resolution without the author binding it. Rejecting the top-level `as` prop blocks that fallthrough while leaving nested data and other prop names untouched.
The framework deliberately does not attempt to block every case: it cannot safely tell a string used as data from one used as a component selector, and it has no island-local hook into Vue's `h()` or `resolveDynamicComponent()`. An island that explicitly forwards an untrusted value into `<component :is>` / `h()` / `resolveDynamicComponent()`, or that forwards it under a different polymorphic prop name, is therefore not covered by the patch and must follow the guidance below. The Nuxt documentation now warns against this.
##### Workarounds
Upgrade to `nuxt@4.5.1` or `nuxt@3.21.10`. That upgrade also removes the related object-prop RCE (GHSA-9473-5f9j-94wq). In addition, in any version:
1. Do not forward island props into `<component :is>`, `resolveDynamicComponent`, or `h()`. Map an untrusted discriminator through a closed allowlist of imported component definitions instead of passing the raw prop value.
2. Declare the props an island accepts, or set `inheritAttrs: false` on it, so request input cannot fall through to a polymorphic root component.
3. Avoid registering sensitive components globally that could leak information if instantiated by an attacker.
#### Severity
- CVSS Score: 4.8 / 10 (Medium)
- Vector String: `CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N`
#### References
- [https://github.com/nuxt/nuxt/security/advisories/GHSA-48hr-524c-v5w3](https://github.com/nuxt/nuxt/security/advisories/GHSA-48hr-524c-v5w3)
- [https://github.com/nuxt/nuxt](https://github.com/nuxt/nuxt)
- [https://github.com/nuxt/nuxt/releases/tag/v3.21.10](https://github.com/nuxt/nuxt/releases/tag/v3.21.10)
- [https://github.com/nuxt/nuxt/releases/tag/v4.5.1](https://github.com/nuxt/nuxt/releases/tag/v4.5.1)
This data is provided by [OSV](https://osv.dev/vulnerability/GHSA-48hr-524c-v5w3) and the [GitHub Advisory Database](https://github.com/github/advisory-database) ([CC-BY 4.0](https://github.com/github/advisory-database/blob/main/LICENSE.md)).
</details>
---
### Nuxt dev server discloses project root and workspace UUID via the Chrome DevTools workspace endpoint
[CVE-2026-72744](https://nvd.nist.gov/vuln/detail/CVE-2026-72744) / [GHSA-7c4v-fwgw-9rf7](https://github.com/advisories/GHSA-7c4v-fwgw-9rf7)
<details>
<summary>More information</summary>
#### Details
##### Impact
When a Nuxt dev server is bound to a network-reachable interface (for example `nuxt dev --host` for on-device testing), the default-enabled Chrome DevTools workspace endpoint `GET /.well-known/appspecific/com.chrome.devtools.json` returns the absolute project root (`workspace.root`, i.e. `rootDir`) and a persistent per-project workspace UUID.
`GHSA-rq7w-g337-39qq` added a gate (`isLocalDevRequest`) intended to restrict this endpoint to local requests, but that gate is header-based: it trusts request metadata rather than the connected peer address. A request with no `Sec-Fetch-Site`, `Origin`, and `Referer` headers (normal for a non-browser client such as `curl`) is treated as local, and the `Host` allow-list is compared against the attacker-supplied `Host` header. As a result, any unauthenticated host that can reach the dev server on the LAN can retrieve the project's absolute filesystem path and workspace UUID, for example with `curl -H 'Host: localhost' http://<dev-host-lan-ip>:3000/.well-known/appspecific/com.chrome.devtools.json`.
This is information disclosure only: there is no file read, file write, or code execution reachable from the endpoint. It requires the dev server to be reachable beyond loopback and `experimental.chromeDevtoolsProjectSettings` to be enabled (it defaults to `true`). Production builds are unaffected, because the endpoint is registered only as a development handler.
##### Patches
Fixed in `nuxt@4.5.1` and `nuxt@3.21.10`. The endpoint now additionally requires the connected TCP peer to be a loopback address, verified from the socket rather than from request headers, so a non-loopback LAN client is rejected regardless of the `Host`, `Origin`, `Referer`, or `Sec-Fetch-*` headers it sends. The shared header-based check is left unchanged, so the CSRF / same-origin behaviour that other dev handlers rely on is preserved. After this fix, Chrome DevTools workspace auto-mapping only works when the browser reaches the dev server over loopback (`localhost` / `127.0.0.1` / `::1`), which matches the feature's intent (the browser and dev server sharing a filesystem).
##### Workarounds
- Do not bind the dev server to a non-loopback interface on an untrusted network, or restrict access to the dev port with a firewall.
- Disable the feature by setting `experimental.chromeDevtoolsProjectSettings: false` in `nuxt.config`.
#### Severity
- CVSS Score: 5.3 / 10 (Medium)
- Vector String: `CVSS:4.0/AV:A/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N`
#### References
- [https://github.com/nuxt/nuxt/security/advisories/GHSA-7c4v-fwgw-9rf7](https://github.com/nuxt/nuxt/security/advisories/GHSA-7c4v-fwgw-9rf7)
- [https://nvd.nist.gov/vuln/detail/CVE-2026-72744](https://nvd.nist.gov/vuln/detail/CVE-2026-72744)
- [https://github.com/nuxt/nuxt/commit/00f71bb6517abff67257c8ea1fcdc777b938b68d](https://github.com/nuxt/nuxt/commit/00f71bb6517abff67257c8ea1fcdc777b938b68d)
- [https://github.com/nuxt/nuxt/commit/e30c611ea03240f341fe784ab1711aa6424da2fa](https://github.com/nuxt/nuxt/commit/e30c611ea03240f341fe784ab1711aa6424da2fa)
- [https://github.com/nuxt/nuxt](https://github.com/nuxt/nuxt)
- [https://github.com/nuxt/nuxt/releases/tag/v3.21.10](https://github.com/nuxt/nuxt/releases/tag/v3.21.10)
- [https://github.com/nuxt/nuxt/releases/tag/v4.5.1](https://github.com/nuxt/nuxt/releases/tag/v4.5.1)
- [https://www.vulncheck.com/advisories/nuxt-before-information-disclosure-via-chrome-devtools](https://www.vulncheck.com/advisories/nuxt-before-information-disclosure-via-chrome-devtools)
This data is provided by [OSV](https://osv.dev/vulnerability/GHSA-7c4v-fwgw-9rf7) and the [GitHub Advisory Database](https://github.com/github/advisory-database) ([CC-BY 4.0](https://github.com/github/advisory-database/blob/main/LICENSE.md)).
</details>
---
### Nuxt: Server-Side Remote Code Execution via Runtime Template Injection in Nuxt Server Island Props
[CVE-2026-71320](https://nvd.nist.gov/vuln/detail/CVE-2026-71320) / [GHSA-9473-5f9j-94wq](https://github.com/advisories/GHSA-9473-5f9j-94wq)
<details>
<summary>More information</summary>
#### Details
##### Impact
Nuxt server islands accept props via the `/__nuxt_island/` endpoint. When `vue.runtimeCompiler: true` is enabled (off by default) and the application has a server island component that forwards props into Vue's dynamic component resolution (`<component :is>`, `resolveDynamicComponent`, or `h()`), an attacker can inject a `template` key into the island props to achieve server-side remote code execution in the Nitro process.
```json
{ "as": { "template": "<attacker-controlled>" } }
```
Vue's runtime template compiler compiles and executes the attacker-controlled `template` in the server process. The same primitive also works on the client side when the runtime compiler is active there, though the server-side path is the primary concern.
Some component libraries expose a polymorphic `as` / `asChild` prop that forwards its value into `<component :is>`; `@nuxt/ui` (via `reka-ui`) is a widely used example. An application is affected if such a component receives the attacker-controlled value, provided `vue.runtimeCompiler` is also enabled. Note this does not require the island author to explicitly forward a prop: island props that the island component does not declare fall through as attributes onto its single root element (standard Vue attribute inheritance), so an island whose root is a polymorphic component receives the attacker's `as` value implicitly. These libraries are not themselves vulnerable; they are noted only because they commonly provide the dynamic-component sink.
##### Common configurations that satisfy the preconditions
The flaw is in Nuxt core. The library below is not itself vulnerable; it is noted because it commonly provides the dynamic-component sink an application might inadvertently expose.
- **`@nuxt/ui`** (via its underlying **`reka-ui`** primitives) exposes a polymorphic `as` / `asChild` prop that is forwarded into Vue's dynamic-component resolution. Installing `@nuxt/ui` does not by itself register any component as a server island. Exploitation requires the application to define an island component (a `.server.vue` file) whose rendered output puts the attacker-controlled value on such a component's `as` / `asChild` prop; with `vue.runtimeCompiler: true`, the attacker-controlled `template` is then compiled and executed. Because undeclared island props fall through as attributes to the single root, this can happen without any explicit binding: an island whose root is a `reka-ui` / `@nuxt/ui` component is enough. An example vulnerable island (the `as` value falls through to `UButton`, no explicit forwarding needed):
```vue
<!-- components/MyWidget.server.vue -->
<template>
<UButton>Save</UButton>
</template>
```
The island URL hash (`/__nuxt_island/<Name>_<hash>.json`) is a deterministic (unsalted) content hash, not an authentication token. It provides integrity relative to the URL but is not a security boundary: an attacker who knows the component name and desired props can compute a valid hash.
##### Mitigating factors
- `vue.runtimeCompiler` is **off by default** in Nuxt. The vast majority of Nuxt applications are not affected.
- Exploitation requires a **second precondition**: the application must have a server island component that puts an attacker-controlled value onto a dynamic-component path (`<component :is>`, `resolveDynamicComponent`, `h()`, or a polymorphic `as` / `asChild` prop). This can occur explicitly or via attribute fallthrough when the island's root is such a component.
- SSG / static deployments are largely unreachable via this vector (no server process to exploit).
- Island component names are constrained to the build-time component registry; an attacker cannot resolve arbitrary components.
##### Affected versions
Nuxt `>=3.4.0 <3.21.10` and `>=4.0.0 <4.5.1`, and only when `vue.runtimeCompiler: true` and component islands are active. Earlier versions did not allow the Vue compiler to be enabled in the server bundle (the compiler dependencies have been mock-aliased on the server since v3.0.0-rc.1), so the runtime-compilation path is not reachable. Nuxt 2 is not affected (no server islands).
##### Patches
When `vue.runtimeCompiler` is enabled, island requests whose decoded props contain a `template` key at any depth are rejected with an HTTP 400 and a diagnostic suggesting the author rename the prop or disable the runtime compiler. The guard is gated on the runtime compiler being enabled, so the default configuration (compiler off) is unaffected and legitimate props that merely contain a `template` field (for example CMS content) continue to render. A `render` key is not rejected: island props arrive as JSON, so a `render` value can only be an inert string, which Vue ignores.
Fixed in `nuxt@4.5.1` and backported to `nuxt@3.21.10`.
##### Workarounds
Upgrade to `nuxt@4.5.1` or `nuxt@3.21.10`. If you cannot immediately upgrade, you can mitigate by:
1. Ensure `vue.runtimeCompiler` is set to `false` (the default).
2. Do not forward island props into `<component :is>`, `resolveDynamicComponent`, or `h()` without sanitization.
3. As a defense-in-depth measure, deploy a WAF rule on `/__nuxt_island/` that URL-decodes and JSON-parses the `props` value and blocks any object property named `template` or `render` in the decoded island props, inspecting both query and body for all methods. Note: this only covers direct and browser-originated island requests; initial-SSR internal island renders do not transit the edge and a WAF alone does not close the vector.
#### Severity
- CVSS Score: 8.1 / 10 (High)
- Vector String: `CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H`
#### References
- [https://github.com/nuxt/nuxt/security/advisories/GHSA-9473-5f9j-94wq](https://github.com/nuxt/nuxt/security/advisories/GHSA-9473-5f9j-94wq)
- [https://github.com/nuxt/nuxt/commit/5b60017f7f1d5e9384cadf1d6c580b99d583c418](https://github.com/nuxt/nuxt/commit/5b60017f7f1d5e9384cadf1d6c580b99d583c418)
- [https://github.com/nuxt/nuxt/commit/ee6c846338f4eb75801815dda86df1f494725859](https://github.com/nuxt/nuxt/commit/ee6c846338f4eb75801815dda86df1f494725859)
- [https://github.com/nuxt/nuxt](https://github.com/nuxt/nuxt)
- [https://github.com/nuxt/nuxt/releases/tag/v3.21.10](https://github.com/nuxt/nuxt/releases/tag/v3.21.10)
- [https://github.com/nuxt/nuxt/releases/tag/v4.5.1](https://github.com/nuxt/nuxt/releases/tag/v4.5.1)
This data is provided by [OSV](https://osv.dev/vulnerability/GHSA-9473-5f9j-94wq) and the [GitHub Advisory Database](https://github.com/github/advisory-database) ([CC-BY 4.0](https://github.com/github/advisory-database/blob/main/LICENSE.md)).
</details>
---
### Nuxt: Unauthenticated CPU exhaustion parsing and hashing the Nuxt island endpoint body before hash validation
[CVE-2026-71321](https://nvd.nist.gov/vuln/detail/CVE-2026-71321) / [GHSA-9pgf-384g-p7mv](https://github.com/advisories/GHSA-9pgf-384g-p7mv)
<details>
<summary>More information</summary>
#### Details
##### Impact
The internal island renderer endpoint (`/__nuxt_island/...`) decodes and hashes attacker-controlled request input before it validates the URL-resident hash. An unauthenticated `POST /__nuxt_island/<name>_<anything>.json` with a large JSON body (for example ~4.6 MB / 150k keys) is fully read, `destr`-parsed, and run through `ohash` before the request is rejected with a 400. Because Nitro runs on a single event loop, this both wastes CPU on the doomed request and delays every concurrent request. A low request rate is enough to degrade or stall the server. No valid hash and no authentication are required.
##### Patches
Fixed in `nuxt@4.5.1` and `nuxt@3.21.10`. The island handler now enforces a raw body-size cap (`413`) and a JSON nesting-depth cap (`400`) before parsing or hashing, so oversized or deeply nested input is rejected cheaply.
##### Workarounds
Put a small request-body limit in front of `/__nuxt_island/` at your reverse proxy / edge (islands legitimately send only a compact props payload), or disable server components if unused.
#### Severity
- CVSS Score: 7.5 / 10 (High)
- Vector String: `CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H`
#### References
- [https://github.com/nuxt/nuxt/security/advisories/GHSA-9pgf-384g-p7mv](https://github.com/nuxt/nuxt/security/advisories/GHSA-9pgf-384g-p7mv)
- [https://github.com/nuxt/nuxt/commit/4e35ae9babd94be53246e31200232d48438bb34e](https://github.com/nuxt/nuxt/commit/4e35ae9babd94be53246e31200232d48438bb34e)
- [https://github.com/nuxt/nuxt/commit/668cdfdfda41849ed11c1ee5e2067a11fc103b22](https://github.com/nuxt/nuxt/commit/668cdfdfda41849ed11c1ee5e2067a11fc103b22)
- [https://github.com/nuxt/nuxt](https://github.com/nuxt/nuxt)
- [https://github.com/nuxt/nuxt/releases/tag/v3.21.10](https://github.com/nuxt/nuxt/releases/tag/v3.21.10)
- [https://github.com/nuxt/nuxt/releases/tag/v4.5.1](https://github.com/nuxt/nuxt/releases/tag/v4.5.1)
This data is provided by [OSV](https://osv.dev/vulnerability/GHSA-9pgf-384g-p7mv) and the [GitHub Advisory Database](https://github.com/github/advisory-database) ([CC-BY 4.0](https://github.com/github/advisory-database/blob/main/LICENSE.md)).
</details>
---
### Nuxt: Unauthenticated out-of-memory crash via unbounded v-for expansion in island rendering
[CVE-2026-71314](https://nvd.nist.gov/vuln/detail/CVE-2026-71314) / [GHSA-hxcr-hm88-mpq6](https://github.com/advisories/GHSA-hxcr-hm88-mpq6)
<details>
<summary>More information</summary>
#### Details
##### Impact
An unauthenticated attacker can crash a Nuxt server that renders any island / server component containing a `v-for` over a prop (for example `v-for="n in count"` or a `<slot v-for>`). Because the island URL hash is a non-secret digest of the request, the attacker can compute a valid hash for arbitrary props and send the iterated prop as a large integer. The server then expands the `v-for` to that many nodes during SSR, allocating memory proportional to the attacker's number. Reporter figures: `count=8000000` produced a 142.9 MB response; `count=40000000` (and `items=4000000` on a slot list) produced an out-of-memory crash of the worker from a single ~130-byte request. Both the plain `v-for` path (Vue's `ssrRenderList`) and the slot path (`vforToArray`) are affected.
##### Patches
Fixed in `nuxt@4.5.1` and `nuxt@3.21.10`. Island/server-component `v-for` sources are now clamped to a maximum iteration count (`MAX_VFOR_LENGTH = 100000`) at the render boundary, covering the plain path, the `<slot v-for>` element, and the `vforToArray` slot-props helper. Combined with the body-size cap (GHSA-9pgf-384g-p7mv), a single island render can no longer allocate without bound regardless of which `v-for` path is used or whether the prop arrives as an integer or an array.
##### Workarounds
Avoid `v-for` directly over an unclamped prop in server components, or clamp the count in the component (`v-for="n in Math.min(count, 1000)"`). A body-size limit in front of `/__nuxt_island/` only mitigates array-shaped inputs, not the integer-amplification case.
##### References
- Bound helper: `packages/nuxt/src/app/components/vfor.ts`
- Transform: `packages/nuxt/src/components/plugins/islands-transform.ts`
- Slot helper: `packages/nuxt/src/app/components/utils.ts` (`vforToArray`)
#### Severity
- CVSS Score: 7.5 / 10 (High)
- Vector String: `CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H`
#### References
- [https://github.com/nuxt/nuxt/security/advisories/GHSA-hxcr-hm88-mpq6](https://github.com/nuxt/nuxt/security/advisories/GHSA-hxcr-hm88-mpq6)
- [https://github.com/nuxt/nuxt/commit/4e35ae9babd94be53246e31200232d48438bb34e](https://github.com/nuxt/nuxt/commit/4e35ae9babd94be53246e31200232d48438bb34e)
- [https://github.com/nuxt/nuxt/commit/668cdfdfda41849ed11c1ee5e2067a11fc103b22](https://github.com/nuxt/nuxt/commit/668cdfdfda41849ed11c1ee5e2067a11fc103b22)
- [https://github.com/nuxt/nuxt](https://github.com/nuxt/nuxt)
- [https://github.com/nuxt/nuxt/releases/tag/v3.21.10](https://github.com/nuxt/nuxt/releases/tag/v3.21.10)
- [https://github.com/nuxt/nuxt/releases/tag/v4.5.1](https://github.com/nuxt/nuxt/releases/tag/v4.5.1)
This data is provided by [OSV](https://osv.dev/vulnerability/GHSA-hxcr-hm88-mpq6) and the [GitHub Advisory Database](https://github.com/github/advisory-database) ([CC-BY 4.0](https://github.com/github/advisory-database/blob/main/LICENSE.md)).
</details>
---
### Nuxt route rules silently dropped for mixed-case paths, bypassing appMiddleware auth gates (incomplete fix for CVE-2026-53721)
[CVE-2026-71315](https://nvd.nist.gov/vuln/detail/CVE-2026-71315) / [GHSA-hxvh-4h3w-prp9](https://github.com/advisories/GHSA-hxvh-4h3w-prp9)
<details>
<summary>More information</summary>
#### Details
##### Impact
Nuxt matches route rules case-insensitively by default (mirroring vue-router's default `sensitive: false` routing). The fix for GHSA-mm7m-92g8-7m47 / CVE-2026-53721 lowercased the *lookup* path before matching route rules, but the route-rule *keys* compiled into the matcher were left verbatim. As a result, any route rule whose key contains an uppercase character (for example `/Admin`, `/Dashboard/**`, or the rules Nuxt derives from PascalCase/camelCase page files such as `pages/Admin.vue`) never matches, because every lookup is folded to lowercase while the key stays mixed-case.
vue-router still serves the page case-insensitively, so the page renders with none of its Nuxt route-rule protections applied. The most serious consequence is an authorization bypass: an `appMiddleware` rule used as an auth gate (`routeRules: { '/Admin/dashboard': { appMiddleware: 'auth' } }`) is dropped, and `/Admin/dashboard`, `/admin/dashboard`, and `/ADMIN/dashboard` all render the protected page (and its SSR-fetched data) to an unauthenticated visitor instead of redirecting to login. The same gap drops Nuxt's other app-side route-rule behaviours for mixed-case keys, including the client redirect middleware, the app-side `ssr: false` decision, `prerender`, and payload handling.
##### Patches
Fixed in `nuxt@4.5.1` (4.x) and `nuxt@3.21.10` (3.x). The route-rule matcher now case-folds the compiled keys the same way it folds the lookup path, so key and lookup normalisation are symmetric. Both sides are gated on `router.options.sensitive`: with `sensitive: true` (case-sensitive routing) configured casing is preserved on both sides.
Scope note: server-emitted per-route `headers`, server `redirect`, and `proxy` are matched by Nitro's own case-sensitive route-rule matcher, not by Nuxt's app-level matcher. They are unchanged by this advisory. The fix covers the app-level protections Nuxt owns (`appMiddleware`, `appLayout`, the client redirect middleware, the app `ssr` decision, `prerender`, and payload).
##### Workarounds
If you cannot upgrade immediately, any one of:
- Key all `routeRules` (and name your page files) in lowercase, so the keys already match the folded lookup path.
- Set `router: { options: { sensitive: true } }` so routing and route-rule matching are both case-sensitive and exact (requests must then use the exact casing).
- Enforce the sensitive protections server-side independently of route rules (for example a server middleware that checks auth), which does not rely on case-insensitive route-rule matching.
#### Severity
- CVSS Score: 8.2 / 10 (High)
- Vector String: `CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:N`
#### References
- [https://github.com/nuxt/nuxt/security/advisories/GHSA-hxvh-4h3w-prp9](https://github.com/nuxt/nuxt/security/advisories/GHSA-hxvh-4h3w-prp9)
- [https://github.com/nuxt/nuxt/commit/619963309e082190bac4a26b05f2dd155b039b81](https://github.com/nuxt/nuxt/commit/619963309e082190bac4a26b05f2dd155b039b81)
- [https://github.com/nuxt/nuxt/commit/ad624a75ad2d215f43633f6b40be346a7194d34d](https://github.com/nuxt/nuxt/commit/ad624a75ad2d215f43633f6b40be346a7194d34d)
- [https://github.com/nuxt/nuxt](https://github.com/nuxt/nuxt)
- [https://github.com/nuxt/nuxt/releases/tag/v3.21.10](https://github.com/nuxt/nuxt/releases/tag/v3.21.10)
- [https://github.com/nuxt/nuxt/releases/tag/v4.5.1](https://github.com/nuxt/nuxt/releases/tag/v4.5.1)
This data is provided by [OSV](https://osv.dev/vulnerability/GHSA-hxvh-4h3w-prp9) and the [GitHub Advisory Database](https://github.com/github/advisory-database) ([CC-BY 4.0](https://github.com/github/advisory-database/blob/main/LICENSE.md)).
</details>
---
### Nuxt runtime payload cache discloses another user's SSR data across users and to unauthenticated clients
[CVE-2026-71316](https://nvd.nist.gov/vuln/detail/CVE-2026-71316) / [GHSA-wm8w-6qjm-cv43](https://github.com/advisories/GHSA-wm8w-6qjm-cv43)
<details>
<summary>More information</summary>
#### Details
##### Impact
When a page is covered by `routeRules` `cache` / `swr` / `isr`, Nuxt enables runtime payload extraction and serves `/<page>/_payload.json`. On affected versions the renderer stored the SSR payload in the shared `cache:nuxt:payload` storage under a path-only key (no cookie, `authorization`, or `cache.varies` dimension) and, on a later payload request, returned the cached entry before route middleware / page guards ran again.
As a result, once any authenticated user warms a protected, cached page, a subsequent `GET /<page>/_payload.json` from an unauthenticated client or a different authenticated user receives the first user's payload: the full SSR data for that route, including anything loaded via `useFetch` / `useAsyncData` (for example `/api/me`: profile, tenant, billing, token-like values). The HTML response stays correctly varied and protected; only the extracted payload leaks. Both cross-user (A warms, B receives A) and unauthenticated disclosure are exploitable. `cache.varies` does not mitigate it, because the payload cache ignores `varies`.
Introduced when runtime payload extraction landed for cached routes (#​34410); the regression is specific to the 4.x line, where the runtime `cache:nuxt:payload` storage was added and the `import.meta.prerender` gate on the payload-cache read/writes was dropped. The 3.x line shipped the same feature with the gate intact and is not affected.
##### Patches
Fixed in `nuxt@4.5.1`. Runtime payload-cache reads and writes are again confined to prerendering (`import.meta.prerender`); at runtime, `/<page>/_payload.json` follows the normal render path so route middleware, `routeRules.appMiddleware`, and page guards run for the current request. `main` / v5 and the `3.x` line already had this property, so 3.x is not affected.
##### Workarounds
- Set `experimental.payloadExtraction: false` (reporter-validated): the standalone `/_payload.json` endpoint returns 404 and the page still serves a 200 with an inline payload.
- Do not apply `cache` / `swr` / `isr` to authenticated pages that render user-specific SSR data.
- As defense-in-depth, require authentication for `/**/_payload.json` at a proxy / CDN.
- After upgrading, purge any CDN / platform cache that may already hold protected payloads.
#### Severity
- CVSS Score: 7.5 / 10 (High)
- Vector String: `CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N`
#### References
- [https://github.com/nuxt/nuxt/security/advisories/GHSA-wm8w-6qjm-cv43](https://github.com/nuxt/nuxt/security/advisories/GHSA-wm8w-6qjm-cv43)
- [https://github.com/nuxt/nuxt/commit/ac9b41a36b62296a117862254ee7d2b21a2a5203](https://github.com/nuxt/nuxt/commit/ac9b41a36b62296a117862254ee7d2b21a2a5203)
- [https://github.com/nuxt/nuxt](https://github.com/nuxt/nuxt)
- [https://github.com/nuxt/nuxt/releases/tag/v4.5.1](https://github.com/nuxt/nuxt/releases/tag/v4.5.1)
This data is provided by [OSV](https://osv.dev/vulnerability/GHSA-wm8w-6qjm-cv43) and the [GitHub Advisory Database](https://github.com/github/advisory-database) ([CC-BY 4.0](https://github.com/github/advisory-database/blob/main/LICENSE.md)).
</details>
---
### Release Notes
<details>
<summary>nuxt/nuxt (nuxt)</summary>
### [`v4.5.1`](https://github.com/nuxt/nuxt/releases/tag/v4.5.1)
[Compare Source](https://github.com/nuxt/nuxt/compare/v4.5.0...v4.5.1)
> ⚠️ **This is a security release.** We recommend upgrading as soon as possible with `npx nuxt upgrade --dedupe`.
It fixes server-side RCE and unauthorized component instantiation via server island props, a route rule authorization bypass, server component DoS, cross-user payload disclosure on cached pages, and dev server path disclosure. Refreshing your lockfile also pulls in `@nuxt/devtools@3.3.1`, which fixes a separate critical development-only RCE.
If you already upgraded for the earlier route rule advisory ([CVE-2026-53721](https://github.com/nuxt/nuxt/security/advisories/GHSA-mm7m-92g8-7m47)), you still need this release: one of the fixes addresses a regression introduced by that fix.
If you use the `cache`, `swr` or `isr` route rules, purge any CDN or edge cache after upgrading; a leaked `_payload.json` may already be cached upstream.
Full details: [Nuxt Security Patch Releases](https://nuxt.com/blog/v4-5-security) and [GitHub Security Advisories](https://github.com/nuxt/nuxt/security/advisories).
#### 👉 Changelog
[compare changes](https://github.com/nuxt/nuxt/compare/v4.5.0...v4.5.1)
##### 🔥 Performance
- **nitro:** Replace island teleports in a single html pass ([#​35515](https://github.com/nuxt/nuxt/pull/35515))
- **nuxt:** Add `vue.optionsApi` and disable it for v5+ ([#​35791](https://github.com/nuxt/nuxt/pull/35791))
- **nuxt:** Without pages, skip client plugins that require routing ([#​35794](https://github.com/nuxt/nuxt/pull/35794))
- **nuxt:** Skip payload revival plugin when `ssr: false` ([#​35782](https://github.com/nuxt/nuxt/pull/35782))
##### 🩹 Fixes
- **nitro:** Read rspack dev output fs lazily for server entry ([#​35740](https://github.com/nuxt/nuxt/pull/35740))
- **rspack,webpack:** Resolve loaders and runtime deps from nuxt dirs ([#​35568](https://github.com/nuxt/nuxt/pull/35568))
- **nuxt:** Return global route for `useRoute` in detached effect scope ([#​35659](https://github.com/nuxt/nuxt/pull/35659))
- **nuxt:** Ignore custom `name` or `path` when reusing an existing page in `pages:extend` ([#​35661](https://github.com/nuxt/nuxt/pull/35661))
- **nuxt:** Render client components in nested server components ([#​35669](https://github.com/nuxt/nuxt/pull/35669))
- **nuxt:** Preserve explicit `useFetch` method inference ([#​35671](https://github.com/nuxt/nuxt/pull/35671))
- **nuxt:** Revalidate cached route payloads instead of using `force-cache` ([#​35672](https://github.com/nuxt/nuxt/pull/35672))
- **nuxt:** Correct default export detection in plugin metadata ([#​35676](https://github.com/nuxt/nuxt/pull/35676))
- **nuxt:** Clear hide/reset timeouts in set() ([#​35534](https://github.com/nuxt/nuxt/pull/35534))
- **kit,nuxt,rspack,schema,webpack:** Add `.mts` file extension in resolver ([#​33845](https://github.com/nuxt/nuxt/pull/33845))
- **nuxt:** Preserve trailing slash in NuxtLink href when unset ([#​35501](https://github.com/nuxt/nuxt/pull/35501))
- **nuxt:** Don't cross-pollute useAsyncData cache on reactive key change ([#​35656](https://github.com/nuxt/nuxt/pull/35656))
- **nuxt:** Filter plugin dependencies by build target ([#​35682](https://github.com/nuxt/nuxt/pull/35682))
- **nuxt:** Reload real page module on HMR of JSX render-function pages ([#​35678](https://github.com/nuxt/nuxt/pull/35678))
- **nuxt:** Resolve `@unhead/vue/*` from nuxt's dependency tree ([#​35690](https://github.com/nuxt/nuxt/pull/35690))
- **kit:** Surface module load errors instead of masking as missing ([#​35497](https://github.com/nuxt/nuxt/pull/35497))
- **nuxt:** Type auto-imported `$fetch` with nitro's `$Fetch` ([#​35704](https://github.com/nuxt/nuxt/pull/35704))
- **nuxt:** Don't reference app config sources in shared and node tsconfigs ([#​35673](https://github.com/nuxt/nuxt/pull/35673))
- **vite:** Resolve SSR inlined CSS module class name mismatch ([#​35610](https://github.com/nuxt/nuxt/pull/35610))
- **nuxt:** Generate layout types even when pages module is disabled ([#​35717](https://github.com/nuxt/nuxt/pull/35717))
- **nitro:** Skip resource hints for stylesheets already rendered as blocking links ([#​35691](https://github.com/nuxt/nuxt/pull/35691))
- **kit:** Dedupe layers that are both auto-scanned and explicitly extended ([#​35712](https://github.com/nuxt/nuxt/pull/35712))
- **nuxt:** Don't apply scroll behaviour after a subsequent nav ([#​35719](https://github.com/nuxt/nuxt/pull/35719))
- **vite:** Ensure server sourcemap-preserver plugin actually runs ([#​35680](https://github.com/nuxt/nuxt/pull/35680))
- **vite:** Preserve css suffix when extracting ssr inline styles ([#​35714](https://github.com/nuxt/nuxt/pull/35714))
- **nuxt:** Watch external component directories in development ([#​35652](https://github.com/nuxt/nuxt/pull/35652))
- **nuxt:** Don't exclude client entry module from style extraction ([#​35720](https://github.com/nuxt/nuxt/pull/35720))
- **nuxt:** Amend cleanup command in NUXT\_B7014 error message ([#​35735](https://github.com/nuxt/nuxt/pull/35735))
- **nuxt:** Only pull in `vue-router` when there are island pages ([#​35739](https://github.com/nuxt/nuxt/pull/35739))
- **vite:** Suppress external warnings for internal vite-node paths ([#​35744](https://github.com/nuxt/nuxt/pull/35744))
- **nuxt:** Use scope-aware oxc parser for auto-imports ([#​35743](https://github.com/nuxt/nuxt/pull/35743))
- **nuxt:** Sync layout meta during middleware on SSR ([#​35633](https://github.com/nuxt/nuxt/pull/35633))
- **nitro:** Add alias for `h3` that pins it to the version nuxt depends on ([#​35774](https://github.com/nuxt/nuxt/pull/35774))
- **nuxt:** Preserve query params in cached payload extraction ([#​35696](https://github.com/nuxt/nuxt/pull/35696))
- **nuxt:** Mirror runtime route tree in generated typed-router types ([#​35788](https://github.com/nuxt/nuxt/pull/35788))
- **nuxt:** Warn when an imports preset `from` cannot be resolved ([#​35799](https://github.com/nuxt/nuxt/pull/35799))
- **kit:** Avoid mutating layer configs when resolving options ([#​35729](https://github.com/nuxt/nuxt/pull/35729))
- **nuxt:** Convert inline route rules exactly or drop with a warning ([#​35455](https://github.com/nuxt/nuxt/pull/35455))
- **nitro,nuxt,vite:** Dedupe and normalise global css links in dev ([#​35834](https://github.com/nuxt/nuxt/pull/35834))
- **vite:** Register template HMR plugin on dev servers ([929c6c138](https://github.com/nuxt/nuxt/commit/929c6c138))
- **nuxt:** Remove dev error overlay when error is cleared ([#​35821](https://github.com/nuxt/nuxt/pull/35821))
- **rspack,webpack:** Resolve bundled postcss defaults from builder ([#​35823](https://github.com/nuxt/nuxt/pull/35823))
- **schema:** Normalise slashes in `app.buildAssetsDir` ([#​35833](https://github.com/nuxt/nuxt/pull/35833))
- **nitro:** Bound island props and v-for to prevent unauthenticated DoS ([4e35ae9ba](https://github.com/nuxt/nuxt/commit/4e35ae9ba))
- **nitro:** Confine runtime payload cache to prerendering ([ac9b41a36](https://github.com/nuxt/nuxt/commit/ac9b41a36))
- **nuxt:** Case-fold route rule keys to match folded lookups ([ad624a75a](https://github.com/nuxt/nuxt/commit/ad624a75a))
- **nitro:** Require loopback peer for chrome devtools workspace endpoint ([0769c4f9b](https://github.com/nuxt/nuxt/commit/0769c4f9b))
- **nuxt:** Reject reserved `template` island prop under runtime compiler ([ee6c84633](https://github.com/nuxt/nuxt/commit/ee6c84633))
- **nuxt:** Reject top-level `as` prop for islands ([581651ff3](https://github.com/nuxt/nuxt/commit/581651ff3))
##### 💅 Refactors
- **nuxt:** Use tick based debounce for asyncData executes ([#​34151](https://github.com/nuxt/nuxt/pull/34151))
- **kit,nuxt:** Replace `semver` with `verkit` ([#​35713](https://github.com/nuxt/nuxt/pull/35713))
- **rspack,webpack:** Use DI model to split builders ([#​35751](https://github.com/nuxt/nuxt/pull/35751))
##### 📖 Documentation
- Add dev container setup guide ([#​35665](https://github.com/nuxt/nuxt/pull/35665))
- Fix dev container setup guide ([#​35666](https://github.com/nuxt/nuxt/pull/35666))
- Add explanation of 200.html and 404.html SPA fallbacks ([#​34483](https://github.com/nuxt/nuxt/pull/34483))
- Expand payload extraction documentation ([#​35648](https://github.com/nuxt/nuxt/pull/35648))
- Clarify NuxtLink componentName is the internal (devtools) name ([#​35658](https://github.com/nuxt/nuxt/pull/35658))
- Add example for components dir pattern option ([#​35663](https://github.com/nuxt/nuxt/pull/35663))
- Require a single root element ([#​35677](https://github.com/nuxt/nuxt/pull/35677))
- Add reason for why you should never import Vue app code in nitro code ([#​34481](https://github.com/nuxt/nuxt/pull/34481))
- Document disabling code-splitting with `codeSplitting: false` ([#​35683](https://github.com/nuxt/nuxt/pull/35683))
- Document nuxt-client caveat for non-SFC components ([#​35654](https://github.com/nuxt/nuxt/pull/35654))
- Clarify favicon does not use cdnURL by default ([#​35681](https://github.com/nuxt/nuxt/pull/35681))
- Standardize section order and headings across docs ([#​35685](https://github.com/nuxt/nuxt/pull/35685))
- Clarify `onPrehydrate` example comment ([#​35684](https://github.com/nuxt/nuxt/pull/35684))
- Add useId as a known limitation of nuxt island ([#​35693](https://github.com/nuxt/nuxt/pull/35693))
- Recommend status over pending in data fetching ([#​35694](https://github.com/nuxt/nuxt/pull/35694))
- Fill gaps in API minimalVersion badges after [#​34485](https://github.com/nuxt/nuxt/issues/34485) ([#​35708](https://github.com/nuxt/nuxt/pull/35708), [#​34485](https://github.com/nuxt/nuxt/issues/34485))
- Explain dynamic asset paths ([#​35695](https://github.com/nuxt/nuxt/pull/35695))
- Document `runtimeConfig` env var casting edge cases ([#​35709](https://github.com/nuxt/nuxt/pull/35709))
- Clarify module dependency resolution ([#​35718](https://github.com/nuxt/nuxt/pull/35718))
- Add more writing guidelines ([#​35662](https://github.com/nuxt/nuxt/pull/35662))
- Add note with alternatives to using remote layers ([#​35721](https://github.com/nuxt/nuxt/pull/35721))
- Use `nuxt` rather than `nuxi` ([8891e179c](https://github.com/nuxt/nuxt/commit/8891e179c))
- Document relative baseURL workarounds ([#​34004](https://github.com/nuxt/nuxt/pull/34004))
- Warn about `runtimeCompiler` security best practices ([449b63ab1](https://github.com/nuxt/nuxt/commit/449b63ab1))
- Warn about validating server component props ([2c981cb07](https://github.com/nuxt/nuxt/commit/2c981cb07))
##### 📦 Build
- **nitro:** Re-export type from augments to preserve module ([dac937675](https://github.com/nuxt/nuxt/commit/dac937675))
- **nuxt:** Remove `.ts` file extension from `runtime/` imports ([#​35689](https://github.com/nuxt/nuxt/pull/35689))
- **ui-templates:** Commit generated ui template files ([#​35770](https://github.com/nuxt/nuxt/pull/35770))
##### 🏡 Chore
- Add extension 🤦 ([d595fb3d4](https://github.com/nuxt/nuxt/commit/d595fb3d4))
- Move to pnpm catalogs ([#​35748](https://github.com/nuxt/nuxt/pull/35748))
- Update knip config, resolve issues, and run in ci ([#​35742](https://github.com/nuxt/nuxt/pull/35742))
- Ignore `@nuxt/telemetry` in knip ([9824d4f10](https://github.com/nuxt/nuxt/commit/9824d4f10))
- **ui-templates:** Pass config file path to unocss ([34e7a810d](https://github.com/nuxt/nuxt/commit/34e7a810d))
- Allow regenerating lockfile in release script ([c31389df3](https://github.com/nuxt/nuxt/commit/c31389df3))
- **nuxt:** Bump `@nuxt/devtools` to v3.3.1 ([#​35815](https://github.com/nuxt/nuxt/pull/35815))
- Ensure types are setup before unit tests ([784d8f700](https://github.com/nuxt/nuxt/commit/784d8f700))
- Simplify knip configuration ([#​35827](https://github.com/nuxt/nuxt/pull/35827))
##### ✅ Tests
- Reproduce duplicate CSS in shared chunks ([#​35649](https://github.com/nuxt/nuxt/pull/35649))
- Prepare fixtures automatically before fixture and e2e runs ([e8b3e6411](https://github.com/nuxt/nuxt/commit/e8b3e6411))
- Improve and refactor basic fixture ([#​35687](https://github.com/nuxt/nuxt/pull/35687))
- Slim down client-only, chunk-error and axis-independent suites ([#​35706](https://github.com/nuxt/nuxt/pull/35706))
- Do not run type checking when benchmarking nuxt build ([6355f3bc9](https://github.com/nuxt/nuxt/commit/6355f3bc9))
- **kit:** Scope loadNuxt temp dir so it doesn't delete sibling fixtures ([37301dd51](https://github.com/nuxt/nuxt/commit/37301dd51))
- Guard against transient undefined `_route` in gotoPath ([ca92d082b](https://github.com/nuxt/nuxt/commit/ca92d082b))
- Retry fixture prepare on transient ENOTEMPTY ([3a6b59f96](https://github.com/nuxt/nuxt/commit/3a6b59f96))
- Raise route-HMR polling timeout to reduce e2e flakiness ([01dc27b7e](https://github.com/nuxt/nuxt/commit/01dc27b7e))
- Update bundle size snapshot ([30d24817c](https://github.com/nuxt/nuxt/commit/30d24817c))
##### 🤖 CI
- Rebalance shards and drop non-vite windows fixtures ([#​35700](https://github.com/nuxt/nuxt/pull/35700))
- Warm windows dependency cache on main ([#​35730](https://github.com/nuxt/nuxt/pull/35730))
- Run knip in default and production modes ([#​35745](https://github.com/nuxt/nuxt/pull/35745))
- Run knip on pull requests ([d620aa972](https://github.com/nuxt/nuxt/commit/d620aa972))
- Prepare tests ([c7bf7f738](https://github.com/nuxt/nuxt/commit/c7bf7f738))
- Also prepare tests in `knip` job ([58f68bd64](https://github.com/nuxt/nuxt/commit/58f68bd64))
- Check internal docs links on pull requests and all links weekly ([#​35767](https://github.com/nuxt/nuxt/pull/35767))
##### ❤️ Contributors
- Daniel Roe ([@​danielroe](https://github.com/danielroe))
- Lars Kappert ([@​webpro](https://github.com/webpro))
- Matej Černý ([@​cernymatej](https://github.com/cernymatej))
- Anthony Fu ([@​antfu](https://github.com/antfu))
- Harlan Wilton ([@​harlan-zw](https://github.com/harlan-zw))
- Florian Heuberger ([@​Flo0806](https://github.com/Flo0806))
- Anoesj Sadraee ([@​Anoesj](https://github.com/Anoesj))
- Ryota Watanabe ([@​wattanx](https://github.com/wattanx))
- Alexander Lichter ([@​TheAlexLichter](https://github.com/TheAlexLichter))
- Nestor Vera ([@​hacknug](https://github.com/hacknug))
- Mateleo ([@​Mateleo](https://github.com/Mateleo))
- Kevin Deng ([@​sxzz](https://github.com/sxzz))
- Robin ([@​OrbisK](https://github.com/OrbisK))
- Bochkarev Ivan ([@​Ibochkarev](https://github.com/Ibochkarev))
- Quentin Macq ([@​quentinmcq](https://github.com/quentinmcq))
- Julien Huang ([@​huang-julien](https://github.com/huang-julien))
- Max ([@​onmax](https://github.com/onmax))
- Luke Nelson ([@​luc122c](https://github.com/luc122c))
- Darlan José Batista do Prado ([@​DarlanPrado](https://github.com/DarlanPrado))
- kealan ([@​KealanAU](https://github.com/KealanAU))
- Sushant ([@​sushantguri](https://github.com/sushantguri))
- raminjafary ([@​raminjafary](https://github.com/raminjafary))
- Aubakirov Asker ([@​Askerka00](https://github.com/Askerka00))
- lutejka ([@​lutejka](https://github.com/lutejka))
- Amulet Iris ([@​KazariAI](https://github.com/KazariAI))
- bdbch ([@​bdbch](https://github.com/bdbch))
- Elecmonkey ([@​elecmonkey](https://github.com/elecmonkey))
### [`v4.5.0`](https://github.com/nuxt/nuxt/releases/tag/v4.5.0)
[Compare Source](https://github.com/nuxt/nuxt/compare/v4.4.8...v4.5.0)
> 4.5.0 is the next minor release.
#### 📣 Some News
##### Preparing for Nuxt 5
A good chunk of this release is (hopefully) invisible plumbing for Nuxt 5. We've moved onto the latest major versions of several core dependencies (`unhead` v3, `unctx` v3, and Vite 8), switched the framework's own build over to [`tsdown`](https://github.com/nuxt/nuxt/pull/35179), and introduced a stable `nuxt/*` build output contract with dev exports so that type-checking in the Nuxt monorepo works without a build step ([#​35463](https://github.com/nuxt/nuxt/issues/35463), [#​35605](https://github.com/nuxt/nuxt/issues/35605)).
Much of this is working to shrink the gap between v4 and v5 internally, so that the migration will be as boring as possible.
> \[!TIP]
> If you want to test some of the breaking changes of Nuxt v5, you can already opt in with `future.compatibilityVersion: 5`. Keep an eye on the [Upgrade Guide](https://nuxt.com/docs/getting-started/upgrade) for details as they land.
With the release of Nuxt v4.5, our focus as a team will turn to stabilising Nuxt v5 and creating compatibility utilities to make the upgrade as smooth as possible.
##### Nuxt 3 End-of-Life
Nuxt 3 reaches end-of-life on **July 31, 2026**, so this is one of the last few 3.x releases we'll ship. If you're still on v3, now is a great time to move across. Most people told us the v3 to v4 upgrade was smooth, and we've kept the [upgrade guide](https://nuxt.com/docs/getting-started/upgrade) up to date.
Alongside v4.5.0 we're publishing a maintenance patch for the 3.x line (**v3.21.9**) with the compatible bug fixes and smaller improvements from this release backported. The headline items here (Vite 8, Rspack 2, `unhead` v3, `unctx` v3) are major upgrades and stay v4-only, so 3.x remains stable as it approaches end-of-life.
#### 👀 Highlights
Nuxt 4.5 is a big one. This release ships three major upgrades to the build layer (Vite 8, Rspack 2, and a brand new Rsbuild-powered pipeline for the Rspack builder), an experimental SSR streaming mode, a handful of new composables and conventions, and a lot of groundwork that brings us closer to Nuxt 5.
There's a lot here, so grab a coffee. ☕️
##### ⚡️ Vite 8
Nuxt now runs on [Vite 8](https://vite.dev) ([#​34256](https://github.com/nuxt/nuxt/issues/34256)). This brings faster cold starts, the latest Rolldown-powered internals, and many upstream improvements from the Vite team.
For most apps this is a transparent upgrade. If you have custom Vite plugins or config, it's worth skimming the [Vite migration guide](https://vite.dev/guide/migration) to check for anything that affects you.
> \[!WARNING]
> Vite 8 is a major version bump. If you depend on Vite directly (custom plugins, `vite.config` tweaks, or ecosystem plugins that pin a Vite version), make sure those are compatible before upgrading in production.
##### 🦀 Rspack 2 and Rsbuild
If you use the Rspack builder, this release is a substantial upgrade. We've moved to [Rspack 2](https://www.rspack.dev/blog/announcing-2-0) ([#​34929](https://github.com/nuxt/nuxt/issues/34929)), which is faster and lighter, and rebuilt the builder on top of [`@rsbuild/core`](https://rsbuild.dev) ([#​35489](https://github.com/nuxt/nuxt/issues/35489)).
The public surface stays the same. You still opt in with `builder: 'rspack'` and the existing `rspack:*` hooks continue to work:
```ts
// nuxt.config.ts
export default defineNuxtConfig({
builder: 'rspack',
})
```
Under the hood, though, a lot has changed for the better:
- The dev server now runs in middleware mode via Rsbuild, replacing `webpack-dev-middleware` and `webpack-hot-middleware` ([#​35575](https://github.com/nuxt/nuxt/issues/35575)).
- We use an Rspack-specific Vue loader for correct SSR scoped-style ids and stricter ESM resolution ([#​35566](https://github.com/nuxt/nuxt/issues/35566)).
> \[!NOTE]
> This is the foundation for first-class Rsbuild support. We kept the builder named `rspack` for now so nothing breaks, but the internals are now Rsbuild all the way down.
##### 🌊 Experimental SSR Streaming
This is one I'm particularly excited about. You can now enable **SSR streaming** to dramatically improve Time to First Byte ([#​34411](https://github.com/nuxt/nuxt/issues/34411)). Instead of buffering the whole rendered page and sending it in one go, Nuxt flushes the HTML shell (your `<head>`, styles, preload hints, and entry scripts) immediately, then streams the body as Vue renders it.
```ts
// nuxt.config.ts
export default defineNuxtConfig({
experimental: {
ssrStreaming: true,
},
})
```
Streaming is automatically disabled for bots and crawlers so search engines still receive fully-rendered HTML. You can tune which user agents count as crawlers, and you can opt individual routes out:
```ts
// nuxt.config.ts
export default defineNuxtConfig({
experimental: {
ssrStreaming: {
botRegex: /googlebot|bingbot|my-internal-crawler/i,
},
},
routeRules: {
'/no-stream/**': { streaming: false },
},
})
```
There's one thing worth understanding before you turn it on. Because streaming commits the HTTP status and headers with the very first byte, anything that mutates the response *after* rendering has begun (a `setResponseStatus()` in a `<script setup>`, a cookie write during middleware, and so on) can't reach the client. Nuxt handles the common cases for you: routes with `redirect`, `cache`, `isr`, `swr`, `noScripts`, or `ssr: false` rules automatically fall back to the buffered renderer, and in development we log a warning naming any dropped mutations so nothing fails silently.
> \[!WARNING]
> SSR streaming is experimental and off by default. It could be great for content-heavy routes where TTFB matters, but test it against your app's response-mutating logic (status codes, headers, cookies) before shipping it widely. The [experimental features docs](https://nuxt.com/docs/guide/going-further/experimental-features#ssrstreaming) cover the fallback rules and caveats in detail.
📖 [SSR streaming documentation](https://nuxt.com/docs/guide/going-further/experimental-features#ssrstreaming)
##### 🩺 Stable Error Codes
This one I'm really happy about. Nuxt now has a **stable error code system** ([#​35429](https://github.com/nuxt/nuxt/issues/35429)). Warnings and errors raised during build and at runtime now carry a stable code (like `NUXT_E1001` or `NUXT_B5001`), a short explanation of **why** it happened, and a concrete **fix** to try.
Every code is greppable and bookmarkable, and the ones that need more than a one-line fix link straight to a dedicated docs page. For example, the classic "a composable was called outside a Nuxt context" now surfaces as `NUXT_E1001` with the why/fix inline and a [docs page](https://nuxt.com/docs/errors/e1001) explaining the context rules and how to use `runWithContext()`.
To keep production output lean, the verbose why/fix text is stripped from production builds, leaving just the stable code.
This is the foundation for much better error messages across Nuxt, and we'll keep migrating existing warnings and errors onto it over the coming releases. If you've ever squinted at a cryptic Nuxt message, this is for you.
##### 🎨 `useLayout` Composable
There's a new `useLayout` composable for reading the layout that's been resolved for the current route ([#​35623](https://github.com/nuxt/nuxt/issues/35623)). Previously there was no clean, reactive way to ask "which layout is this page using?" from within a component.
```vue
<!-- app/components/LayoutBadge.vue -->
<script setup lang="ts">
const layout = useLayout()
</script>
<template>
<span>Current layout: {{ layout }}</span>
</template>
```
It returns a read-only computed ref, so it stays in sync as you navigate or as route rules and `definePageMeta` change the resolved layout.
📖 [`useLayout` documentation](https://nuxt.com/docs/api/composables/use-layout)
##### 🪟 Named Views
Nuxt now supports **named views** through a filename convention ([#​35123](https://github.com/nuxt/nuxt/issues/35123)). If a parent page renders more than one `<NuxtPage>` outlet, you can give each outlet a name and provide a sibling page file for it using the `name@view.vue` convention:
```bash
# Directory Structure
-| pages/
---| parent/
-----| child.vue
-----| child@sidebar.vue
---| parent.vue
```
```vue
<!-- pages/parent.vue -->
<template>
<div>
<NuxtPage />
<aside>
<NuxtPage name="sidebar" />
</aside>
</div>
</template>
```
Navigating to `/parent/child` renders `child.vue` into the default outlet and `child@sidebar.vue` into the `sidebar` outlet. This has actually been possible in Vue Router for a long time; this release wires it up to Nuxt's file-based routing.
> \[!NOTE]
> `definePageMeta` is read from the default route file only, and per-view rendering modes aren't supported (the parent page's mode applies to the default view).
📖 [Named views documentation](https://nuxt.com/docs/guide/directory-structure/app/pages#named-views)
##### 🚦 `enabled` Option for `useFetch` and `useAsyncData`
You can now gate data fetching with a reactive `enabled` option ([#​33260](https://github.com/nuxt/nuxt/pull/33260)). While `enabled` is `false`, every execution is blocked (the initial fetch, `execute`/`refresh`, and watch triggers), and if you flip it from `true` to `false` mid-flight, the in-flight request is cancelled without clearing your existing `data`.
```vue [app/pages/search.vue]
<script setup lang="ts">
const query = ref('')
const { data } = await useFetch('/api/search', {
query: { q: query },
// Only fetch once the user has typed something
enabled: () => query.value.length > 2,
})
</script>
```
This is perfect for dependent or conditional queries, where you don't want to fire a request until some precondition is met. It pairs naturally with a getter or a ref, so it stays reactive.
📖 [`useAsyncData` documentation](https://nuxt.com/docs/api/composables/use-async-data)
##### 🔗 `NuxtLink` Prefetch Control for Custom Slots
When you use `<NuxtLink>` with the `custom` prop, Nuxt no longer attaches prefetch handlers for you, because it can't know how you've structured your markup. To make that ergonomic, the slot now exposes everything you need to wire prefetching up yourself ([#​34539](https://github.com/nuxt/nuxt/issues/34539)):
```vue
<template>
<NuxtLink
v-slot="{ href, navigate, prefetch, prefetched, shouldPrefetch }"
to="/about"
custom
>
<a
:href="href"
:class="{ 'is-prefetched': prefetched }"
@click="navigate"
@pointerenter="shouldPrefetch('interaction') && prefetch()"
@focus="shouldPrefetch('interaction') && prefetch()"
>
About page
</a>
</NuxtLink>
</template>
```
You get `prefetch` to trigger it, `prefetched` to know whether it's already happened (great for a prefetched class), and `shouldPrefetch` to respect the user's connection and config.
📖 [`NuxtLink` documentation](https://nuxt.com/docs/api/components/nuxt-link)
##### ⚡️ Forwarded Preload Hints on Prefetch
Here's another one we'd love you to try. When you prefetch a link to a route with payload extraction, Nuxt already primes the destination's data and chunks. With the new opt-in `experimental.prefetchPreloadTags` ([#​35144](https://github.com/nuxt/nuxt/issues/35144)), it also forwards the destination's `<link rel="preload">` and `modulepreload` hints (whatever the page sets via `useHead`, or via modules like `@nuxt/image`'s `<NuxtImg preload>`) into the *current* document, downgraded to `rel="prefetch"` so they don't compete with the resources the user is looking at right now.
```ts
// nuxt.config.ts
export default defineNuxtConfig({
experimental: {
prefetchPreloadTags: true,
},
})
```
The practical effect is that heavy above-the-fold assets on the *next* page (a hero image, a critical script) start downloading while the user is still on the current one, so the navigation feels instant. It's off by default while we gather feedback, so please give it a spin and tell us how it behaves on your app.
##### 🌐 `import.meta.envName`
The resolved Nuxt environment name is now available at runtime as `import.meta.envName` for both Vite and webpack/Rspack builds ([#​34844](https://github.com/nuxt/nuxt/issues/34844)). This is the value set by `--envName` (or the resolved default), so you can branch on it in your app code:
```ts
if (import.meta.envName === 'staging') {
// enable staging-only behaviour
}
```
📖 [`import.meta` documentation](https://nuxt.com/docs/api/advanced/import-meta)
##### 🔭 Tracing Channels for SSR Events
Nuxt now publishes [diagnostics-channel](https://nodejs.org/api/diagnostics_channel.html) traces for its server-side subsystems ([#​35191](https://github.com/nuxt/nuxt/issues/35191)). It's unopinionated: we emit `nuxt.render`, `nuxt.island`, `nuxt.data`, and `nuxt.plugin` channels following the [untracing](https://github.com/unjs/untracing) naming convention, and you can build OpenTelemetry (or anything else) on top. It works in Node, Deno, Bun, and Cloudflare Workers.
```ts
// nuxt.config.ts
export default defineNuxtConfig({
// Turn on Nuxt's own channels
tracingChannel: true,
})
```
You can also enable it granularly. In Nuxt v5, there will be additional Nitro-level channels:
```ts
// nuxt.config.ts
export default defineNuxtConfig({
tracingChannel: {
nuxt: true,
},
})
```
> \[!NOTE]
> Channel names, payload shapes, and option keys may still change while the [untracing](https://github.com/unjs/untracing) registry settles.
##### 📦 Dependency Upgrades
##### `unhead` v3
Nuxt's head management now runs on [`unhead` v3](https://unhead.unjs.io/docs/releases/v3) ([#​34793](https://github.com/nuxt/nuxt/issues/34793)). It's smaller, uses a synchronous engine internally, and ships better type-safety for `useHead` out of the box. This also unblocks SSR streaming.
> \[!WARNING]
> `unhead` v3 introduces type-narrowing for `useHead`, which can be a **breaking type change** if you were relying on the looser v2 types. The runtime behaviour is compatible for the vast majority of apps, and promise input (deprecated in v2) is no longer supported. If you hit type errors after upgrading, they're almost always genuine tightening rather than regressions.
##### `unctx` v3
We've moved to [`unctx` v3](https://github.com/unjs/unctx) ([#​35541](https://github.com/nuxt/nuxt/issues/35541)), which resolves a class of long-standing async context issues ([#​33644](https://github.com/nuxt/nuxt/issues/33644)). This is part of the composable-context reliability work that continues into v5.
##### And more
We've also updated `magic-string` to v1, Babel to v8, and pulled in the latest Rolldown across the board. Running `nuxt upgrade --dedupe` (see below) is the easiest way to pull these through cleanly.
##### 🛠️ Nuxt CLI
This release bundles some improvements from `@nuxt/cli` v3.36 and v3.37:
- **`nuxt module remove`** to uninstall a module and clean up its config ([nuxt/cli#1306](https://github.com/nuxt/cli/pull/1306)), the natural companion to `nuxt module add`.
- **Non-interactive `nuxt init`** for scripting and CI ([nuxt/cli#1341](https://github.com/nuxt/cli/pull/1341)), plus it now respects the template's own package manager instead of prompting ([nuxt/cli#1330](https://github.com/nuxt/cli/pull/1330)).
- **Type-check onboarding**: `nuxt typecheck` now offers to install `vue-tsc` and `typescript` for you if they're missing ([nuxt/cli#1316](https://github.com/nuxt/cli/pull/1316)).
- **Golar support for type-checking**: `nuxt typecheck` can now use [Golar](https://golar.dev) as an alternative to `vue-tsc` ([nuxt/cli#1362](https://github.com/nuxt/cli/pull/1362)). It's picked up automatically if it's installed (or a `golar.config.*` file exists), and you can force a checker with `--checker=vue-tsc` or `--checker=golar`:
```sh
nuxt typecheck --checker=golar
```
- **Layer-aware dev server**: the dev server now reloads on changes to local layer `nuxt.config` files ([nuxt/cli#1345](https://github.com/nuxt/cli/pull/1345)).
##### 🧩 TypeScript Plugin and Named Layout Slots
Nuxt's experimental TypeScript plugin is powered by [`@dxup/nuxt`](https://github.com/KazariEX/dxup), and this release bundles a newer version of it. If you haven't tried it, turning on `experimental.typescriptPlugin` gives you a set of editor niceties: renaming an auto-imported component updates every usage, plus go-to-definition for glob imports, Nitro routes, `definePageMeta`, `runtimeConfig`, and typed route names.
New in the bundled version is an opt-in **runtime** feature: named layout slots ([KazariEX/dxup#20](https://github.com/KazariEX/dxup/pull/20)). You can write a top-level named-slot template in a page and have it forwarded into the matching named slot of the active layout. That lets a page inject content into its layout's slots, which file-based layouts don't otherwise allow.
Enable it alongside the plugin:
```ts
// nuxt.config.ts
export default defineNuxtConfig({
experimental: {
typescriptPlugin: true,
},
dxup: {
features: {
namedLayoutSlots: true,
},
},
})
```
Then a layout can expose named slots:
```vue
<!-- layouts/center.vue -->
<template>
<slot />
<slot name="side" one="one" />
</template>
```
And any page using that layout can fill them:
```vue
<!-- pages/about.vue -->
<script setup lang="ts">
definePageMeta({ layout: 'center' })
</script>
<template>
<template #side="{ one }">
This "{{ one }}" comes from the layout slot.
</template>
<div>About page</div>
</template>
```
📖 [`typescriptPlugin` documentation](https://nuxt.com/docs/guide/going-further/experimental-features#typescriptplugin)
##### 🔥 Performance and Reliability
As always, a lot of work has gone into making Nuxt faster and steadier.
One you can opt into today is the **shared file watcher** ([#​35143](https://github.com/nuxt/nuxt/issues/35143)). Vite already runs a chokidar-based watcher, so Nuxt can piggy-back on it instead of spinning up a second one, using less memory and fewer file handles. This becomes the default with `compatibilityVersion: 5`, but you can turn it on now and let us know how it goes:
```ts
// nuxt.config.ts
export default defineNuxtConfig({
experimental: {
watcher: 'builder',
},
})
```
There are also some other good performance improvements that don't need to be opted into:
- **Faster dev startup**: dev-only Nitro and pages work is now deferred and streamlined ([#​35381](https://github.com/nuxt/nuxt/issues/35381), [#​35383](https://github.com/nuxt/nuxt/issues/35383)).
- **Leaner production builds**: the island-renderer chunk is skipped entirely when you use no islands ([#​35456](https://github.com/nuxt/nuxt/issues/35456)), and plugin handling is tree-shaken out of prod builds ([#​35278](https://github.com/nuxt/nuxt/issues/35278)).
- **Improved island hashing**: more stable island and key hashing, with `getIslandHash` and `hashKey` now exposed ([#​35583](https://github.com/nuxt/nuxt/issues/35583)).
- **Concurrency and I/O wins** across Kit path resolution and Nitro inline styles ([#​35511](https://github.com/nuxt/nuxt/issues/35511), [#​35514](https://github.com/nuxt/nuxt/issues/35514)).
A couple of other nice quality-of-life fixes:
- `$fetch` is now auto-imported in user code, which fixes edge cases with top-level `$fetch.create` under Rolldown's output format ([#​35581](https://github.com/nuxt/nuxt/issues/35581)).
- Nuxt now honours system `HTTP_PROXY` / `HTTPS_PROXY` environment variables in the builder environment ([#​35183](https://github.com/nuxt/nuxt/issues/35183)).
- HMR now works for `defineNuxtComponent` in JSX ([#​35620](https://github.com/nuxt/nuxt/issues/35620)).
##### ⚠️ Heads-Up Before Upgrading
As mentioned above, this release includes three major dependency bumps. For most apps they're transparent, but they're worth a moment of attention:
- **Vite 8**: check custom Vite plugins and config against the [Vite migration guide](https://vite.dev/guide/migration).
- **Rspack 2**: if you use `builder: 'rspack'`, the internals are now Rsbuild-based, so custom Rspack config may need review.
- **`unhead` v3**: possible breaking *type* changes from stricter `useHead` typing.
##### ⬆️ Upgrading
Our recommendation for upgrading is to run:
```sh
npx nuxt upgrade --dedupe
# or, if you are staying on the 3.x line
npx nuxt@latest upgrade --dedupe --channel=v3
```
This will deduplicate your lockfile and help ensure you pull in updates from the other dependencies Nuxt relies on, particularly across the unjs ecosystem (which matters more than usual this release, given the major bumps).
> \[!TIP]
> Check out our [upgrade guide](https://nuxt.com/docs/getting-started/upgrade) if you're upgrading from an older version.
Thank you to all of the many contributors to this release. This was a big one, and it wouldn't happen without you. 💚
#### 👉 Changelog
[compare changes](https://github.com/nuxt/nuxt/compare/v4.4.8...v4.5.0)
##### 🚀 Enhancements
- **rspack:** Upgrade to rspack v2 ([#​34929](https://github.com/nuxt/nuxt/pull/34929))
- **nuxt:** Migrate to unhead v3 ([#​34793](https://github.com/nuxt/nuxt/pull/34793))
- **nuxt:** Support named views via filename convention ([#​35123](https://github.com/nuxt/nuxt/pull/35123))
- **nuxt:** Expose environment name on `import.meta` ([#​34844](https://github.com/nuxt/nuxt/pull/34844))
- **nuxt:** Forward destination preload hints on link prefetch ([#​35144](https://github.com/nuxt/nuxt/pull/35144))
- **nuxt:** Honour http proxy env vars in builder environment ([#​35183](https://github.com/nuxt/nuxt/pull/35183))
- **kit:** Support `allowImportingTsExtensions` ([87c214b34](https://github.com/nuxt/nuxt/commit/87c214b34))
- **nitro,nuxt,schema:** Add experimental SSR streaming ([5f4efe5a5](https://github.com/nuxt/nuxt/commit/5f4efe5a5))
- **vite:** Migrate to Vite 8 ([#​34256](https://github.com/nuxt/nuxt/pull/34256))
- **nitro,nuxt:** Add support for tracing channels for ssr events ([#​35191](https://github.com/nuxt/nuxt/pull/35191))
- **kit,nitro,nuxt,schema,vite,webpack:** `nuxt/*` build output contract ([#​35463](https://github.com/nuxt/nuxt/pull/35463))
- **nuxt:** Expose `NuxtLink` prefetch props for custom slot ([#​34539](https://github.com/nuxt/nuxt/pull/34539))
- **rspack:** Use rspack-specific Vue loader ([a085e8e34](https://github.com/nuxt/nuxt/commit/a085e8e34))
- **rspack:** Use rspack-specific Vue loader ([#​35566](https://github.com/nuxt/nuxt/pull/35566))
- **nuxt,vite:** Attach vite HMR to the shared dev server listener ([a8ea0c7e2](https://github.com/nuxt/nuxt/commit/a8ea0c7e2))
- **nuxt:** Add `enabled` option to AsyncData ([#​33260](https://github.com/nuxt/nuxt/pull/33260))
- **rspack:** Use rsbuild dev server in middleware mode ([#​35575](https://github.com/nuxt/nuxt/pull/35575))
- **nuxt:** Expose `getIslandHash` + `hashKey` + improve hash for keys ([#​35583](https://github.com/nuxt/nuxt/pull/35583))
- **nuxt:** Add `useLayout` composable for accessing the resolved route layout ([#​35623](https://github.com/nuxt/nuxt/pull/35623))
- **nuxt:** Support `enabled` option in `useFetch` ([#​35627](https://github.com/nuxt/nuxt/pull/35627))
- Error code system ([#​35429](https://github.com/nuxt/nuxt/pull/35429))
- **nuxt:** Require keyed function 'source' with compatibilityVersion 5 ([d56662647](https://github.com/nuxt/nuxt/commit/d56662647))
- **kit:** Order layer aliases before generic root aliases (`~`, `@`) ([#​35641](https://github.com/nuxt/nuxt/pull/35641))
- **kit:** Add `updateAppConfig` util for module authors ([#​35651](https://github.com/nuxt/nuxt/pull/35651))
- **nuxt:** Warn when parent page lacks NuxtPage ([#​35639](https://github.com/nuxt/nuxt/pull/35639))
##### 🔥 Performance
- **nuxt,vite:** Reuse vite watcher via `experimental.watcher` ([#​35143](https://github.com/nuxt/nuxt/pull/35143))
- **nuxt:** Tree-shake unneeded plugin handling from prod builds ([#​35278](https://github.com/nuxt/nuxt/pull/35278))
- **nuxt:** Defer dev-only nitro startup work ([#​35381](https://github.com/nuxt/nuxt/pull/35381))
- **nuxt:** Reduce dev pages startup work ([#​35383](https://github.com/nuxt/nuxt/pull/35383))
- **vite:** Skip unnecessary decorator transforms ([#​35395](https://github.com/nuxt/nuxt/pull/35395))
- **nuxt,rspack,vite,webpack:** Migrate to `rolldown-string` ([#​35058](https://github.com/nuxt/nuxt/pull/35058))
- **nitro,nuxt,vite:** Remove unused dependencies ([#​34861](https://github.com/nuxt/nuxt/pull/34861))
- **nuxt:** Re-parse transformed file once in tree-shake plugin ([#​35509](https://github.com/nuxt/nuxt/pull/35509))
- **nuxt:** Index scanned components by name to avoid quadratic lookup ([#​35510](https://github.com/nuxt/nuxt/pull/35510))
- **kit:** Stat paths directly and probe extensions in parallel ([#​35511](https://github.com/nuxt/nuxt/pull/35511))
- **nitro:** Load inline style chunks concurrently ([#​35514](https://github.com/nuxt/nuxt/pull/35514))
- **nuxt:** Skip island-renderer chunk when no islands used ([#​35456](https://github.com/nuxt/nuxt/pull/35456))
##### 🩹 Fixes
- **nuxt:** Fix explicit return types of factory types ([40a80ec7d](https://github.com/nuxt/nuxt/commit/40a80ec7d))
- **vite:** Do not await `ready` event for vite watcher ([6fe539f52](https://github.com/nuxt/nuxt/commit/6fe539f52))
- **vite:** Invalidate modules for legacy vite server ([#​35315](https://github.com/nuxt/nuxt/pull/35315))
- **kit,nuxt:** Pass error as `cause` when we re-throw ([#​35387](https://github.com/nuxt/nuxt/pull/35387))
- **nuxt:** Make virtual module IDs stable across hosts ([#​35317](https://github.com/nuxt/nuxt/pull/35317))
- **vite:** Inline CSS for lazy components imported from `#components` ([#​35215](https://github.com/nuxt/nuxt/pull/35215))
- **nuxt:** Validate `<ClientOnly>` fallback tag name ([#​35325](https://github.com/nuxt/nuxt/pull/35325))
- **vite:** Extract server page CSS when `inlineStyles` is disabled ([#​35209](https://github.com/nuxt/nuxt/pull/35209))
- **nitro:** Include nested layers in nitro transforms ([#​35327](https://github.com/nuxt/nuxt/pull/35327))
- **kit:** Don't exclude layer module runtime files from app tsconfig ([#​35326](https://github.com/nuxt/nuxt/pull/35326))
- **nitro:** Export empty styles object with `ssr: false` ([#​35330](https://github.com/nuxt/nuxt/pull/35330))
- **nuxt:** Emit auto-import paths as files, not directories ([#​35333](https://github.com/nuxt/nuxt/pull/35333))
- **nuxt:** Suppress spurious slot warning in `NuxtPage` during nav ([#​35335](https://github.com/nuxt/nuxt/pull/35335))
- **vite:** Preserve backslash separators in windows named pipe path ([#​35336](https://github.com/nuxt/nuxt/pull/35336))
- **nuxt:** Detach in-flight promise on `useAsyncData` teardown ([#​35328](https://github.com/nuxt/nuxt/pull/35328))
- **nuxt:** Respect per-page keepalive set via `definePageMeta` ([#​34526](https://github.com/nuxt/nuxt/pull/34526))
- **nuxt:** Align useFetch error type with runtime ([#​35346](https://github.com/nuxt/nuxt/pull/35346))
- **nuxt:** Unwrap getter `body` in `useFetch` ([#​35343](https://github.com/nuxt/nuxt/pull/35343))
- **rspack,webpack:** Strip invalid webpack PURE annotations ([#​35386](https://github.com/nuxt/nuxt/pull/35386))
- **nuxt:** Guard against -1 when unregistering a router hook ([#​35391](https://github.com/nuxt/nuxt/pull/35391))
- **nuxt:** Clean up load/error listeners in preloadPayload ([#​35392](https://github.com/nuxt/nuxt/pull/35392))
- **nuxt:** Avoid idle wait when refreshing data after hydration ([#​35358](https://github.com/nuxt/nuxt/pull/35358))
- **nuxt:** Hash serialized island props ([#​35356](https://github.com/nuxt/nuxt/pull/35356))
- **nuxt:** Ensure `page:start` finishes before `page:finish` starts ([#​35408](https://github.com/nuxt/nuxt/pull/35408))
- **vite:** Invalidate lazily-created SSR modules on render ([#​35410](https://github.com/nuxt/nuxt/pull/35410))
- **nuxt:** Preserve plugin navigation during app boot ([#​35344](https://github.com/nuxt/nuxt/pull/35344))
- **vite:** Invalidate stale SSR virtual modules on render ([#​35413](https://github.com/nuxt/nuxt/pull/35413))
- **nitro:** Don't preload JS for lazy-hydrated components ([#​35342](https://github.com/nuxt/nuxt/pull/35342))
- **vite:** Add extension to vite-node-runner virtual specifier ([0bf3f1d5d](https://github.com/nuxt/nuxt/commit/0bf3f1d5d))
- **vite:** Invalidate ssr copy of regenerated templates ([33a581ce7](https://github.com/nuxt/nuxt/commit/33a581ce7))
- **vite:** Set define for `NODE_ENV` and `__VUE_PROD_DEVTOOLS__` ([#​35444](https://github.com/nuxt/nuxt/pull/35444))
- **vite:** Avoid emitted ssr styles file name collisions ([#​35432](https://github.com/nuxt/nuxt/pull/35432))
- **nuxt:** Avoid syncing route too early when reused page remounts ([#​35440](https://github.com/nuxt/nuxt/pull/35440))
- **nuxt:** Skip showing route errors for crawlers ([#​35411](https://github.com/nuxt/nuxt/pull/35411))
- **nuxt:** Keep nested layout on deferred route during suspended layout switch ([#​35443](https://github.com/nuxt/nuxt/pull/35443))
- **nuxt:** Restore deferred query route before page is mounted ([#​35442](https://github.com/nuxt/nuxt/pull/35442))
- **kit,nuxt:** Resolve auto-import type paths through shared resolver ([#​35451](https://github.com/nuxt/nuxt/pull/35451))
- **nuxt:** Make islands hash backwards-compatible ([#​35452](https://github.com/nuxt/nuxt/pull/35452))
- **vite:** Give ssr its own hmr websocket port on vite 8.1 ([#​35458](https://github.com/nuxt/nuxt/pull/35458))
- **nuxt:** Auto-import `$fetch` where possible ([#​35581](https://github.com/nuxt/nuxt/pull/35581))
- **nuxt:** Update unctx to v3 ([#​35541](https://github.com/nuxt/nuxt/pull/35541))
- **nitro:** Point cache driver at `.mjs` runtime output ([#​35263](https://github.com/nuxt/nuxt/pull/35263))
- **nuxt:** Add `head.push` for client side ([#​35498](https://github.com/nuxt/nuxt/pull/35498))
- **nitro:** Only reload nitro after initial nitro build ([30a573733](https://github.com/nuxt/nuxt/commit/30a573733))
- **nuxt:** Pass nuxt `rootDir` to typed-router context ([#​35565](https://github.com/nuxt/nuxt/pull/35565))
- **nuxt:** Type `__nuxt_error` marker on `NuxtError` ([06e976d2e](https://github.com/nuxt/nuxt/commit/06e976d2e))
- **kit:** `noUncheckedSideEffectImports` (v5) + disable `libReplacement` ([#​35548](https://github.com/nuxt/nuxt/pull/35548))
- **nuxt:** Node10 compatible subpath exports ([a216d732d](https://github.com/nuxt/nuxt/commit/a216d732d))
- **nuxt:** Unwrap module mutation trackers from options after installing ([#​35574](https://github.com/nuxt/nuxt/pull/35574))
- **nuxt:** Entity-encode redirect body to preserve query separators ([#​35486](https://github.com/nuxt/nuxt/pull/35486))
- **rspack,webpack:** Use async entry in dev to avoid circular import ([#​35586](https://github.com/nuxt/nuxt/pull/35586))
- **nuxt:** Tolerate missing dirs in parcel watcher ([#​35585](https://github.com/nuxt/nuxt/pull/35585))
- **nuxt:** Respect saved scroll position on same-page back navigation from hash ([#​35608](https://github.com/nuxt/nuxt/pull/35608))
- **nuxt:** Resolve unctx helper to an absolute path for server bundling ([#​35602](https://github.com/nuxt/nuxt/pull/35602))
- **nuxt:** Apply `unctx` transform marker through `magic-string` ([#​35618](https://github.com/nuxt/nuxt/pull/35618))
- **schema:** Enable HMR for defineNuxtComponent in JSX ([#​35620](https://github.com/nuxt/nuxt/pull/35620))
- **nuxt:** Evict stale forwarded prefetch hints and fix key mismatch ([#​35621](https://github.com/nuxt/nuxt/pull/35621))
- **nuxt:** Re-add computeIslandHash for backwards compatibility ([353164bb5](https://github.com/nuxt/nuxt/commit/353164bb5))
- **nitro:** Correct nitro runtime config type key ([bc3b53add](https://github.com/nuxt/nuxt/commit/bc3b53add))
- **schema:** Resolve Vite cache directory from workspace ([#​35626](https://github.com/nuxt/nuxt/pull/35626))
- **nitro:** Externalise dev SSR entry to avoid slow rollup re-parse ([ff42c54a7](https://github.com/nuxt/nuxt/commit/ff42c54a7))
- **nuxt:** Drop extension on diagnostics import ([c699cc0de](https://github.com/nuxt/nuxt/commit/c699cc0de))
- **nitro:** Write prerender cache files atomically ([#​35619](https://github.com/nuxt/nuxt/pull/35619))
- **nitro:** Don't import `node:process` in diagnostics ([d73b5df6d](https://github.com/nuxt/nuxt/commit/d73b5df6d))
- **nuxt:** Apply latest navigation on concurrent `navigateTo` in built-in router ([#​35631](https://github.com/nuxt/nuxt/pull/35631))
- **nuxt:** Update client URL to match SSR on fatal middleware error ([#​35637](https://github.com/nuxt/nuxt/pull/35637))
##### 💅 Refactors
- **nitro,nuxt:** Add explicit return types to runtime files ([#​35139](https://github.com/nuxt/nuxt/pull/35139))
- **nuxt:** Resolve builder earlier + skip watcher for `nuxt prepare` ([#​35141](https://github.com/nuxt/nuxt/pull/35141))
- **nuxt:** Delete cleared keys in `clearNuxtData` & `clearNuxtState` ([#​35142](https://github.com/nuxt/nuxt/pull/35142))
- **nuxt:** Use jiti `fsCache` instead of deprecated `cache` ([#​35268](https://github.com/nuxt/nuxt/pull/35268))
- **nitro:** Use walkResolver instead of deprecated resolveUnrefHeadInput ([#​35267](https://github.com/nuxt/nuxt/pull/35267))
- **nuxt:** Add type predicates ([#​35208](https://github.com/nuxt/nuxt/pull/35208))
- **vite:** Type vite-node-runner error data and resolve `#vite-node` types from source ([#​35334](https://github.com/nuxt/nuxt/pull/35334))
- **nuxt:** Split head runtime plugin by environment ([#​35389](https://github.com/nuxt/nuxt/pull/35389))
- **nuxt:** Use `rolldown/utils` instead of oxc-\* dependencies ([#​34983](https://github.com/nuxt/nuxt/pull/34983))
- **kit,vite,webpack,nitro,schema:** `ssrStyles` build output => lazy code provider ([#​35601](https://github.com/nuxt/nuxt/pull/35601))
- **nuxt,nitro:** Use dev exports for no-build type-checking ([#​35605](https://github.com/nuxt/nuxt/pull/35605))
- **rspack:** Use `rsbuild` to power rspack builder ([#​35489](https://github.com/nuxt/nuxt/pull/35489))
- **vite:** Extract TemplateHMRPlugin from DevServerPlugin ([#​35579](https://github.com/nuxt/nuxt/pull/35579))
##### 📖 Documentation
- Add builder guide ([#​35576](https://github.com/nuxt/nuxt/pull/35576))
- Correct and improve `useAsyncData` and `useFetch` ([#​35528](https://github.com/nuxt/nuxt/pull/35528))
- Improving testing documentation for better understanding ([#​35526](https://github.com/nuxt/nuxt/pull/35526))
- Fix incorrect transform signature in useAsyncData/useFetch type docs ([#​35403](https://github.com/nuxt/nuxt/pull/35403))
- Add minimal version badges to API documentation ([#​34485](https://github.com/nuxt/nuxt/pull/34485))
- Clarify usage of await with useFetch ([#​33745](https://github.com/nuxt/nuxt/pull/33745))
- Add badges for new features ([1839fe01d](https://github.com/nuxt/nuxt/commit/1839fe01d))
- Remove `enabled` from `useFetch` docs ([bf7810600](https://github.com/nuxt/nuxt/commit/bf7810600))
- Add tip about prerendering registration for pages ([#​35635](https://github.com/nuxt/nuxt/pull/35635))
- Clarify that `useFetch` keys are unique per call site ([#​35645](https://github.com/nuxt/nuxt/pull/35645))
- Clarify useFetch custom data error ([#​35647](https://github.com/nuxt/nuxt/pull/35647))
##### 📦 Build
- Use `tsdown` to build project ([#​35179](https://github.com/nuxt/nuxt/pull/35179))
##### 🏡 Chore
- Update lockfile ([92087005f](https://github.com/nuxt/nuxt/commit/92087005f))
- Handle cleaning nuxt build dist manually ([aaafe9879](https://github.com/nuxt/nuxt/commit/aaafe9879))
- Upgrade oxc/tsdown/rolldown deps together ([28173926d](https://github.com/nuxt/nuxt/commit/28173926d))
- Use `vp` in nightly release script ([97b56d98d](https://github.com/nuxt/nuxt/commit/97b56d98d))
- Update lockfile for tsdown build and dev exports ([5d9cd2d67](https://github.com/nuxt/nuxt/commit/5d9cd2d67))
- Update bundle size snapshots ([b9d353516](https://github.com/nuxt/nuxt/commit/b9d353516))
- Allow node version updates ([#​35297](https://github.com/nuxt/nuxt/pull/35297))
- Update bundle size ([d37b13b60](https://github.com/nuxt/nuxt/commit/d37b13b60))
##### ✅ Tests
- Strip host path from server bundle before measuring size ([#​35316](https://github.com/nuxt/nuxt/pull/35316))
- Run playwright tests in dev mode too ([#​35329](https://github.com/nuxt/nuxt/pull/35329))
- **nuxt:** Benchmark dev `loadNuxt` startup ([#​35382](https://github.com/nuxt/nuxt/pull/35382))
- Update bundle size snapshot ([64a834281](https://github.com/nuxt/nuxt/commit/64a834281))
- Use h3 v1 syntax in test ([1ea2eec07](https://github.com/nuxt/nuxt/commit/1ea2eec07))
- Use `experimental.attachDebugInfo` ([7c2397ae6](https://github.com/nuxt/nuxt/commit/7c2397ae6))
- Update assertions ([55f7ea025](https://github.com/nuxt/nuxt/commit/55f7ea025))
- Pack with pnpm before running attw to honour publishConfig ([e2f0dd69d](https://github.com/nuxt/nuxt/commit/e2f0dd69d))
- Reduce flakiness on hydration tests ([#​35457](https://github.com/nuxt/nuxt/pull/35457))
- Improve test stability on macos ([#​35462](https://github.com/nuxt/nuxt/pull/35462))
- Force disable `test` mode for bundle size tests ([#​35558](https://github.com/nuxt/nuxt/pull/35558))
- Pin typescript in basic-types fixture to dedupe vue ([a3b824918](https://github.com/nuxt/nuxt/commit/a3b824918))
- **webpack:** Cover Vue loader scope hashes ([824daffa0](https://github.com/nuxt/nuxt/commit/824daffa0))
- **builders:** Clean up Vue loader fixtures ([ca18affc3](https://github.com/nuxt/nuxt/commit/ca18affc3))
- Reenable webpack/rspack dev tests ([#​35571](https://github.com/nuxt/nuxt/pull/35571))
- Await `expectNoClientErrors` ([9f3123f60](https://github.com/nuxt/nuxt/commit/9f3123f60))
- Mark tests as succeeding for rspack/webpack ([513ad306e](https://github.com/nuxt/nuxt/commit/513ad306e))
- Add support for non-ascii filenames in public assets ([#​35636](https://github.com/nuxt/nuxt/pull/35636))
- Minify server bundles in bundle size snapshot ([#​35638](https://github.com/nuxt/nuxt/pull/35638))
- Run matrix-independent suites once across the fixture matrix ([#​35646](https://github.com/nuxt/nuxt/pull/35646))
- Mark webpack/rspack tests as passing on 4.x ([3ae095b29](https://github.com/nuxt/nuxt/commit/3ae095b29))
##### 🤖 CI
- Reenable bundle size tests 😱 ([#​35279](https://github.com/nuxt/nuxt/pull/35279))
- Only run workflow in nuxt org ([#​35377](https://github.com/nuxt/nuxt/pull/35377))
- Skip entire job, not just step ([#​35378](https://github.com/nuxt/nuxt/pull/35378))
- Error handling for webhook triggers ([#​35379](https://github.com/nuxt/nuxt/pull/35379))
- Update agentscan ([#​35397](https://github.com/nuxt/nuxt/pull/35397))
- Only run benchmark tests in Nuxt org ([#​35414](https://github.com/nuxt/nuxt/pull/35414))
- Don't auto-close team prs 🫣 ([cd965f569](https://github.com/nuxt/nuxt/commit/cd965f569))
- Use vite-plus to manage pnpm ([#​35570](https://github.com/nuxt/nuxt/pull/35570))
- Use parallel keyword for concurrent workflow steps ([#​35448](https://github.com/nuxt/nuxt/pull/35448))
- Handle missing base branch (for stacked prs) ([8d3daa34e](https://github.com/nuxt/nuxt/commit/8d3daa34e))
- Unlink issues from autoclosed PRs ([#​35615](https://github.com/nuxt/nuxt/pull/35615))
##### ❤️ Contributors
- Jordan Labrosse ([@​Jorgagu](https://github.com/Jorgagu))
- Luke Nelson ([@​luc122c](https://github.com/luc122c))
- Florian Heuberger ([@​Flo0806](https://github.com/Flo0806))
- Daniel Roe ([@​danielroe](https://github.com/danielroe))
- Yannik Schröder ([@​yschroe](https://github.com/yschroe))
- Mateleo ([@​Mateleo](https://github.com/Mateleo))
- Matej Černý ([@​cernymatej](https://github.com/cernymatej))
- Eduardo San Martin Morote ([@​posva](https://github.com/posva))
- Darlan José Batista do Prado ([@​DarlanPrado](https://github.com/DarlanPrado))
- Ulrich-Matthias Schäfer ([@​Fuzzyma](https://github.com/Fuzzyma))
- Bochkarev Ivan ([@​Ibochkarev](https://github.com/Ibochkarev))
- Julien Huang ([@​huang-julien](https://github.com/huang-julien))
- Damian Głowala ([@​DamianGlowala](https://github.com/DamianGlowala))
- KazariAI ([@​KazariAI](https://github.com/KazariAI))
- Fahad Khan ([@​oniChan1226](https://github.com/oniChan1226))
- skoenfaelt ([@​skoenfaelt](https://github.com/skoenfaelt))
- Cynthia Rey ([@​cyyynthia](https://github.com/cyyynthia))
- meomking ([@​CaptainWang98](https://github.com/CaptainWang98))
- antlis ([@​antlis](https://github.com/antlis))
- Néstor ([@​Nsttt](https://github.com/Nsttt))
- Issayah ([@​VividLemon](https://github.com/VividLemon))
- Alexander Lichter ([@​TheAlexLichter](https://github.com/TheAlexLichter))
- Anoesj Sadraee ([@​Anoesj](https://github.com/Anoesj))
- Quentin Macq ([@​quentinmcq](https://github.com/quentinmcq))
- raminjafary ([@​raminjafary](https://github.com/raminjafary))
- Kurt Medley ([@​HeavyMedl](https://github.com/HeavyMedl))
- isksss ([@​isksss](https://github.com/isksss))
- Matteo Gabriele ([@​MatteoGabriele](https://github.com/MatteoGabriele))
- Max ([@​onmax](https://github.com/onmax))
- Harlan Wilton ([@​harlan-zw](https://github.com/harlan-zw))
</details>
---
- [x] <!-- rebase-check -->If you want to rebase/retry this PR, check this box
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4yOC4wIiwidXBkYXRlZEluVmVyIjoiNDQuMjkuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIiwic2VjdXJpdHkiXX0=-->
Renovate Bot
changed title from chore(deps): update dependency nuxt to v4.5.1 [security] to chore(deps): update dependency nuxt to v4.5.1 [security] - autoclosed2026-08-14 23:01:34 +00:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
This PR contains the following updates:
4.4.8→4.5.1Nuxt: Unauthorized Component Instantiation via Server Island Props
CVE-2026-71318 / GHSA-48hr-524c-v5w3
More information
Details
Impact
Nuxt server islands accept props via the
/__nuxt_island/endpoint. When an application has a server island component that forwards props directly into Vue's dynamic component resolution (<component :is>,resolveDynamicComponent, orh()), an attacker can pass a plain string value (rather than a component definition) to instantiate any globally-registered Vue component or any native HTML element.For example:
...resolves and renders
SomeGlobalComponentif it is globally registered, even though the attacker should only be able to drive props for the island's declared component. Similarly,{ "as": "iframe" }renders an<iframe>element.Unlike the primary RCE vector (GHSA-9473-5f9j-94wq), this does not require
vue.runtimeCompilerto be enabled. A plain string prop is sufficient to trigger component resolution. Thetemplate/renderkey guard that addresses the RCE vector does not block plain string values.Some component libraries expose a polymorphic
as/asChildprop that forwards its value into<component :is>;@nuxt/ui(viareka-ui) is a widely used example. An application is affected if such a component receives the attacker-controlled value inside a server island. Note this does not require explicit prop forwarding: island props the island component does not declare fall through as attributes onto its single root element, so an island whose root is areka-ui/@nuxt/uicomponent receives the attacker'sasvalue implicitly. Unlike the RCE vector, novue.runtimeCompileris required, which makes this vector reachable in more configurations. These libraries are not themselves vulnerable; they are noted only because they commonly provide the dynamic-component sink. Installing@nuxt/uidoes not by itself register any component as a server island: the application must define the island (a.server.vuefile).Mitigating factors
<component :is>,resolveDynamicComponent,h(), or a polymorphicas/asChildprop), either explicitly or via attribute fallthrough when the island's root is such a component.RouterViewandRouterLink(both registered globally by the pages router plugin, which runs even in component islands), plus anycomponents/global/component and any component a module registers globally.RouterLinkin particular renders an attacker-influenced<a>(and, because an island that declares no props forwards all props,toand otherRouterLinkprops ride the same fallthrough). Vue built-ins (Transition,KeepAlive,Teleport,Suspense) and Nuxt auto-imports (ClientOnly,NuxtLink,NuxtPage, etc.) are NOT in the island app's global registry and cannot be resolved this way; an unresolved name instead renders as a native HTML element (the element-injection half of this issue).inheritAttrs: falseon it, prevents an undeclaredasfrom falling through to a polymorphic root and neutralizes this vector.template/rendercompilation).Affected versions
Nuxt
>=3.1.0 <3.21.10and>=4.0.0 <4.5.1, with component islands active. The island prop-forwarding behavior has existed since server islands were introduced in v3.1.0, and this vector does not depend onvue.runtimeCompiler. Nuxt 2 is not affected.Note the patch (below) closes the implicit attribute-fallthrough path, which is the majority case. An island that explicitly forwards an untrusted prop into dynamic component resolution (or forwards it under a prop name other than
as) remains the application's responsibility in every version; see Workarounds.Patches
Fixed in
nuxt@4.5.1andnuxt@3.21.10. Patched releases reject a top-levelasisland prop (HTTP 400 at the/__nuxt_island/endpoint). This closes the implicit path: island props an island does not declare fall through as attributes onto its single root, so a top-levelaswould otherwise reach a polymorphic root component'sasprop (thereka-ui/@nuxt/uiconvention) and drive dynamic component resolution without the author binding it. Rejecting the top-levelasprop blocks that fallthrough while leaving nested data and other prop names untouched.The framework deliberately does not attempt to block every case: it cannot safely tell a string used as data from one used as a component selector, and it has no island-local hook into Vue's
h()orresolveDynamicComponent(). An island that explicitly forwards an untrusted value into<component :is>/h()/resolveDynamicComponent(), or that forwards it under a different polymorphic prop name, is therefore not covered by the patch and must follow the guidance below. The Nuxt documentation now warns against this.Workarounds
Upgrade to
nuxt@4.5.1ornuxt@3.21.10. That upgrade also removes the related object-prop RCE (GHSA-9473-5f9j-94wq). In addition, in any version:<component :is>,resolveDynamicComponent, orh(). Map an untrusted discriminator through a closed allowlist of imported component definitions instead of passing the raw prop value.inheritAttrs: falseon it, so request input cannot fall through to a polymorphic root component.Severity
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:NReferences
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
Nuxt dev server discloses project root and workspace UUID via the Chrome DevTools workspace endpoint
CVE-2026-72744 / GHSA-7c4v-fwgw-9rf7
More information
Details
Impact
When a Nuxt dev server is bound to a network-reachable interface (for example
nuxt dev --hostfor on-device testing), the default-enabled Chrome DevTools workspace endpointGET /.well-known/appspecific/com.chrome.devtools.jsonreturns the absolute project root (workspace.root, i.e.rootDir) and a persistent per-project workspace UUID.GHSA-rq7w-g337-39qqadded a gate (isLocalDevRequest) intended to restrict this endpoint to local requests, but that gate is header-based: it trusts request metadata rather than the connected peer address. A request with noSec-Fetch-Site,Origin, andRefererheaders (normal for a non-browser client such ascurl) is treated as local, and theHostallow-list is compared against the attacker-suppliedHostheader. As a result, any unauthenticated host that can reach the dev server on the LAN can retrieve the project's absolute filesystem path and workspace UUID, for example withcurl -H 'Host: localhost' http://<dev-host-lan-ip>:3000/.well-known/appspecific/com.chrome.devtools.json.This is information disclosure only: there is no file read, file write, or code execution reachable from the endpoint. It requires the dev server to be reachable beyond loopback and
experimental.chromeDevtoolsProjectSettingsto be enabled (it defaults totrue). Production builds are unaffected, because the endpoint is registered only as a development handler.Patches
Fixed in
nuxt@4.5.1andnuxt@3.21.10. The endpoint now additionally requires the connected TCP peer to be a loopback address, verified from the socket rather than from request headers, so a non-loopback LAN client is rejected regardless of theHost,Origin,Referer, orSec-Fetch-*headers it sends. The shared header-based check is left unchanged, so the CSRF / same-origin behaviour that other dev handlers rely on is preserved. After this fix, Chrome DevTools workspace auto-mapping only works when the browser reaches the dev server over loopback (localhost/127.0.0.1/::1), which matches the feature's intent (the browser and dev server sharing a filesystem).Workarounds
experimental.chromeDevtoolsProjectSettings: falseinnuxt.config.Severity
CVSS:4.0/AV:A/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:NReferences
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
Nuxt: Server-Side Remote Code Execution via Runtime Template Injection in Nuxt Server Island Props
CVE-2026-71320 / GHSA-9473-5f9j-94wq
More information
Details
Impact
Nuxt server islands accept props via the
/__nuxt_island/endpoint. Whenvue.runtimeCompiler: trueis enabled (off by default) and the application has a server island component that forwards props into Vue's dynamic component resolution (<component :is>,resolveDynamicComponent, orh()), an attacker can inject atemplatekey into the island props to achieve server-side remote code execution in the Nitro process.Vue's runtime template compiler compiles and executes the attacker-controlled
templatein the server process. The same primitive also works on the client side when the runtime compiler is active there, though the server-side path is the primary concern.Some component libraries expose a polymorphic
as/asChildprop that forwards its value into<component :is>;@nuxt/ui(viareka-ui) is a widely used example. An application is affected if such a component receives the attacker-controlled value, providedvue.runtimeCompileris also enabled. Note this does not require the island author to explicitly forward a prop: island props that the island component does not declare fall through as attributes onto its single root element (standard Vue attribute inheritance), so an island whose root is a polymorphic component receives the attacker'sasvalue implicitly. These libraries are not themselves vulnerable; they are noted only because they commonly provide the dynamic-component sink.Common configurations that satisfy the preconditions
The flaw is in Nuxt core. The library below is not itself vulnerable; it is noted because it commonly provides the dynamic-component sink an application might inadvertently expose.
@nuxt/ui(via its underlyingreka-uiprimitives) exposes a polymorphicas/asChildprop that is forwarded into Vue's dynamic-component resolution. Installing@nuxt/uidoes not by itself register any component as a server island. Exploitation requires the application to define an island component (a.server.vuefile) whose rendered output puts the attacker-controlled value on such a component'sas/asChildprop; withvue.runtimeCompiler: true, the attacker-controlledtemplateis then compiled and executed. Because undeclared island props fall through as attributes to the single root, this can happen without any explicit binding: an island whose root is areka-ui/@nuxt/uicomponent is enough. An example vulnerable island (theasvalue falls through toUButton, no explicit forwarding needed):The island URL hash (
/__nuxt_island/<Name>_<hash>.json) is a deterministic (unsalted) content hash, not an authentication token. It provides integrity relative to the URL but is not a security boundary: an attacker who knows the component name and desired props can compute a valid hash.Mitigating factors
vue.runtimeCompileris off by default in Nuxt. The vast majority of Nuxt applications are not affected.<component :is>,resolveDynamicComponent,h(), or a polymorphicas/asChildprop). This can occur explicitly or via attribute fallthrough when the island's root is such a component.Affected versions
Nuxt
>=3.4.0 <3.21.10and>=4.0.0 <4.5.1, and only whenvue.runtimeCompiler: trueand component islands are active. Earlier versions did not allow the Vue compiler to be enabled in the server bundle (the compiler dependencies have been mock-aliased on the server since v3.0.0-rc.1), so the runtime-compilation path is not reachable. Nuxt 2 is not affected (no server islands).Patches
When
vue.runtimeCompileris enabled, island requests whose decoded props contain atemplatekey at any depth are rejected with an HTTP 400 and a diagnostic suggesting the author rename the prop or disable the runtime compiler. The guard is gated on the runtime compiler being enabled, so the default configuration (compiler off) is unaffected and legitimate props that merely contain atemplatefield (for example CMS content) continue to render. Arenderkey is not rejected: island props arrive as JSON, so arendervalue can only be an inert string, which Vue ignores.Fixed in
nuxt@4.5.1and backported tonuxt@3.21.10.Workarounds
Upgrade to
nuxt@4.5.1ornuxt@3.21.10. If you cannot immediately upgrade, you can mitigate by:vue.runtimeCompileris set tofalse(the default).<component :is>,resolveDynamicComponent, orh()without sanitization./__nuxt_island/that URL-decodes and JSON-parses thepropsvalue and blocks any object property namedtemplateorrenderin the decoded island props, inspecting both query and body for all methods. Note: this only covers direct and browser-originated island requests; initial-SSR internal island renders do not transit the edge and a WAF alone does not close the vector.Severity
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:HReferences
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
Nuxt: Unauthenticated CPU exhaustion parsing and hashing the Nuxt island endpoint body before hash validation
CVE-2026-71321 / GHSA-9pgf-384g-p7mv
More information
Details
Impact
The internal island renderer endpoint (
/__nuxt_island/...) decodes and hashes attacker-controlled request input before it validates the URL-resident hash. An unauthenticatedPOST /__nuxt_island/<name>_<anything>.jsonwith a large JSON body (for example ~4.6 MB / 150k keys) is fully read,destr-parsed, and run throughohashbefore the request is rejected with a 400. Because Nitro runs on a single event loop, this both wastes CPU on the doomed request and delays every concurrent request. A low request rate is enough to degrade or stall the server. No valid hash and no authentication are required.Patches
Fixed in
nuxt@4.5.1andnuxt@3.21.10. The island handler now enforces a raw body-size cap (413) and a JSON nesting-depth cap (400) before parsing or hashing, so oversized or deeply nested input is rejected cheaply.Workarounds
Put a small request-body limit in front of
/__nuxt_island/at your reverse proxy / edge (islands legitimately send only a compact props payload), or disable server components if unused.Severity
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:HReferences
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
Nuxt: Unauthenticated out-of-memory crash via unbounded v-for expansion in island rendering
CVE-2026-71314 / GHSA-hxcr-hm88-mpq6
More information
Details
Impact
An unauthenticated attacker can crash a Nuxt server that renders any island / server component containing a
v-forover a prop (for examplev-for="n in count"or a<slot v-for>). Because the island URL hash is a non-secret digest of the request, the attacker can compute a valid hash for arbitrary props and send the iterated prop as a large integer. The server then expands thev-forto that many nodes during SSR, allocating memory proportional to the attacker's number. Reporter figures:count=8000000produced a 142.9 MB response;count=40000000(anditems=4000000on a slot list) produced an out-of-memory crash of the worker from a single ~130-byte request. Both the plainv-forpath (Vue'sssrRenderList) and the slot path (vforToArray) are affected.Patches
Fixed in
nuxt@4.5.1andnuxt@3.21.10. Island/server-componentv-forsources are now clamped to a maximum iteration count (MAX_VFOR_LENGTH = 100000) at the render boundary, covering the plain path, the<slot v-for>element, and thevforToArrayslot-props helper. Combined with the body-size cap (GHSA-9pgf-384g-p7mv), a single island render can no longer allocate without bound regardless of whichv-forpath is used or whether the prop arrives as an integer or an array.Workarounds
Avoid
v-fordirectly over an unclamped prop in server components, or clamp the count in the component (v-for="n in Math.min(count, 1000)"). A body-size limit in front of/__nuxt_island/only mitigates array-shaped inputs, not the integer-amplification case.References
packages/nuxt/src/app/components/vfor.tspackages/nuxt/src/components/plugins/islands-transform.tspackages/nuxt/src/app/components/utils.ts(vforToArray)Severity
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:HReferences
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
Nuxt route rules silently dropped for mixed-case paths, bypassing appMiddleware auth gates (incomplete fix for CVE-2026-53721)
CVE-2026-71315 / GHSA-hxvh-4h3w-prp9
More information
Details
Impact
Nuxt matches route rules case-insensitively by default (mirroring vue-router's default
sensitive: falserouting). The fix for GHSA-mm7m-92g8-7m47 / CVE-2026-53721 lowercased the lookup path before matching route rules, but the route-rule keys compiled into the matcher were left verbatim. As a result, any route rule whose key contains an uppercase character (for example/Admin,/Dashboard/**, or the rules Nuxt derives from PascalCase/camelCase page files such aspages/Admin.vue) never matches, because every lookup is folded to lowercase while the key stays mixed-case.vue-router still serves the page case-insensitively, so the page renders with none of its Nuxt route-rule protections applied. The most serious consequence is an authorization bypass: an
appMiddlewarerule used as an auth gate (routeRules: { '/Admin/dashboard': { appMiddleware: 'auth' } }) is dropped, and/Admin/dashboard,/admin/dashboard, and/ADMIN/dashboardall render the protected page (and its SSR-fetched data) to an unauthenticated visitor instead of redirecting to login. The same gap drops Nuxt's other app-side route-rule behaviours for mixed-case keys, including the client redirect middleware, the app-sidessr: falsedecision,prerender, and payload handling.Patches
Fixed in
nuxt@4.5.1(4.x) andnuxt@3.21.10(3.x). The route-rule matcher now case-folds the compiled keys the same way it folds the lookup path, so key and lookup normalisation are symmetric. Both sides are gated onrouter.options.sensitive: withsensitive: true(case-sensitive routing) configured casing is preserved on both sides.Scope note: server-emitted per-route
headers, serverredirect, andproxyare matched by Nitro's own case-sensitive route-rule matcher, not by Nuxt's app-level matcher. They are unchanged by this advisory. The fix covers the app-level protections Nuxt owns (appMiddleware,appLayout, the client redirect middleware, the appssrdecision,prerender, and payload).Workarounds
If you cannot upgrade immediately, any one of:
routeRules(and name your page files) in lowercase, so the keys already match the folded lookup path.router: { options: { sensitive: true } }so routing and route-rule matching are both case-sensitive and exact (requests must then use the exact casing).Severity
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:NReferences
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
Nuxt runtime payload cache discloses another user's SSR data across users and to unauthenticated clients
CVE-2026-71316 / GHSA-wm8w-6qjm-cv43
More information
Details
Impact
When a page is covered by
routeRulescache/swr/isr, Nuxt enables runtime payload extraction and serves/<page>/_payload.json. On affected versions the renderer stored the SSR payload in the sharedcache:nuxt:payloadstorage under a path-only key (no cookie,authorization, orcache.variesdimension) and, on a later payload request, returned the cached entry before route middleware / page guards ran again.As a result, once any authenticated user warms a protected, cached page, a subsequent
GET /<page>/_payload.jsonfrom an unauthenticated client or a different authenticated user receives the first user's payload: the full SSR data for that route, including anything loaded viauseFetch/useAsyncData(for example/api/me: profile, tenant, billing, token-like values). The HTML response stays correctly varied and protected; only the extracted payload leaks. Both cross-user (A warms, B receives A) and unauthenticated disclosure are exploitable.cache.variesdoes not mitigate it, because the payload cache ignoresvaries.Introduced when runtime payload extraction landed for cached routes (#34410); the regression is specific to the 4.x line, where the runtime
cache:nuxt:payloadstorage was added and theimport.meta.prerendergate on the payload-cache read/writes was dropped. The 3.x line shipped the same feature with the gate intact and is not affected.Patches
Fixed in
nuxt@4.5.1. Runtime payload-cache reads and writes are again confined to prerendering (import.meta.prerender); at runtime,/<page>/_payload.jsonfollows the normal render path so route middleware,routeRules.appMiddleware, and page guards run for the current request.main/ v5 and the3.xline already had this property, so 3.x is not affected.Workarounds
experimental.payloadExtraction: false(reporter-validated): the standalone/_payload.jsonendpoint returns 404 and the page still serves a 200 with an inline payload.cache/swr/isrto authenticated pages that render user-specific SSR data./**/_payload.jsonat a proxy / CDN.Severity
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:NReferences
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
Release Notes
nuxt/nuxt (nuxt)
v4.5.1Compare Source
It fixes server-side RCE and unauthorized component instantiation via server island props, a route rule authorization bypass, server component DoS, cross-user payload disclosure on cached pages, and dev server path disclosure. Refreshing your lockfile also pulls in
@nuxt/devtools@3.3.1, which fixes a separate critical development-only RCE.If you already upgraded for the earlier route rule advisory (CVE-2026-53721), you still need this release: one of the fixes addresses a regression introduced by that fix.
If you use the
cache,swrorisrroute rules, purge any CDN or edge cache after upgrading; a leaked_payload.jsonmay already be cached upstream.Full details: Nuxt Security Patch Releases and GitHub Security Advisories.
👉 Changelog
compare changes
🔥 Performance
vue.optionsApiand disable it for v5+ (#35791)ssr: false(#35782)🩹 Fixes
useRoutein detached effect scope (#35659)nameorpathwhen reusing an existing page inpages:extend(#35661)useFetchmethod inference (#35671)force-cache(#35672).mtsfile extension in resolver (#33845)@unhead/vue/*from nuxt's dependency tree (#35690)$fetchwith nitro's$Fetch(#35704)vue-routerwhen there are island pages (#35739)h3that pins it to the version nuxt depends on (#35774)fromcannot be resolved (#35799)app.buildAssetsDir(#35833)templateisland prop under runtime compiler (ee6c84633)asprop for islands (581651ff3)💅 Refactors
semverwithverkit(#35713)📖 Documentation
codeSplitting: false(#35683)onPrehydrateexample comment (#35684)runtimeConfigenv var casting edge cases (#35709)nuxtrather thannuxi(8891e179c)runtimeCompilersecurity best practices (449b63ab1)📦 Build
.tsfile extension fromruntime/imports (#35689)🏡 Chore
@nuxt/telemetryin knip (9824d4f10)@nuxt/devtoolsto v3.3.1 (#35815)✅ Tests
_routein gotoPath (ca92d082b)🤖 CI
knipjob (58f68bd64)❤️ Contributors
v4.5.0Compare Source
📣 Some News
Preparing for Nuxt 5
A good chunk of this release is (hopefully) invisible plumbing for Nuxt 5. We've moved onto the latest major versions of several core dependencies (
unheadv3,unctxv3, and Vite 8), switched the framework's own build over totsdown, and introduced a stablenuxt/*build output contract with dev exports so that type-checking in the Nuxt monorepo works without a build step (#35463, #35605).Much of this is working to shrink the gap between v4 and v5 internally, so that the migration will be as boring as possible.
With the release of Nuxt v4.5, our focus as a team will turn to stabilising Nuxt v5 and creating compatibility utilities to make the upgrade as smooth as possible.
Nuxt 3 End-of-Life
Nuxt 3 reaches end-of-life on July 31, 2026, so this is one of the last few 3.x releases we'll ship. If you're still on v3, now is a great time to move across. Most people told us the v3 to v4 upgrade was smooth, and we've kept the upgrade guide up to date.
Alongside v4.5.0 we're publishing a maintenance patch for the 3.x line (v3.21.9) with the compatible bug fixes and smaller improvements from this release backported. The headline items here (Vite 8, Rspack 2,
unheadv3,unctxv3) are major upgrades and stay v4-only, so 3.x remains stable as it approaches end-of-life.👀 Highlights
Nuxt 4.5 is a big one. This release ships three major upgrades to the build layer (Vite 8, Rspack 2, and a brand new Rsbuild-powered pipeline for the Rspack builder), an experimental SSR streaming mode, a handful of new composables and conventions, and a lot of groundwork that brings us closer to Nuxt 5.
There's a lot here, so grab a coffee. ☕️
⚡️ Vite 8
Nuxt now runs on Vite 8 (#34256). This brings faster cold starts, the latest Rolldown-powered internals, and many upstream improvements from the Vite team.
For most apps this is a transparent upgrade. If you have custom Vite plugins or config, it's worth skimming the Vite migration guide to check for anything that affects you.
🦀 Rspack 2 and Rsbuild
If you use the Rspack builder, this release is a substantial upgrade. We've moved to Rspack 2 (#34929), which is faster and lighter, and rebuilt the builder on top of
@rsbuild/core(#35489).The public surface stays the same. You still opt in with
builder: 'rspack'and the existingrspack:*hooks continue to work:Under the hood, though, a lot has changed for the better:
webpack-dev-middlewareandwebpack-hot-middleware(#35575).🌊 Experimental SSR Streaming
This is one I'm particularly excited about. You can now enable SSR streaming to dramatically improve Time to First Byte (#34411). Instead of buffering the whole rendered page and sending it in one go, Nuxt flushes the HTML shell (your
<head>, styles, preload hints, and entry scripts) immediately, then streams the body as Vue renders it.Streaming is automatically disabled for bots and crawlers so search engines still receive fully-rendered HTML. You can tune which user agents count as crawlers, and you can opt individual routes out:
There's one thing worth understanding before you turn it on. Because streaming commits the HTTP status and headers with the very first byte, anything that mutates the response after rendering has begun (a
setResponseStatus()in a<script setup>, a cookie write during middleware, and so on) can't reach the client. Nuxt handles the common cases for you: routes withredirect,cache,isr,swr,noScripts, orssr: falserules automatically fall back to the buffered renderer, and in development we log a warning naming any dropped mutations so nothing fails silently.📖 SSR streaming documentation
🩺 Stable Error Codes
This one I'm really happy about. Nuxt now has a stable error code system (#35429). Warnings and errors raised during build and at runtime now carry a stable code (like
NUXT_E1001orNUXT_B5001), a short explanation of why it happened, and a concrete fix to try.Every code is greppable and bookmarkable, and the ones that need more than a one-line fix link straight to a dedicated docs page. For example, the classic "a composable was called outside a Nuxt context" now surfaces as
NUXT_E1001with the why/fix inline and a docs page explaining the context rules and how to userunWithContext().To keep production output lean, the verbose why/fix text is stripped from production builds, leaving just the stable code.
This is the foundation for much better error messages across Nuxt, and we'll keep migrating existing warnings and errors onto it over the coming releases. If you've ever squinted at a cryptic Nuxt message, this is for you.
🎨
useLayoutComposableThere's a new
useLayoutcomposable for reading the layout that's been resolved for the current route (#35623). Previously there was no clean, reactive way to ask "which layout is this page using?" from within a component.It returns a read-only computed ref, so it stays in sync as you navigate or as route rules and
definePageMetachange the resolved layout.📖
useLayoutdocumentation🪟 Named Views
Nuxt now supports named views through a filename convention (#35123). If a parent page renders more than one
<NuxtPage>outlet, you can give each outlet a name and provide a sibling page file for it using thename@view.vueconvention:Navigating to
/parent/childrenderschild.vueinto the default outlet andchild@sidebar.vueinto thesidebaroutlet. This has actually been possible in Vue Router for a long time; this release wires it up to Nuxt's file-based routing.📖 Named views documentation
🚦
enabledOption foruseFetchanduseAsyncDataYou can now gate data fetching with a reactive
enabledoption (#33260). Whileenabledisfalse, every execution is blocked (the initial fetch,execute/refresh, and watch triggers), and if you flip it fromtruetofalsemid-flight, the in-flight request is cancelled without clearing your existingdata.This is perfect for dependent or conditional queries, where you don't want to fire a request until some precondition is met. It pairs naturally with a getter or a ref, so it stays reactive.
📖
useAsyncDatadocumentation🔗
NuxtLinkPrefetch Control for Custom SlotsWhen you use
<NuxtLink>with thecustomprop, Nuxt no longer attaches prefetch handlers for you, because it can't know how you've structured your markup. To make that ergonomic, the slot now exposes everything you need to wire prefetching up yourself (#34539):You get
prefetchto trigger it,prefetchedto know whether it's already happened (great for a prefetched class), andshouldPrefetchto respect the user's connection and config.📖
NuxtLinkdocumentation⚡️ Forwarded Preload Hints on Prefetch
Here's another one we'd love you to try. When you prefetch a link to a route with payload extraction, Nuxt already primes the destination's data and chunks. With the new opt-in
experimental.prefetchPreloadTags(#35144), it also forwards the destination's<link rel="preload">andmodulepreloadhints (whatever the page sets viauseHead, or via modules like@nuxt/image's<NuxtImg preload>) into the current document, downgraded torel="prefetch"so they don't compete with the resources the user is looking at right now.The practical effect is that heavy above-the-fold assets on the next page (a hero image, a critical script) start downloading while the user is still on the current one, so the navigation feels instant. It's off by default while we gather feedback, so please give it a spin and tell us how it behaves on your app.
🌐
import.meta.envNameThe resolved Nuxt environment name is now available at runtime as
import.meta.envNamefor both Vite and webpack/Rspack builds (#34844). This is the value set by--envName(or the resolved default), so you can branch on it in your app code:📖
import.metadocumentation🔭 Tracing Channels for SSR Events
Nuxt now publishes diagnostics-channel traces for its server-side subsystems (#35191). It's unopinionated: we emit
nuxt.render,nuxt.island,nuxt.data, andnuxt.pluginchannels following the untracing naming convention, and you can build OpenTelemetry (or anything else) on top. It works in Node, Deno, Bun, and Cloudflare Workers.You can also enable it granularly. In Nuxt v5, there will be additional Nitro-level channels:
📦 Dependency Upgrades
unheadv3Nuxt's head management now runs on
unheadv3 (#34793). It's smaller, uses a synchronous engine internally, and ships better type-safety foruseHeadout of the box. This also unblocks SSR streaming.unctxv3We've moved to
unctxv3 (#35541), which resolves a class of long-standing async context issues (#33644). This is part of the composable-context reliability work that continues into v5.And more
We've also updated
magic-stringto v1, Babel to v8, and pulled in the latest Rolldown across the board. Runningnuxt upgrade --dedupe(see below) is the easiest way to pull these through cleanly.🛠️ Nuxt CLI
This release bundles some improvements from
@nuxt/cliv3.36 and v3.37:nuxt module removeto uninstall a module and clean up its config (nuxt/cli#1306), the natural companion tonuxt module add.Non-interactive
nuxt initfor scripting and CI (nuxt/cli#1341), plus it now respects the template's own package manager instead of prompting (nuxt/cli#1330).Type-check onboarding:
nuxt typechecknow offers to installvue-tscandtypescriptfor you if they're missing (nuxt/cli#1316).Golar support for type-checking:
nuxt typecheckcan now use Golar as an alternative tovue-tsc(nuxt/cli#1362). It's picked up automatically if it's installed (or agolar.config.*file exists), and you can force a checker with--checker=vue-tscor--checker=golar:Layer-aware dev server: the dev server now reloads on changes to local layer
nuxt.configfiles (nuxt/cli#1345).🧩 TypeScript Plugin and Named Layout Slots
Nuxt's experimental TypeScript plugin is powered by
@dxup/nuxt, and this release bundles a newer version of it. If you haven't tried it, turning onexperimental.typescriptPlugingives you a set of editor niceties: renaming an auto-imported component updates every usage, plus go-to-definition for glob imports, Nitro routes,definePageMeta,runtimeConfig, and typed route names.New in the bundled version is an opt-in runtime feature: named layout slots (KazariEX/dxup#20). You can write a top-level named-slot template in a page and have it forwarded into the matching named slot of the active layout. That lets a page inject content into its layout's slots, which file-based layouts don't otherwise allow.
Enable it alongside the plugin:
Then a layout can expose named slots:
And any page using that layout can fill them:
📖
typescriptPlugindocumentation🔥 Performance and Reliability
As always, a lot of work has gone into making Nuxt faster and steadier.
One you can opt into today is the shared file watcher (#35143). Vite already runs a chokidar-based watcher, so Nuxt can piggy-back on it instead of spinning up a second one, using less memory and fewer file handles. This becomes the default with
compatibilityVersion: 5, but you can turn it on now and let us know how it goes:There are also some other good performance improvements that don't need to be opted into:
getIslandHashandhashKeynow exposed (#35583).A couple of other nice quality-of-life fixes:
$fetchis now auto-imported in user code, which fixes edge cases with top-level$fetch.createunder Rolldown's output format (#35581).HTTP_PROXY/HTTPS_PROXYenvironment variables in the builder environment (#35183).defineNuxtComponentin JSX (#35620).⚠️ Heads-Up Before Upgrading
As mentioned above, this release includes three major dependency bumps. For most apps they're transparent, but they're worth a moment of attention:
builder: 'rspack', the internals are now Rsbuild-based, so custom Rspack config may need review.unheadv3: possible breaking type changes from stricteruseHeadtyping.⬆️ Upgrading
Our recommendation for upgrading is to run:
This will deduplicate your lockfile and help ensure you pull in updates from the other dependencies Nuxt relies on, particularly across the unjs ecosystem (which matters more than usual this release, given the major bumps).
Thank you to all of the many contributors to this release. This was a big one, and it wouldn't happen without you. 💚
👉 Changelog
compare changes
🚀 Enhancements
import.meta(#34844)allowImportingTsExtensions(87c214b34)nuxt/*build output contract (#35463)NuxtLinkprefetch props for custom slot (#34539)enabledoption to AsyncData (#33260)getIslandHash+hashKey+ improve hash for keys (#35583)useLayoutcomposable for accessing the resolved route layout (#35623)enabledoption inuseFetch(#35627)~,@) (#35641)updateAppConfigutil for module authors (#35651)🔥 Performance
experimental.watcher(#35143)rolldown-string(#35058)🩹 Fixes
readyevent for vite watcher (6fe539f52)causewhen we re-throw (#35387)#components(#35215)<ClientOnly>fallback tag name (#35325)inlineStylesis disabled (#35209)ssr: false(#35330)NuxtPageduring nav (#35335)useAsyncDatateardown (#35328)definePageMeta(#34526)bodyinuseFetch(#35343)page:startfinishes beforepage:finishstarts (#35408)NODE_ENVand__VUE_PROD_DEVTOOLS__(#35444)$fetchwhere possible (#35581).mjsruntime output (#35263)head.pushfor client side (#35498)rootDirto typed-router context (#35565)__nuxt_errormarker onNuxtError(06e976d2e)noUncheckedSideEffectImports(v5) + disablelibReplacement(#35548)unctxtransform marker throughmagic-string(#35618)node:processin diagnostics (d73b5df6d)navigateToin built-in router (#35631)💅 Refactors
nuxt prepare(#35141)clearNuxtData&clearNuxtState(#35142)fsCacheinstead of deprecatedcache(#35268)#vite-nodetypes from source (#35334)rolldown/utilsinstead of oxc-* dependencies (#34983)ssrStylesbuild output => lazy code provider (#35601)rsbuildto power rspack builder (#35489)📖 Documentation
useAsyncDataanduseFetch(#35528)enabledfromuseFetchdocs (bf7810600)useFetchkeys are unique per call site (#35645)📦 Build
tsdownto build project (#35179)🏡 Chore
vpin nightly release script (97b56d98d)✅ Tests
loadNuxtstartup (#35382)experimental.attachDebugInfo(7c2397ae6)testmode for bundle size tests (#35558)expectNoClientErrors(9f3123f60)🤖 CI
❤️ Contributors
4cdc2a069cto43210c3ad1@jasper can you fix the test failures on this branch and force push?
43210c3ad1toe0b1cbea3ee0b1cbea3eto4f341147a54f341147a5to6d88414746chore(deps): update dependency nuxt to v4.5.1 [security]to chore(deps): update dependency nuxt to v4.5.1 [security] - autoclosedPull request closed