🚀 Features
- Upgrade
@vitejs/devtools-kitto v0.4.10 - by @antfu (5cea1)
🐞 Bug Fixes
- devtools: Continue hardening the RPC surface - by @antfubot in https://github.com/nuxt/devtools/issues/1049 (7f072)
@vitejs/devtools-kit to v0.4.10 - by @antfu (5cea1)v2.1.0 is the next minor release.
Nuxt Image 2.1 ships eight new image providers, a major upgrade to our image processing engine, and a bunch of fixes across existing providers.
Under the hood, we've upgraded to IPX v4 which brings significant improvements:
svgo: false would return untouched SVGs. The sanitizer now removes SMIL attribute injection, foreign content (<foreignObject>, <iframe>, etc.), and unsafe URIs across all elementsopacity, brightness, saturation, hue, lightness, autoorient, dilate, erode, clahe, and linear400 errors with clear messages instead of cryptic 500sparseURL optionThis release adds support for eight new image optimization services:
key modifier for named presets (#2207)baseURL for custom CDN setups (#2273)minimumCacheTTL is now configurable (#2278)NuxtImgProps and NuxtPictureProps are now exported from the runtime, making it easier to type your own components that wrap <NuxtImg> or <NuxtPicture> (#2115).
import type { NuxtImgProps, NuxtPictureProps } from '#image'
key modifier (#2207)minimumCacheTTL configurable (#2278)baseURL option to Sanity provider (#2273)137ee63)provider key in generated image options (#2279)7e561f6)237e480)baseURL for urls with no operations (#2162)imagesrcset preload when densities are set w/o sizes (#2176)runtimeConfig.ipx with defaults (#2134)withoutEnlargement modifier type (#2133)remote_images config (#2086)formats (#2038)layer.config.image (23b8cef)data-* in imgAttrs (#2017)public/ dir in relation to rootDir (3e07761)6f59ab1)_routes.json (#2136)<NuxtImg /> example (#2074)93de8a9)2deacc0)prepack (97d3758)consola to dev dependencies (#2275)pnpm-workspace.yaml (9b80ce6)fd87f7d)<NuxtPicture> (#2145)13b88c1)03b5ef4)e529f37)2f8b332)23ee710)777239c)a200dae)f60d0f7)d1cb12b)09e5efb)402c729)906111c)No significant changes
4.1.0 is the next minor release.
RouterLink stub works with pages: false (#1687)ssr.resolve.conditions to remove import (#1732)defineVitestProject (#1724)viteEnvironmentApi (#1708)h3-next to optional dependencies (#1733)autoImport: false (#1688)⚠️ 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)⚠️ 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)No significant changes
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.configtweaks, 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
rspackfor 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
definePageMetais 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
unheadv3 introduces type-narrowing foruseHead, 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.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).nuxt typecheck now offers to install vue-tsc and typescript for you if they're missing (nuxt/cli#1316).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
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=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. 💚
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)enabled from useFetch docs (bf7810600)useFetch keys are unique per call site (#35645)tsdown to build project (#35179)vp in nightly release script (97b56d98d)loadNuxt startup (#35382)experimental.attachDebugInfo (7c2397ae6)test mode for bundle size tests (#35558)expectNoClientErrors (9f3123f60)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)A new InputRating component lets you display and collect ratings, with support for half-steps, custom length, clearing, hover preview and any icon:
<script setup lang="ts">
const value = shallowRef(3.5)
</script>
<template>
<UInputRating v-model="value" :step="0.5" hoverable />
</template>
Nuxt UI now embeds the icons it uses into your build, so they render straight away during SSR and work fully offline instead of being fetched from the Iconify API at runtime. As long as the collection is installed locally, this happens automatically for Nuxt UI's own icons (the lucide collection by default), on both Nuxt and pure Vue/Vite.
On Nuxt this rides on @nuxt/icon's existing client bundle. On Vue the @nuxt/ui/vite plugin gains an icon.clientBundle option to bring the same bundling to Vite, either by listing icons or by scanning your source. This is powered by the standalone Vite plugin and utils added in @nuxt/icon v2.3.0:
import ui from '@nuxt/ui/vite'
export default defineConfig({
plugins: [
ui({
icon: {
clientBundle: {
// list them explicitly...
icons: ['lucide:heart', 'simple-icons:github'],
// ...or scan your whole app
scan: true
}
}
})
]
})
!TIP Install each collection you use with
@iconify-json/{collection_name}so its icons can be bundled. See the Icons integration for the details.
unmountOnHideThe Modal and Slideover components now accept the unmountOnHide prop. Set it to false to keep their content mounted while closed so form state, scroll position and expensive children survive open/close cycles:
<template>
<UModal :unmount-on-hide="false">
<UButton label="Open" />
<template #content>
<!-- kept alive when the modal is closed -->
</template>
</UModal>
</template>
actions prop for tool approval (#6694) (1a3a9dc)unmountOnHide prop (#6523) (f03f98b)close and closeIcon props (#6669) (53e88a4)loading and loadingIcon props (#6707) (86cd25c)unmountOnHide prop (#6626) (4deb61b), closes #5839 #3605@nuxt/icon client bundle (#6633) (8d46034)enableTouch prop (#6626) (54125f3), closes #2346claude action (#6693) (7bdcb13)getScrollElement virtualize option (#6650) (a84de85)getScrollElement virtualize option (#6657) (7e6d0f7)$attrs to root element when to prop is absent (#6628) (d3b5f1d)data-slot to component root (#6643) (c9c5232)prefers-reduced-motion in animations (#6723) (f951529)arrowdown to shiftable keys (#6702) (2128414)aria-disabled attribute when disabled (#6653) (c3b2996)useRoute().fullPath reactive across navigations (#6696) (c256997)rel prop to internal links (#6677) (276302e)localePath fails (#6637) (906e8fd)hookOnce in colors plugin (#6658) (e4ca579)create-item (#6689) (a71dece)defaultVariants override withDefaults (#6686) (f5e58fb)sideEffects for barrel tree-shaking (#6729) (2cc2849)useComponentProps from the component-types barrel (#6648) (6576fb8)Full Changelog: https://github.com/nuxt/ui/compare/v4.9.0...v4.10.0
warmupStrategy: false when defaulting to preload - by @harlan-zw in https://github.com/nuxt/scripts/issues/828 (6cbea)