NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
Nuxt is a free and open-source framework with an intuitive and extendable way to create type-safe, performant and production-grade full-stack web applications and websites with Vue.js.
Last release 4 days ago
02 Nov 2026
Release timing varies
gaps range from 8 days to 2 months
Rarely documented
notes for 14 of the last 60 stable releases
Nothing withdrawn
no release was ever pulled
10 years old
412 releases · first in 2016
4.6.4 is a security release. It fixes eight advisories in Nuxt and pulls in nitropack 2.13.6, which fixes three more in Nitro . We recommend upgrading…
4.6.4 is a security release. It fixes eight advisories in Nuxt and pulls in
nitropack2.13.6, which fixes three more in Nitro. We recommend upgrading as soon as you can.
These probably won't affect most apps, but it's worth flagging anyway:
createError. Everything else is stripped.Host checks. Webpack and rspack dev servers now reject requests with an unexpected Host header, as Vite already does. If you use a custom hostname or tunnel, you'll need to add it to the new devServer.allowedHosts option.workspaceDir detection. When workspaceDir isn't set, Nuxt now only widens it past rootDir to an explicit workspace root or the closest git repository.cache, swr and isr route rules now only cache GET and HEAD requests, and no longer store responses that set a cookie or send Cache-Control: private or no-store. Wildcard proxy and redirect rules also reject requests whose resolved target would leave the rule's scope./api%2Fadmin is covered by a /api/** rule.768d21368)bf31e4cd8)1b13c6981)75c12c6fb)6f1b93c64)1e7dfa6a7)8c56b71e4)97424dd21)8c11d965d)3c9645326)f8d64c9aa)devServer.allowedHosts (3e1c7964e)084396b10)89d3306d1)x-forwarded-proto in when opted-in (#36818)nuxt/server handler (#36817)24d75659d)definePageMeta (#36751)1225ca7ff)c852d871f)7528c3ce6)545b5934c)40f72b40b)861e153dd)d093c88b1)4b90681bd)6428070e0)bc17c03d1)One column per quarter.
v4.6.3 is the next patch release.
v4.6.3 is the next patch release.
63299e13c) - GHSA-x287-wcfc-q46cc7e204ec6) - GHSA-648h-gw55-m2g716dfdc2a9) - GHSA-3xf4-9g3c-4h4w440250d97) - GHSA-9v7c-q64q-vv33b20372dbe) - GHSA-2fxj-c3j7-rr69fs.allow (#36702)navigateTo while middleware runs (#36650)_asyncData (#36709)import.meta keys + limit replace to client dev (#36703)#app composables (#36696)shared/ in build cache hash (#36704)rootDir (#36697)updateCallbackDone of aborted view transition (#36639)sensitive: false (#36708)634cba64c)noScripts wildcard patterns single-segment (fa323db7d)nuxt/schema augmentation (#36633)nuxt/* imports (#36632)vite.config files (#36628)include (#36627)v-for sources in island renders at render time (#36623)build:manifest hook (#36695)4b12d15c9)NUXT_B4023 as main-only (4fd54ff9a)453406999)NUXT_B9006 as main-only (e107e0f40)132698a46)7d228ba4b)bad8c3637)9d47884f3)1d89ad434)v4.6.2 is the next patch release.
v4.6.2 is the next patch release.
useHeadSafe rules to server island head (493565e99)010f6b43d)f6cfee52c)rawVaporSlotKey from vue (#36614)experimental.typescriptPlugin (#36561)AnyFetchPath for emitted server route types (#36610)nuxt/app to the app's runtime (#36587)appMiddleware (#36565)render:response listeners (#36583)vue to its bundler build in dev (#36569)/@id/ in dev (#36549)nitroAutoImports is off (#36548)H(TTP) to HTTP (#36559)proxy route rule (4a529be68)definePlugin and defineNitroPlugin in server plugin tip (64b8e0334)e551df267)7499a445b)3110d6947)fb01db4eb)nuxt/app entry shim from root typecheck (c65cef3a8)test:engines (6ce412822)#build/server-routes stub via root tsconfig paths (8235dd605)ca784deb7)inlinedDependencies in autofix (4b6da3a29)917bdf218)v4.6.1 is the next patch release.
v4.6.1 is the next patch release.
cffb182d6)baseURL when fetch response type is named (#36523)nitropack/types declarations under nodenext (242e1971e)false in setPageLayout types (#36506)Serialize object types with JSON (#36503)publishConfig config export extension (64fa191cb)RequestEvent to nuxt dev server handlers (e6beceea2)$fetch with nuxt's TypedFetch (#36492).wrangler directory (#36487)BroadcastChannel throws (#36480)nuxt dev server handler variant (452052793)21657c6fc)unhead from nuxt in server bundle (dc1e2d6e6)package-manager-detector workspace + deno handling (1563a6d25)CLAUDE.md (#36462)bdf98e936)787d0d980)This has become particularly clear as we have been upgrading to new majors of h3 and nitro, which ship breaking changes with a cascading effect throug…
v4.6.0 is the next minor release.
Alongside the release of Nuxt v4.6, today also brings a new major release of the Nuxt CLI: @nuxt/cli v4. It ships as a dependency of nuxt, so you'll get it automatically when you upgrade.
Most of what's new is in nuxt dev:
nuxt.config keys changed, where the time went during a slow start or build, and how long each module took to set upmy-bad (see below), powering things like automatically reloading when a syntax error in nuxt.config.ts is fixed.nuxt/, which lets a second nuxt dev (say, one started by an agent) take over or defer to the one you started, and powers new nuxt curl and nuxt task commands that talk to the running serverThere's also a new nuxt docs "<query>" search, and nuxt preview --takeover can replace a running preview server. And in general nuxt/cli is a lot smaller and starts a lot faster:
| v3.37 | v4.0 | ||
|---|---|---|---|
@nuxt/cli install size |
13.1 MB | 3.5 MB | -73% |
@nuxt/cli dependencies |
70 | 31 | -56% |
nuxt dev: first paint |
330 ms | 50 ms | 6.6x faster |
nuxt dev: port bound |
338 ms | 104 ms | 3.2x faster |
nuxt dev: memory at rest (Linux) |
630 MB | 440 MB | -30% |
Although this is a major version, none of the changes should be breaking for Nuxt v4 users: we require Node.js v22.21+, v24.11+ or v26+, we drop nuxt init and only support npm create nuxt@latest, and Nuxt 2 and @nuxt/bridge are no longer supported.
👉 Check out the full Nuxt CLI v4 release notes for everything that's changed.
The biggest thing about this release is our move towards making Nuxt server-agnostic.
I feel that freedom of choice is very much a fundamental value of the web, and one that unites the whole Nuxt team.
You can use pages/ (with vue-router) or not. You can use Vite, webpack or Rspack to bundle your code. You can pick from dozens of providers to deploy to, pick any image or font provider, choose any database adapter. In every case, the framework is the same.
The server side was different. #app composables imported h3 types, server code imported from h3 and nitropack, and every module that touched the server was tied to whichever major version of those packages Nuxt happened to depend on.
This has become particularly clear as we have been upgrading to new majors of h3 and nitro, which ship breaking changes with a cascading effect throughout the whole ecosystem.
👉 This release changes that.
Alongside explicitly defining our public API in nuxt/kit (which now does not refer to external packages), Nuxt now specifies our own types for the request event, route rules and typed $fetch, and we expose an import surface (nuxt/server) for the server utilities that will be needed by most apps.
This is the culmination of work we started almost a year ago, making it possible to use any server builder with Nuxt, not just Nitro (#33462).
Of course, under the hood, nuxt/server is still powered by Nitro by default - though we are also announcing a second, experimental implementation, @nuxt/vite-server, which allows pure-Vite server builds using the Vite Environment API.
Important
We believe that Nitro is still the right choice for almost everyone.
🌟 We see a number of key benefits for nuxt/server.
nuxt/server on 4.6 runs unchanged there, so a module can ship one file for both./app and /server - and the bundler + server that you ultimately want to build your app.... and there are a number of other benefits too, from a single type surface to being able to iterate more quickly on features.
Finally, I want to say a special thank-you to @pi0, whose relentless focus on server agnosticism and work on h3, Nitro and web-standard server primitives over the last few years is what makes a portable RequestEvent possible at all. Thank you, Pooya. ❤️
Almost every feature in this release is already in the Nuxt 5 branch, and most of the remaining Nuxt 5 defaults can be tested today with future.compatibilityVersion: 5 (more details below!).
Tip
Nuxt 3 reached end-of-life on July 31, 2026, so there is no 3.x release alongside this one. If you're still on v3, the upgrade guide is waiting for you.
🤷 If you've read this far I'm afraid I have bad news for you: there's a lot more still to say! Nuxt 4.6 is one of our biggest minor releases, with over 420 commits since v4.5.2.
... so, you might want to grab a coffee! ☕️
nuxt/serverIt has been asked for for a long time, and it now exists (#36275)!
nuxt/server is a new import source for server code: handlers, middleware and utilities - a complement to nuxt/app. Where nuxt/app is for the part of your application that also runs in the browser, nuxt/server is for the part that only runs on the server.
import { defineEventHandler, getQuery } from 'nuxt/server'
export default defineEventHandler((event) => {
const { name } = getQuery<{ name?: string }>(event)
return { message: `Hello, ${name ?? 'world'}!` }
})The utilities use web standards and are typed against a portable RequestEvent:
event.req // Request
event.url // URL
event.res // { status, statusText, headers }
event.res.headers // Headers
event.context // per-request contextUnder @nuxt/nitro-server they are backed by Nitro and h3, but you never import from either.
So the same handler runs under Nitro v2, Nitro v3 or @nuxt/vite-server, and a module that imports from nuxt/server doesn't need a peer dependency on h3 or nitropack.
We think this will make a big difference in smoothing out the upgrade to Nuxt v5 and Nitro v3.
There is a typing benefit too. We no longer hoist h3 or Nitro types into your app to type useRequestEvent, $fetch or route rules, which removes a source of type conflicts when versions differ (#36212, #36214, #36293).
The surface is small, and covers what published modules and user code typically need: defineEventHandler, createError/isNuxtError, request URL, headers, query, body (plain and validated with any Standard Schema library or a function), cookies, redirects, response status, getRouterParam(s), getRequestIP, handleCors, getRouteRules, useRuntimeConfig, useAppConfig and sessions.
import { defineEventHandler, readValidatedBody } from 'nuxt/server'
import { z } from 'zod'
export default defineEventHandler(async (event) => {
const user = await readValidatedBody(event, z.object({ name: z.string() }))
return { created: user.name }
})We encourage you to use web APIs (event.req.headers, for example), or raise an issue if there's functionality you're missing from nuxt/server 🙏
If you do need to step outside nuxt/server for a particular handler, don't worry! Nothing has been taken away: import defineEventHandler and the helpers you need from h3 or nitropack/runtime as before, and that handler works exactly as it did on Nuxt 4.5.
import { defineEventHandler, readMultipartFormData } from 'h3'
export default defineEventHandler(async (event) => {
const parts = await readMultipartFormData(event)
return { received: parts?.length ?? 0 }
})Important
On Nuxt 4, the auto-imported defineEventHandler, getQuery, readBody and the rest are still h3's own helpers, which take a different shape of event. Import from nuxt/server explicitly, including defineEventHandler to use the new server runtime. If you mix the two, you'll see a NUXT_E8012 error which should tell you which import to change.
A few helpers also behave differently from their h3 v1 namesakes: sendRedirect returns the response rather than sending it, createError takes status and statusText, and response headers are set through event.res.headers. The upgrade guide has a table of the differences.
Nothing in this release requires a migration. But if you have server code you'd like to make portable ahead of Nuxt 5, this is the best way.
👉 Read the server imports guide.
appSecret and sessionsNuxt now has a root application secret: runtimeConfig.appSecret, set with NUXT_APP_SECRET (#35874, thanks to @onmax). Modules and server features derive purpose-specific secrets from it with deriveSecret(purpose), so NUXT_APP_SECRET is the only secret you need to configure.
openssl rand -base64 32In development Nuxt generates and persists one if none is configured (and warns the first time a derived secret is used). Builds never generate one.
The first thing to use it is a set of session helpers in nuxt/server (#36358). Sessions are sealed into a cookie with iron, so there's no server-side storage to configure:
import { defineEventHandler, useSession } from 'nuxt/server'
export default defineEventHandler(async (event) => {
const session = await useSession<{ visits: number }>(event)
await session.update(data => ({ visits: (data.visits ?? 0) + 1 }))
return { visits: session.data.visits }
})Note
As a reminder, runtime config keys prefixed with app (runtimeConfig.app, runtimeConfig.appSecret) are reserved for Nuxt.
$fetch, rebuilt$fetch and useFetch have been typed from your server routes for a long time. But the types were derived from Nitro's InternalApi interface, and past a few hundred routes they hit TypeScript's instantiation limit with the familiar TS2589: Type instantiation is excessively deep and possibly infinite.
We've rebuilt typed fetch on top of fetchdts (#36238). Nuxt compiles your server routes into a route tree with an exact-match table for static paths and accessors specialised to your route set. Resolution cost now scales with call sites, not with route count:
| routes | before | after |
|---|---|---|
| 100 | 1,397,361 instantiations / 0.77s | 51,558 / 0.26s |
| 300 | 5,774,425 / 3.49s (TS2589) |
51,558 / 0.16s |
| 1000 | 12,831,467 / 7.44s (TS2589) |
51,558 / 0.20s |
| 3000 (200 call sites) | 71,875,148 / 53.63s (TS2589) |
101,678 / 0.62s |
Peak memory for the same runs dropped from 946 MB to 140 MB. 🔥
Plus, the route set also carries the body, query and headers a handler validates, so calls are checked more tightly than before:
// server/api/users.post.ts validates { title: string, count: number }
await $fetch('/api/users', { method: 'post', body: { title: 'a', count: 1 } })
await $fetch('/api/users', { method: 'post', body: { title: 'a', count: 'no' } })
// ^ not assignable to number
await $fetch('/api/users', { method: 'post' })
// ^ body is requiredOn Nuxt 4 this is opt-in, because there are small changes to type inference, and hand-written ServerRoutes augmentations need a small rewrite. It is the default in Nuxt 5.
export default defineNuxtConfig({
experimental: {
routeTypedFetch: true,
},
})There's also a new experimental.strictRouteTypes option to reject calls to paths that don't exist (otherwise these just return unknown), and an 'isomorphic' mode that types your pages as GET routes too if you want to be able to $fetch from the Vue renderer with type safety.
👉 Read more in the experimental features docs.
my-badServer-side errors in development used to look like this:
ReferenceError: foo is not defined
at Object.<anonymous> (/_nuxt/app/pages/index.vue:1:1)
There was no source position or code frame, and the Youch iframe we rendered could not show you the frame in your own source either. Both are now fixed (#36258, nuxt/cli#1518).
Dev SSR stack traces are now mapped before anything reads the error, and the Youch overlay has been replaced with my-bad. It renders into your app's own error page as an overlay (or as a standalone page when the app can't render one), with the mapped stack trace and a code frame from your source, and the same report is printed in your terminal. We are still working with @atinux, @HugoRCD and @antfu to make these pages nicer still.
With Nuxt CLI v4, there is a single live error channel at the CLI level. It survives worker restarts, so (for example) a syntax error in nuxt.config.ts will live-reload the page once you fix it. Each error is rendered once rather than at every layer it passes through, and the same channel streams build progress and app logs to the dev panel.
@HugoRCD has redrawn the loading screen you see while the dev server starts. It is now a WebGL2 particle field that traces a mountain range behind the Nuxt lockup (#36178), using a single shader and a single draw call. Without WebGL2 it falls back to the static lockup, and with prefers-reduced-motion the animation stops. Try hovering over it. 🏔️
Hugo also gave the built-in 404 and error pages a neutral palette and lighter type (#36255), and @MirkoJa added a back button to the 404 page (#35688).
Nuxt now supports Vue 3.6's Vapor Mode in interop mode (#35759). Your app root stays on the virtual DOM, and you can opt individual components or pages into Vapor by adding the vapor attribute to <script setup>:
export default defineNuxtConfig({
vue: {
vapor: true,
},
})<script setup vapor lang="ts">
const count = ref(0)
</script>
<template>
<button @click="count++">
count is {{ count }}
</button>
</template>Routing, useAsyncData, layouts and most built-in components keep working unchanged. Along the way we made the auto-import loader, useAsyncData, definePageMeta and slot inspection Vapor-aware, and we now have a Vapor test suite so we can track what's supported.
Note
This requires Vue ^3.6.0-rc.2 or newer. See the known limitations and try it in a fresh project first.
useFetch and useAsyncData@cernymatej has added an addons option to the createUseFetch and createUseAsyncData factories (#35797). An addon can declare custom call-site options, adjust the merged options, wrap the handler with middleware, and extend what the composable returns, and it can be reused across as many custom instances as you like.
const refreshOnFocus = defineUseFetchAddon({
setup: (options: UseFetchAddonOptions<{ refreshOnFocus?: boolean }>) => {
if (import.meta.server || !options.refreshOnFocus) { return }
return (asyncData) => {
const focused = useWindowFocus()
watch(focused, focused => focused && asyncData.refresh())
return { focused }
}
},
})
export const useApiFetch = createUseFetch({ baseURL: '/api', addons: [refreshOnFocus] })<script setup lang="ts">
const { data, focused } = await useApiFetch('/todos', { refreshOnFocus: true })
</script>This resolves a long list of feature requests for useAsyncData and useFetch (refresh on focus, polling, retries, auth headers and more) without making the core composables opinionated about any of them.
👉 Read about defineUseFetchAddon and defineUseAsyncDataAddon.
There is a lot of performance work in this release.
<NuxtLink> renders 58% faster on the server (#36015). Internal links are now rendered as a plain <a> with no useLink, no computed and no reactive state. Rendering 200 links went from 1.36ms to 0.57ms, which is 1.4x faster than a bare <RouterLink>.experimental.early404, page routes are compiled into a static matcher at build time and requests that can't match any page skip creating the Vue app, running plugins and middleware entirely. On an app with 30 pages and ~23ms of boot work per render, a JSON 404 went from 37.1ms to 0.3ms.builder:watch hook settles in 0.6ms instead of 36ms.useCookie parses the cookie header once per request (or once per microtask on the client) instead of on every call. A nice side effect: a cookie set in a plugin during SSR can now be read by a later useCookie in a page.ssr: false pages are tree-shaken from the server bundle (#35836, thanks to @Austin1serb).definePageMeta keys are extracted at build time (#35919), behind experimental.extractSerializablePageMeta.unctx is no longer shipped to the browser and defu is skipped for a single app.config (#36371).@nuxt/kit no longer depends on c12, untyped, confbox, pkg-types, ufo or mlly, and jiti and giget are now optional peers loaded only when needed (#35936, #35943, #35946, #36071, #36073, #36083).isVue checks migrated to plugin filters (#36116) and an flru prerender cache (#36340).Put together, on our benchmark machine (arm64 Linux, Node 24.15, medians of 5 runs):
| v4.5.2 | v4.6.0 | ||
|---|---|---|---|
@nuxt/kit install size |
6.4 MB | 2.1 MB | -67% |
@nuxt/kit transitive dependencies |
36 | 22 | -39% |
nuxt build, starter |
4.4 s | 3.6 s | -18% |
nuxt build, 200 pages / 200 components / 50 routes |
11.6 s | 10.3 s | -11% |
SSR throughput, page with 300 <NuxtLink>s |
110 req/s | 140 req/s | +26% |
Dev server start-up (spawn to first HTML) on the same machine is about 12% faster on a starter app, with the second request served in roughly half the time, though the CLI major changed alongside so not all of that is Nuxt.
useAsyncData and useFetch accept a serialize: false option to keep data out of the __NUXT_DATA__ payload (#35779). That's most useful inside components that never hydrate, and experimental.stripNeverHydratedData applies it automatically to data fetched within hydrate-never component trees.
Nuxt also warns in development when a page's payload exceeds 100 kB (#35777) and when a route rendered with noScripts relies on client-side JavaScript (#35780), and noScripts pages keep their non-script resource hints and attach their styles correctly (#35803, #36356, #36359).
<NuxtLink> now prefetches server-page islands (#35808), and @atinux made prefetch hints throttled and prioritised so a page full of links doesn't flood the network (#36261, #36324). Building on that, route chunks, layouts, middleware, payloads, islands and resource hints all now go through one client prefetch scheduler with per-kind concurrency caps, deduplication by key, and cancellation of in-flight work when you navigate away (#36391).
Most of what's new in Nuxt 5 is already in 4.6, either as the default or behind a flag. future.compatibilityVersion: 5 turns on the Nuxt 5 defaults in one go, and every one of them can be enabled (or disabled) individually.
export default defineNuxtConfig({
future: {
compatibilityVersion: 5,
},
})Newly gated behind the flag in this release:
experimental.typedPages) (#35789)$fetch (experimental.routeTypedFetch), described abovenavigateTo (experimental.navigateToEarlyReturn): navigateTo in <script setup> short-circuits the rest of the setup, so redirects and 404s don't throw on missing data (#36115)experimental.extractSerializablePageMeta)experimental.payloadExtraction: 'client')clearNuxtState resetting to defaults, experimental.watcher: 'builder', and no auto-imported server-only head composablesbaseUrl in generated tsconfigs (#36040, thanks to @oritwoen)experimental.inlineErrorRendering): when a server render fails, error.vue is rendered in the same request with a plain try/catch, instead of re-entering the server over an internal request to /__nuxt_error. Headers and cookies the failed render had already set are kept, error renders no longer pass through Nitro middleware and route rules a second time, and render:html fires with the original event (#36399).The upgrade guide now lists exactly what the flag changes on Nuxt 4.
@nuxt/vite-serverSince v4.2 server.builder has been configurable. This release adds a second server builder: @nuxt/vite-server. It builds a Nuxt app with Vite alone (#36218, #36279, #36288).
This is all you need to do to configure it.
export default defineNuxtConfig({
server: {
builder: 'vite', // 'nitro' is the default
},
})It can produce a pure client SPA, a server-rendered app with a small Node entry, a web-standard fetch handler for platforms that provide the server (there are e2e examples for Cloudflare Workers, Netlify and universal deploy), as well as fully static output with nuxt generate.
Right now, this helps keep Nuxt's code agnostic, enforce the contract behind nuxt/server, and to give Vite plugins that provide a deploy target something to build on. It does not have Nitro's full feature set (there is no storage, caching, tasks or server plugins), and we expect most apps to keep using Nitro. Nitro remains the default.
Warning
This is highly experimental and the API will change. 'nitro' and 'vite' are new shorthands for @nuxt/nitro-server and @nuxt/vite-server.
<NuxtLayout> or <NuxtLink> in a template now shows a short description and a link to the docs. Thanks to @Ibochkarev.app/types/ and server/types/ are included in the right tsconfig, so ambient types and augmentations placed there are picked up (#35783, thanks to @Flo0806, who also added an augmentable NuxtPageMeta for typing NuxtPage.meta in #34816).components/: a (group)/ folder is excluded from the component name (#35699, thanks to @abaza738).expires in useCookie, accepting a function (#35628, thanks to @DarlanPrado).public/ file shadows an application route (#35674, thanks to @Norbiros).404.html, or any status codes you pass) with experimental.prerenderErrorPages instead of an empty SPA shell (#35193, thanks again to @Flo0806).$Fetch is exported from nuxt/app (#35625) and ShallowRef is in the Vue auto-import preset (#36266), both from @DamianGlowala; preloadComponents and the NuxtIsland name prop are typed (#35775), and useLayout, useLoadingIndicator and useRequestHeader are exported from nuxt/app (#36033).typescript.tsConfig is now a shared baseline for all four generated tsconfigs, with appTsConfig and serverTsConfig for per-context overrides (#35697, thanks to @chairulakmal).prerender option, an alias for nitro.prerender in the same way runtimeConfig and routeRules are top-level (#32356). Nuxt now also points you towards top-level options where they exist, since those work across server builders (#36416).public/ files just work. If the router has no route for a <NuxtLink> target (a PDF in public/, say, or a link from Markdown with Nuxt Content), Nuxt falls through to a full-page load instead of rendering your 404, so external is no longer required (#36169).node_modules are pre-bundled by Vite (#36208), and symlinked layer directories resolve to their real path (#36402, thanks to @silverbackdan).If you maintain a module with server code, we've enabled making modules compatible with both Nuxt 4 & 5, without requiring a major bump. (And we'll be opening PRs proactively after the release of Nuxt v4.6 to assist with preparing for a Nuxt v5 release...)
One module for Nuxt 4 and Nuxt 5. addServerHandler, addDevServerHandler and addNitroPlugin accept a map of variants per server API (#36317). Nuxt chooses the most appropriate one. A handler that imports only from nuxt/server needs no Nitro 2/3 variants at all (but does require Nuxt v4.6+).
addServerHandler({
route: '/api/my-module/status',
handler: {
nuxt: resolve('./runtime/server/status'),
nitro2: resolve('./runtime/server/status.legacy'),
},
})Nuxt reads the file's imports to decide which API it uses. Where that isn't enough, you can declare meta.compatibility.server. getNitroVersion and hasNitroVersion are also there in case you have logic that explicitly requires you to know the Nitro version installed (#36127).
👉 Read the server compatibility guide for more information.
Nuxt-owned, augmentable server types: ServerTypes, ServerRoutes, AppRouteRules and NuxtRequestContext. Augment @nuxt/schema once; nuxt/schema mirrors it (#36293).
useTerminal for host-aware prompts, status messages and tasks in progress, rendered by the CLI's dev panel when there is one (#36162).
module:before and module:done hooks, which Nuxt CLI v4 uses to show per-module setup time as it happens (#36173).
onConfigResolved and diffNuxtConfig to see what changed between two config loads (#35853).
ensureDependencyInstalled and getAddDependencyCommand to check for and offer to install optional dependencies with the user's package manager (#34554).
Template dependencies to say exactly what should invalidate a template (#35875).
addServerImports, addServerImportsDir and addServerTemplate work the same across Nitro versions; a server tsconfig and versioned route config types are generated for you (#36265).
@nuxt/kit has an explicit public API (#36074) and @nuxt/schema is an optional peer (#36246).
Modules can now set experimental.asyncContext (#36175, thanks to @cernymatej), Vite plugins added via kit land at the top level rather than wrapped (#36037), and more build-time warnings have moved to diagnostic codes with docs pages (#36138).
@nuxt/kit peer dependency ranges are widened to the versions actually required rather than tracking the latest of each package (#36417), and updateRuntimeConfig no longer warns when called before Nitro exists (#36403, thanks to @Neekoras).
useAsyncData no longer resolves with unfetched data on hydration (#36124), awaited lazy async data resolves immediately (Note truncated.
4.5.2 is the next patch release.
4.5.2 is the next patch release.
v-for islands transform (#35877)apply and applyToEnvironment in vite wrapper (#35899)enforce in vite wrapper (#35916)vite-node (#35758)<NuxtPage> on nav + with slot (#35948)prepend is set (#35942)import.meta.test for server code (#35987)vue-component-type-helpers instead of locally maintained helpers (#35840)isReferenceIdentifier in page-meta plugin (#35882)defineVitestProject in e2e tests (#35926)@nuxt/devtools to v3.4.0 (#35892)toFsUrl (a5ad667f7)permissions: {} to workflows (2e1a1cbeb)⚠️ This is a security release. We recommend upgrading as soon as possible with npx nuxt upgrade --dedupe .
⚠️ 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.
Full details: Nuxt Security Patch Releases and GitHub Security Advisories.
vue.optionsApi and disable it for v5+ (#35791)ssr: false (#35782)useRoute in detached effect scope (#35659)name or path when reusing an existing page in pages:extend (#35661)useFetch method inference (#35671)force-cache (#35672).mts file extension in resolver (#33845)@unhead/vue/* from nuxt's dependency tree (#35690)$fetch with nitro's $Fetch (#35704)vue-router when there are island pages (#35739)h3 that pins it to the version nuxt depends on (#35774)from cannot be resolved (#35799)app.buildAssetsDir (#35833)template island prop under runtime compiler (ee6c84633)as prop for islands (581651ff3)semver with verkit (#35713)codeSplitting: false (#35683)onPrehydrate example comment (#35684)runtimeConfig env var casting edge cases (#35709)nuxt rather than nuxi (8891e179c)runtimeCompiler security best practices (449b63ab1).ts file extension from runtime/ imports (#35689)@nuxt/telemetry in knip (9824d4f10)@nuxt/devtools to v3.3.1 (#35815)_route in gotoPath (ca92d082b)knip job (58f68bd64)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 Upgra…
4.5.0 is the next minor release.
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 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.
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. ☕️
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.
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:
// nuxt.config.ts
export default defineNuxtConfig({
builder: 'rspack',
})Under the hood, though, a lot has changed for the better:
webpack-dev-middleware and webpack-hot-middleware (#35575).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.
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.
// 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:
// 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 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 ComposableThere'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.
<!-- 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.
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:
# Directory Structure
-| pages/
---| parent/
-----| child.vue
-----| child@sidebar.vue
---| parent.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).
enabled Option for useFetch and useAsyncDataYou 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.
<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.
NuxtLink Prefetch Control for Custom SlotsWhen 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):
<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.
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.
// 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.envNameThe 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:
if (import.meta.envName === 'staging') {
// enable staging-only behaviour
}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
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:
// nuxt.config.ts
export default defineNuxtConfig({
tracingChannel: {
nuxt: true,
},
})Note
Channel names, payload shapes, and option keys may still change while the untracing registry settles.
unhead v3Nuxt'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 v3We'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.
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.
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=golarLayer-aware dev server: the dev server now reloads on changes to local layer nuxt.config files (nuxt/cli#1345).
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.
Enable it alongside the plugin:
// nuxt.config.ts
export default defineNuxtConfig({
experimental: {
typescriptPlugin: true,
},
dxup: {
features: {
namedLayoutSlots: true,
},
},
})Then a layout can expose named slots:
<!-- layouts/center.vue -->
<template>
<slot />
<slot name="side" one="one" />
</template>And any page using that layout can fill them:
<!-- 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
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:
// 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:
getIslandHash and hashKey now exposed (#35583).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).HTTP_PROXY / HTTPS_PROXY environment variables in the builder environment (#35183).defineNuxtComponent in JSX (#35620).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.unhead v3: possible breaking type changes from stricter useHead typing.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=v3This 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. 💚
import.meta (#34844)allowImportingTsExtensions (87c214b34)nuxt/* build output contract (#35463)NuxtLink prefetch props for custom slot (#34539)enabled option to AsyncData (#33260)getIslandHash + hashKey + improve hash for keys (#35583)useLayout composable for accessing the resolved route layout (#35623)enabled option in useFetch (#35627)~, @) (#35641)updateAppConfig util for module authors (#35651)experimental.watcher (#35143)rolldown-string (#35058)ready event for vite watcher (6fe539f52)cause when we re-throw (#35387)#components (#35215)<ClientOnly> fallback tag name (#35325)inlineStyles is disabled (#35209)ssr: false (#35330)NuxtPage during nav (#35335)useAsyncData teardown (#35328)definePageMeta (#34526)body in useFetch (#35343)page:start finishes before page:finish starts (#35408)NODE_ENV and __VUE_PROD_DEVTOOLS__ (#35444)$fetch where possible (#35581).mjs runtime output (#35263)head.push for client side (#35498)rootDir to typed-router context (#35565)__nuxt_error marker on NuxtError (06e976d2e)noUncheckedSideEffectImports (v5) + disable libReplacement (#35548)unctx transform marker through magic-string (#35618)node:process in diagnostics (d73b5df6d)navigateTo in built-in router (#35631)nuxt prepare (#35141)clearNuxtData & clearNuxtState (#35142)fsCache instead of deprecated cache (#35268)#vite-node types from source (#35334)rolldown/utils instead of oxc-* dependencies (#34983)ssrStyles build output => lazy code provider (#35601)rsbuild to power rspack builder (#35489)useAsyncData and useFetch (#35528)Note truncated.
4.4.8 is a hotfix release to address an issue running the dev server on MacOS.
4.4.8 is a hotfix release to address an issue running the dev server on MacOS.
type option in findPath (#35272)null to undefined in data-fetching docs to match actual types (#35301)4.4.7 is a security hotfix release.
4.4.7 is a security hotfix release.
👉 make sure to check https://github.com/nuxt/nuxt/security/advisories to view open advisories resolved by this release.
noSSR before deciding payload extraction (#35108)allowDirs (#35112)pathe for buildCache path boundary check (#35111)isValid in dev clipboard-copy listener (#35109)reloadNuxtApp path before reload (#35115)getCachedData after initial fetch (#35122)useFetch/useAsyncData factory types (#35133)isScriptProtocol guard to navigateTo open option (#35206)<NuxtPage> (#35198)<NuxtClientFallback> ssr output (#35199).d.mts/.d.cts in resolveTypePaths (#35235)<NoScript> slot content (4b054e9d9)vue-router (07e39cd6f)<NuxtLink> href (0103ce06f)navigateTo (2cce6fb02)reloadNuxtApp (e447a793c)es2023 array methods (#34980)runInNewContext with AST walker (d72a89ef4)execFileSync for safety in release scripts (1d7baaf01)Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Important Nuxt v3 is now end-of-life. Please upgrade to Nuxt v4, or you may wish to check out our partner HeroDevs who provide never-ending support fo
3.21.11 is a patch release.
Important
Nuxt v3 is now end-of-life. Please upgrade to Nuxt v4, or you may wish to check out our partner HeroDevs who provide never-ending support for end-of-life releases
oxc-walker (a6c9dc6d3)⚠️ This is a security release. We recommend upgrading as soon as possible with npx nuxt upgrade --dedupe .
⚠️ 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, 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.
Full details: Nuxt Security Patch Releases and GitHub Security Advisories.
useRoute in detached effect scope (#35659)name or path when reusing an existing page in pages:extend (#35661)useFetch method inference (#35671)@unhead/vue/* from nuxt's dependency tree (#35690)force-cache (#35672)app.buildAssetsDir (#35833)template island prop under runtime compiler (5b60017f7)as prop for islands (00a2b0494)runtimeCompiler security best practices (b76c1a8bd)_route in gotoPath (5391e7e6a)3.21.9 is the next patch release.
3.21.9 is the next patch release.
cause when we re-throw (#35387)#components (#35215)<ClientOnly> fallback tag name (#35325)inlineStyles is disabled (#35209)ssr: false (#35330)NuxtPage during nav (#35335)useAsyncData teardown (#35328)definePageMeta (#34526)body in useFetch (#35343)page:start finishes before page:finish starts (#35408)NODE_ENV and __VUE_PROD_DEVTOOLS__ (#35444)rootDir to typed-router context (#35565)navigateTo in built-in router (#35631)expectNoClientErrors (afffe7f09)3.21.8 is a hotfix release to address an issue running the dev server on MacOS.
3.21.8 is a hotfix release to address an issue running the dev server on MacOS.
type option in findPath (#35272)null to undefined in data-fetching docs to match actual types (#35301)Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Your coding agent can read these notes before it upgrades. Set up the MCP server →