Changelog

Latest changes, improvements, and fixes across Nuxt core and the official modules.

v2.1.0

v2.1.0 is the next minor release.

👀 Highlights

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.

🖼️ IPX v4

Under the hood, we've upgraded to IPX v4 which brings significant improvements:

  • Smaller bundles: IPX is now ESM-only with reduced dependency size
  • Stronger SVG security: SVG sanitization now always runs, regardless of optimization settings. This fixes a potential XSS vector where svgo: false would return untouched SVGs. The sanitizer now removes SMIL attribute injection, foreign content (<foreignObject>, <iframe>, etc.), and unsafe URIs across all elements
  • New image modifiers: opacity, brightness, saturation, hue, lightness, autoorient, dilate, erode, clahe, and linear
  • Better error messages: Invalid modifier arguments now return 400 errors with clear messages instead of cryptic 500s
  • Custom URL parsing: IPX now supports custom URL formats via the parseURL option

🌐 New Providers

This release adds support for eight new image optimization services:

  • Builder.io - Builder.io's Image API (#2050)
  • Cloudflare Images - Cloudflare's dedicated image service (#2100)
  • EdgeOne Pages - Tencent EdgeOne Pages (#2173)
  • Flyimg - Self-hosted image resizing (#2125)
  • imgproxy - Fast, secure image processing server (#2117)
  • Lorem Picsum - Placeholder images for development (#2106)
  • Supabase - Supabase Storage image transformations (#2027)
  • Umbraco - Umbraco CMS image cropper (#2165)

🔧 Provider Improvements

  • Directus: Added support for sharp transforms and a key modifier for named presets (#2207)
  • Sanity: Support for absolute URLs with automatic project/dataset extraction (#2274), and a configurable baseURL for custom CDN setups (#2273)
  • AWS Amplify & Vercel: minimumCacheTTL is now configurable (#2278)

📦 Type Exports

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'

👉 Changelog

compare changes

🚀 Enhancements

  • deps: migrate to ipx v4 (#2094)
  • provider: add EdgeOne Pages provider (#2173)
  • imgproxy: add provider (#2117)
  • directus: add sharp transforms & key modifier (#2207)
  • sanity: support absolute URLs with project and dataset extraction (#2274)
  • awsAmplify,vercel: make minimumCacheTTL configurable (#2278)
  • sanity: add configurable baseURL option to Sanity provider (#2273)
  • runtime: export Picture and Image props types (#2115)
  • umbraco: add image provider (#2165)
  • flyimg: add Flyimg built-in provider (#2125)
  • cloudflareimages: add new provider (#2100)
  • builderio: add Builder.io Image API provider (#2050)
  • picsum: add Lorem Picsum placeholder image provider (#2106)
  • supabase: add image provider (#2027)

🩹 Fixes

  • flyimg: encode all # characters in color values (137ee63)
  • remove duplicated provider key in generated image options (#2279)
  • nuxt-picture: align preload link type with unhead v3 (#2254)
  • github: set max size of 460 (7e561f6)
  • umbraco: shallow clone modifiers before modifying (237e480)
  • cloudflare: add baseURL for urls with no operations (#2162)
  • nuxt-img: set imagesrcset preload when densities are set w/o sizes (#2176)
  • fastly: handle absolute URLs and merge preexisting query params (#2140)
  • ipx: merge user-defined runtimeConfig.ipx with defaults (#2134)
  • directus: add withoutEnlargement modifier type (#2133)
  • shopify: merge preexisting query params with modifiers (#2135)
  • reduce max-width breakpoint values by 1px (#2132)
  • netlifyImageCdn,netlifyLargeMedia: set netlify remote_images config (#2086)
  • deduplicate formats (#2038)
  • sanitise custom provider import names (#2092)
  • guard access from layer.config.image (23b8cef)
  • bunny: merge query params with existing image src (#2054)
  • do not shadow custom provider types with builtins (#2016)
  • nuxt-picture: allow data-* in imgAttrs (#2017)
  • resolve public/ dir in relation to rootDir (3e07761)

📖 Documentation

  • sanity: document baseURL option (6f59ab1)
  • shopify: update NuxtImg syntax to use v-bind for modifiers (#2277)
  • update list of auto-detected providers (#2147)
  • add sharp cross-platform installation section (#2142)
  • add preload example for NuxtImg (#2124)
  • exempt patterns from cf _routes.json (#2136)
  • update placeholder attributes in <NuxtImg /> example (#2074)
  • add nuxt.care health badge (93de8a9)
  • update docus and add mcp name (#2080)
  • reference from Vercel's Edge Network to Vercel's CDN for image optimization. (2deacc0)
  • fix Nuxt badge icon (#2049)
  • update installation warning (#2061)
  • fix storyblok links (#2046)
  • add deno example (#2012)

📦 Build

  • deps: bump serialize-javascript from 7.0.4 to 7.0.5 (#2249)
  • deps: bump js-cookie from 3.0.5 to 3.0.7 (#2248)
  • deps: bump ws from 8.17.1 to 8.20.1 (#2246)
  • deps: bump h3 from 1.15.6 to 1.15.9 (#2177)
  • deps: bump h3 from 1.15.4 to 1.15.5 (#2085)

🏡 Chore

  • run module prepare in prepack (97d3758)
  • move consola to dev dependencies (#2275)
  • migrate to pnpm v11 (#2236)
  • migrate resolutions to pnpm-workspace.yaml (9b80ce6)
  • migrate npm badges and links to npmx.dev (fd87f7d)
  • remove unused slot-related code from <NuxtPicture> (#2145)
  • update lint scripts in package.json (#2139)
  • remove deprecated github silent config (#2026)
  • revert pnpm trust policy and restore provenance action (13b88c1)
  • update pnpm to 10.21 and enable trust policy (03b5ef4)

🤖 CI

  • add workflow to comment on issues resolved in the latest release (e529f37)
  • improve workflows (#2288)
  • migrate to uppt (#2286)
  • migrate agentscan-action to v2 (2f8b332)
  • add agent-scan workflow to flag bot-authored PRs (23ee710)
  • add merge group config (#2137)
  • rename workflow (777239c)
  • avoid checkout for reproduction comment (a200dae)
  • pin github actions to full-length commit shas (f60d0f7)
  • unpin workflow (d1cb12b)
  • try pinning (09e5efb)
  • update var (402c729)
  • use new shared dependency review workflow (906111c)

🎉 New Contributors

  • venzil (@q153877011)
  • Vlad Z. (@Nks)
  • kheiner (@kheiner)
  • Berk Altıok (@berkaltiok)
  • 山吹色御守 (@KazariEX)
  • dependabotbot (@dependabotbot)
  • Jens Morten Mikkelsen (@jemomi)
  • Bernhard Berger (@bernhardberger)
  • FKarstein (@FKarstein)
  • Sergiu Daniel Vidican (@vsergiu93)
  • Mohamed Fadl (@FADL285)
  • Martin Arce (@divagueame)
  • Iniubong Obonguko (@codiini)
  • Alireza Jahandideh (@Youhan)
  • Leon Hornig (@Leon338)
  • Dino Kupinic (@Dino-Kupinic)
  • wotan-allfather (@wotan-allfather)
  • Hugo (@HugoRCD)
  • IO-Fire (@IO-Fire)
  • Ingo Fabbri (@infabo)
  • Andrey Shevtsov (@andreyshivas)
  • Tony Costanzo (@Patrity)

❤️ Contributors

  • Daniel Roe (@danielroe)
  • Luke Nelson (@luc122c)
  • venzil (@q153877011)
  • Vlad Z. (@Nks)
  • kheiner (@kheiner)
  • Berk Altıok (@berkaltiok)
  • Nestor Vera (@hacknug)
  • Damian Głowala (@DamianGlowala)
  • 山吹色御守 (@KazariEX)
  • dependabotbot (@dependabotbot)
  • Julien Huang (@huang-julien)
  • Jens Morten Mikkelsen (@jemomi)
  • Bernhard Berger (@bernhardberger)
  • FKarstein (@FKarstein)
  • Sergiu Daniel Vidican (@vsergiu93)
  • Mohamed Fadl (@FADL285)
  • Martin Arce (@divagueame)
  • Iniubong Obonguko (@codiini)
  • Alireza Jahandideh (@Youhan)
  • Leon Hornig (@Leon338)
  • Dino Kupinic (@Dino-Kupinic)
  • wotan-allfather (@wotan-allfather)
  • Hugo (@HugoRCD)
  • Sébastien Chopin (@atinux)
  • IO-Fire (@IO-Fire)
  • Ingo Fabbri (@infabo)
  • Maxime Pauvert (@maximepvrt)
  • Andrey Shevtsov (@andreyshivas)
  • Domenik Reitzner (@dreitzner)
  • Tony Costanzo (@Patrity)
  • abeer0 (@iiio2)
v4.1.0

4.1.0 is the next minor release.

👉 Changelog

compare changes

🚀 Enhancements

  • e2e: Silence server logs and allow customising log level (#1735)
  • runtime: Improve app.root and app.teleports html tag setup (#1726)

🩹 Fixes

  • e2e: Add warning message for e2e in nuxt vitest setup (#1691)
  • runtime-utils: Ensure RouterLink stub works with pages: false (#1687)
  • config: Patch ssr.resolve.conditions to remove import (#1732)
  • config: Browser setup in defineVitestProject (#1724)
  • config: Inline config for useRuntimeConfig() composable (#1737)
  • config: Nested browser-mode Nuxt Vitest projects with viteEnvironmentApi (#1708)

📦 Build

  • Migrate to tsdown (#1720)

🏡 Chore

  • Drop pkg.pr.new nitro resolution (#1681)
  • Migrate to pnpm v11 (#1701)
  • Move h3-next to optional dependencies (#1733)

✅ Tests

  • Add test for autoImport: false (#1688)

🤖 CI

  • Add agent-scan workflow to flag bot-authored PRs (13dd49cde)
  • Migrate agentscan-action to v2 (08f8ee220)

❤️ Contributors

  • Yoshihiro Yamaguchi (@yamachi4416)
  • Peter Budai (@peterbud)
  • Daniel Roe (@danielroe)

⚠️ 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.

👉 Changelog

compare changes

🩹 Fixes

  • nuxt: Clear hide/reset timeouts in set() (#35534)
  • nuxt: Preserve trailing slash in NuxtLink href when unset (#35501)
  • vite: Resolve SSR inlined CSS module class name mismatch (#35610)
  • nuxt: Sync layout meta during middleware on SSR (#35633)
  • nuxt: Update client URL to match SSR on fatal middleware error (#35637)
  • nuxt: Watch external component directories in development (#35652)
  • nuxt: Don't cross-pollute useAsyncData cache on reactive key change (#35656)
  • nuxt: Return global route for useRoute in detached effect scope (#35659)
  • nuxt: Ignore custom name or path when reusing an existing page in pages:extend (#35661)
  • nuxt: Preserve explicit useFetch method inference (#35671)
  • nuxt: Correct default export detection in plugin metadata (#35676)
  • nuxt: Reload real page module on HMR of JSX render-function pages (#35678)
  • vite: Ensure server sourcemap-preserver plugin actually runs (#35680)
  • nuxt: Resolve @unhead/vue/* from nuxt's dependency tree (#35690)
  • nuxt: Don't apply scroll behaviour after a subsequent nav (#35719)
  • vite: Preserve css suffix when extracting ssr inline styles (#35714)
  • nuxt: Don't exclude client entry module from style extraction (#35720)
  • kit: Avoid mutating layer configs when resolving options (#35729)
  • nuxt: Generate layout types even when pages module is disabled (#35717)
  • nitro: Skip resource hints for stylesheets already rendered as blocking links (#35691)
  • nuxt: Revalidate cached route payloads instead of using force-cache (#35672)
  • nuxt: Preserve query params in cached payload extraction (#35696)
  • rspack,webpack: Resolve loaders and runtime deps from nuxt dirs (#35568)
  • nuxt: Render client components in nested server components (#35669)
  • nuxt: Remove dev error overlay when error is cleared (#35821)
  • rspack,webpack: Resolve bundled postcss defaults from builder (#35823)
  • schema: Normalise slashes in app.buildAssetsDir (#35833)
  • nuxt: Case-fold route rule keys to match folded lookups (619963309)
  • nitro: Require loopback peer for chrome devtools workspace endpoint (00f71bb65)
  • nitro: Bound island props and v-for to prevent unauthenticated DoS (668cdfdfd)
  • nuxt: Reject reserved template island prop under runtime compiler (5b60017f7)
  • nuxt: Reject top-level as prop for islands (00a2b0494)

📖 Documentation

  • Document relative baseURL workarounds (#34004)
  • Warn about runtimeCompiler security best practices (b76c1a8bd)
  • Warn about validating server component props (f6d72628d)

✅ Tests

  • Guard against transient undefined _route in gotoPath (5391e7e6a)
  • Raise route-HMR polling timeout to reduce e2e flakiness (cbc757c63)
  • kit: Scope loadNuxt temp dir so it doesn't delete sibling fixtures (72e4b5578)

❤️ Contributors

  • Daniel Roe (@danielroe)
  • Florian Heuberger (@Flo0806)
  • Matej Černý (@cernymatej)
  • bdbch (@bdbch)
  • Elecmonkey (@elecmonkey)
  • Ryota Watanabe (@wattanx)
  • kealan (@KealanAU)
  • Julien Huang (@huang-julien)
  • Luke Nelson (@luc122c)
  • Harlan Wilton (@harlan-zw)
  • Mateleo (@Mateleo)
  • lutejka (@lutejka)
  • Darlan José Batista do Prado (@DarlanPrado)
  • Max (@onmax)
  • Sushant (@sushantguri)
  • raminjafary (@raminjafary)

⚠️ 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.

👉 Changelog

compare changes

🔥 Performance

  • nitro: Replace island teleports in a single html pass (#35515)
  • nuxt: Add vue.optionsApi and disable it for v5+ (#35791)
  • nuxt: Without pages, skip client plugins that require routing (#35794)
  • nuxt: Skip payload revival plugin when ssr: false (#35782)

🩹 Fixes

  • nitro: Read rspack dev output fs lazily for server entry (#35740)
  • rspack,webpack: Resolve loaders and runtime deps from nuxt dirs (#35568)
  • nuxt: Return global route for useRoute in detached effect scope (#35659)
  • nuxt: Ignore custom name or path when reusing an existing page in pages:extend (#35661)
  • nuxt: Render client components in nested server components (#35669)
  • nuxt: Preserve explicit useFetch method inference (#35671)
  • nuxt: Revalidate cached route payloads instead of using force-cache (#35672)
  • nuxt: Correct default export detection in plugin metadata (#35676)
  • nuxt: Clear hide/reset timeouts in set() (#35534)
  • kit,nuxt,rspack,schema,webpack: Add .mts file extension in resolver (#33845)
  • nuxt: Preserve trailing slash in NuxtLink href when unset (#35501)
  • nuxt: Don't cross-pollute useAsyncData cache on reactive key change (#35656)
  • nuxt: Filter plugin dependencies by build target (#35682)
  • nuxt: Reload real page module on HMR of JSX render-function pages (#35678)
  • nuxt: Resolve @unhead/vue/* from nuxt's dependency tree (#35690)
  • kit: Surface module load errors instead of masking as missing (#35497)
  • nuxt: Type auto-imported $fetch with nitro's $Fetch (#35704)
  • nuxt: Don't reference app config sources in shared and node tsconfigs (#35673)
  • vite: Resolve SSR inlined CSS module class name mismatch (#35610)
  • nuxt: Generate layout types even when pages module is disabled (#35717)
  • nitro: Skip resource hints for stylesheets already rendered as blocking links (#35691)
  • kit: Dedupe layers that are both auto-scanned and explicitly extended (#35712)
  • nuxt: Don't apply scroll behaviour after a subsequent nav (#35719)
  • vite: Ensure server sourcemap-preserver plugin actually runs (#35680)
  • vite: Preserve css suffix when extracting ssr inline styles (#35714)
  • nuxt: Watch external component directories in development (#35652)
  • nuxt: Don't exclude client entry module from style extraction (#35720)
  • nuxt: Amend cleanup command in NUXT_B7014 error message (#35735)
  • nuxt: Only pull in vue-router when there are island pages (#35739)
  • vite: Suppress external warnings for internal vite-node paths (#35744)
  • nuxt: Use scope-aware oxc parser for auto-imports (#35743)
  • nuxt: Sync layout meta during middleware on SSR (#35633)
  • nitro: Add alias for h3 that pins it to the version nuxt depends on (#35774)
  • nuxt: Preserve query params in cached payload extraction (#35696)
  • nuxt: Mirror runtime route tree in generated typed-router types (#35788)
  • nuxt: Warn when an imports preset from cannot be resolved (#35799)
  • kit: Avoid mutating layer configs when resolving options (#35729)
  • nuxt: Convert inline route rules exactly or drop with a warning (#35455)
  • nitro,nuxt,vite: Dedupe and normalise global css links in dev (#35834)
  • vite: Register template HMR plugin on dev servers (929c6c138)
  • nuxt: Remove dev error overlay when error is cleared (#35821)
  • rspack,webpack: Resolve bundled postcss defaults from builder (#35823)
  • schema: Normalise slashes in app.buildAssetsDir (#35833)
  • nitro: Bound island props and v-for to prevent unauthenticated DoS (4e35ae9ba)
  • nitro: Confine runtime payload cache to prerendering (ac9b41a36)
  • nuxt: Case-fold route rule keys to match folded lookups (ad624a75a)
  • nitro: Require loopback peer for chrome devtools workspace endpoint (0769c4f9b)
  • nuxt: Reject reserved template island prop under runtime compiler (ee6c84633)
  • nuxt: Reject top-level as prop for islands (581651ff3)

💅 Refactors

  • nuxt: Use tick based debounce for asyncData executes (#34151)
  • kit,nuxt: Replace semver with verkit (#35713)
  • rspack,webpack: Use DI model to split builders (#35751)

📖 Documentation

  • Add dev container setup guide (#35665)
  • Fix dev container setup guide (#35666)
  • Add explanation of 200.html and 404.html SPA fallbacks (#34483)
  • Expand payload extraction documentation (#35648)
  • Clarify NuxtLink componentName is the internal (devtools) name (#35658)
  • Add example for components dir pattern option (#35663)
  • Require a single root element (#35677)
  • Add reason for why you should never import Vue app code in nitro code (#34481)
  • Document disabling code-splitting with codeSplitting: false (#35683)
  • Document nuxt-client caveat for non-SFC components (#35654)
  • Clarify favicon does not use cdnURL by default (#35681)
  • Standardize section order and headings across docs (#35685)
  • Clarify onPrehydrate example comment (#35684)
  • Add useId as a known limitation of nuxt island (#35693)
  • Recommend status over pending in data fetching (#35694)
  • Fill gaps in API minimalVersion badges after #34485 (#35708, #34485)
  • Explain dynamic asset paths (#35695)
  • Document runtimeConfig env var casting edge cases (#35709)
  • Clarify module dependency resolution (#35718)
  • Add more writing guidelines (#35662)
  • Add note with alternatives to using remote layers (#35721)
  • Use nuxt rather than nuxi (8891e179c)
  • Document relative baseURL workarounds (#34004)
  • Warn about runtimeCompiler security best practices (449b63ab1)
  • Warn about validating server component props (2c981cb07)

📦 Build

  • nitro: Re-export type from augments to preserve module (dac937675)
  • nuxt: Remove .ts file extension from runtime/ imports (#35689)
  • ui-templates: Commit generated ui template files (#35770)

🏡 Chore

  • Add extension 🤦 (d595fb3d4)
  • Move to pnpm catalogs (#35748)
  • Update knip config, resolve issues, and run in ci (#35742)
  • Ignore @nuxt/telemetry in knip (9824d4f10)
  • ui-templates: Pass config file path to unocss (34e7a810d)
  • Allow regenerating lockfile in release script (c31389df3)
  • nuxt: Bump @nuxt/devtools to v3.3.1 (#35815)
  • Ensure types are setup before unit tests (784d8f700)
  • Simplify knip configuration (#35827)

✅ Tests

  • Reproduce duplicate CSS in shared chunks (#35649)
  • Prepare fixtures automatically before fixture and e2e runs (e8b3e6411)
  • Improve and refactor basic fixture (#35687)
  • Slim down client-only, chunk-error and axis-independent suites (#35706)
  • Do not run type checking when benchmarking nuxt build (6355f3bc9)
  • kit: Scope loadNuxt temp dir so it doesn't delete sibling fixtures (37301dd51)
  • Guard against transient undefined _route in gotoPath (ca92d082b)
  • Retry fixture prepare on transient ENOTEMPTY (3a6b59f96)
  • Raise route-HMR polling timeout to reduce e2e flakiness (01dc27b7e)
  • Update bundle size snapshot (30d24817c)

🤖 CI

  • Rebalance shards and drop non-vite windows fixtures (#35700)
  • Warm windows dependency cache on main (#35730)
  • Run knip in default and production modes (#35745)
  • Run knip on pull requests (d620aa972)
  • Prepare tests (c7bf7f738)
  • Also prepare tests in knip job (58f68bd64)
  • Check internal docs links on pull requests and all links weekly (#35767)

❤️ Contributors

  • Daniel Roe (@danielroe)
  • Lars Kappert (@webpro)
  • Matej Černý (@cernymatej)
  • Anthony Fu (@antfu)
  • Harlan Wilton (@harlan-zw)
  • Florian Heuberger (@Flo0806)
  • Anoesj Sadraee (@Anoesj)
  • Ryota Watanabe (@wattanx)
  • Alexander Lichter (@TheAlexLichter)
  • Nestor Vera (@hacknug)
  • Mateleo (@Mateleo)
  • Kevin Deng (@sxzz)
  • Robin (@OrbisK)
  • Bochkarev Ivan (@Ibochkarev)
  • Quentin Macq (@quentinmcq)
  • Julien Huang (@huang-julien)
  • Max (@onmax)
  • Luke Nelson (@luc122c)
  • Darlan José Batista do Prado (@DarlanPrado)
  • kealan (@KealanAU)
  • Sushant (@sushantguri)
  • raminjafary (@raminjafary)
  • Aubakirov Asker (@Askerka00)
  • lutejka (@lutejka)
  • Amulet Iris (@KazariAI)
  • bdbch (@bdbch)
  • Elecmonkey (@elecmonkey)
v4.0.0-alpha.9

   🚨 Breaking Changes

   🚀 Features

    View changes on GitHub
v4.0.0-alpha.8

   🚨 Breaking Changes

   🚀 Features

   🐞 Bug Fixes

    View changes on GitHub

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, 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:

// 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).
  • 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.

// 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.

📖 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_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.

<!-- 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

🪟 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 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.

!NOTEdefinePageMeta 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

🚦 enabled Option for useFetch and useAsyncData

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.

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

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):

<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

⚡️ 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"> 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.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:

if (import.meta.envName === 'staging') {
  // enable staging-only behaviour
}

📖 import.meta documentation

🔭 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, 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.

📦 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.

!WARNINGunhead 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.

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

🔥 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:

// 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, #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:

  • Vite 8: check custom Vite plugins and config against the Vite migration guide.
  • 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. 💚

👉 Changelog

compare changes

🚀 Enhancements

  • rspack: Upgrade to rspack v2 (#34929)
  • nuxt: Migrate to unhead v3 (#34793)
  • nuxt: Support named views via filename convention (#35123)
  • nuxt: Expose environment name on import.meta (#34844)
  • nuxt: Forward destination preload hints on link prefetch (#35144)
  • nuxt: Honour http proxy env vars in builder environment (#35183)
  • kit: Support allowImportingTsExtensions (87c214b34)
  • nitro,nuxt,schema: Add experimental SSR streaming (5f4efe5a5)
  • vite: Migrate to Vite 8 (#34256)
  • nitro,nuxt: Add support for tracing channels for ssr events (#35191)
  • kit,nitro,nuxt,schema,vite,webpack: nuxt/* build output contract (#35463)
  • nuxt: Expose NuxtLink prefetch props for custom slot (#34539)
  • rspack: Use rspack-specific Vue loader (a085e8e34)
  • rspack: Use rspack-specific Vue loader (#35566)
  • nuxt,vite: Attach vite HMR to the shared dev server listener (a8ea0c7e2)
  • nuxt: Add enabled option to AsyncData (#33260)
  • rspack: Use rsbuild dev server in middleware mode (#35575)
  • nuxt: Expose getIslandHash + hashKey + improve hash for keys (#35583)
  • nuxt: Add useLayout composable for accessing the resolved route layout (#35623)
  • nuxt: Support enabled option in useFetch (#35627)
  • Error code system (#35429)
  • nuxt: Require keyed function 'source' with compatibilityVersion 5 (d56662647)
  • kit: Order layer aliases before generic root aliases (~, @) (#35641)
  • kit: Add updateAppConfig util for module authors (#35651)
  • nuxt: Warn when parent page lacks NuxtPage (#35639)

🔥 Performance

  • nuxt,vite: Reuse vite watcher via experimental.watcher (#35143)
  • nuxt: Tree-shake unneeded plugin handling from prod builds (#35278)
  • nuxt: Defer dev-only nitro startup work (#35381)
  • nuxt: Reduce dev pages startup work (#35383)
  • vite: Skip unnecessary decorator transforms (#35395)
  • nuxt,rspack,vite,webpack: Migrate to rolldown-string (#35058)
  • nitro,nuxt,vite: Remove unused dependencies (#34861)
  • nuxt: Re-parse transformed file once in tree-shake plugin (#35509)
  • nuxt: Index scanned components by name to avoid quadratic lookup (#35510)
  • kit: Stat paths directly and probe extensions in parallel (#35511)
  • nitro: Load inline style chunks concurrently (#35514)
  • nuxt: Skip island-renderer chunk when no islands used (#35456)

🩹 Fixes

  • nuxt: Fix explicit return types of factory types (40a80ec7d)
  • vite: Do not await ready event for vite watcher (6fe539f52)
  • vite: Invalidate modules for legacy vite server (#35315)
  • kit,nuxt: Pass error as cause when we re-throw (#35387)
  • nuxt: Make virtual module IDs stable across hosts (#35317)
  • vite: Inline CSS for lazy components imported from #components (#35215)
  • nuxt: Validate <ClientOnly> fallback tag name (#35325)
  • vite: Extract server page CSS when inlineStyles is disabled (#35209)
  • nitro: Include nested layers in nitro transforms (#35327)
  • kit: Don't exclude layer module runtime files from app tsconfig (#35326)
  • nitro: Export empty styles object with ssr: false (#35330)
  • nuxt: Emit auto-import paths as files, not directories (#35333)
  • nuxt: Suppress spurious slot warning in NuxtPage during nav (#35335)
  • vite: Preserve backslash separators in windows named pipe path (#35336)
  • nuxt: Detach in-flight promise on useAsyncData teardown (#35328)
  • nuxt: Respect per-page keepalive set via definePageMeta (#34526)
  • nuxt: Align useFetch error type with runtime (#35346)
  • nuxt: Unwrap getter body in useFetch (#35343)
  • rspack,webpack: Strip invalid webpack PURE annotations (#35386)
  • nuxt: Guard against -1 when unregistering a router hook (#35391)
  • nuxt: Clean up load/error listeners in preloadPayload (#35392)
  • nuxt: Avoid idle wait when refreshing data after hydration (#35358)
  • nuxt: Hash serialized island props (#35356)
  • nuxt: Ensure page:start finishes before page:finish starts (#35408)
  • vite: Invalidate lazily-created SSR modules on render (#35410)
  • nuxt: Preserve plugin navigation during app boot (#35344)
  • vite: Invalidate stale SSR virtual modules on render (#35413)
  • nitro: Don't preload JS for lazy-hydrated components (#35342)
  • vite: Add extension to vite-node-runner virtual specifier (0bf3f1d5d)
  • vite: Invalidate ssr copy of regenerated templates (33a581ce7)
  • vite: Set define for NODE_ENV and __VUE_PROD_DEVTOOLS__ (#35444)
  • vite: Avoid emitted ssr styles file name collisions (#35432)
  • nuxt: Avoid syncing route too early when reused page remounts (#35440)
  • nuxt: Skip showing route errors for crawlers (#35411)
  • nuxt: Keep nested layout on deferred route during suspended layout switch (#35443)
  • nuxt: Restore deferred query route before page is mounted (#35442)
  • kit,nuxt: Resolve auto-import type paths through shared resolver (#35451)
  • nuxt: Make islands hash backwards-compatible (#35452)
  • vite: Give ssr its own hmr websocket port on vite 8.1 (#35458)
  • nuxt: Auto-import $fetch where possible (#35581)
  • nuxt: Update unctx to v3 (#35541)
  • nitro: Point cache driver at .mjs runtime output (#35263)
  • nuxt: Add head.push for client side (#35498)
  • nitro: Only reload nitro after initial nitro build (30a573733)
  • nuxt: Pass nuxt rootDir to typed-router context (#35565)
  • nuxt: Type __nuxt_error marker on NuxtError (06e976d2e)
  • kit: noUncheckedSideEffectImports (v5) + disable libReplacement (#35548)
  • nuxt: Node10 compatible subpath exports (a216d732d)
  • nuxt: Unwrap module mutation trackers from options after installing (#35574)
  • nuxt: Entity-encode redirect body to preserve query separators (#35486)
  • rspack,webpack: Use async entry in dev to avoid circular import (#35586)
  • nuxt: Tolerate missing dirs in parcel watcher (#35585)
  • nuxt: Respect saved scroll position on same-page back navigation from hash (#35608)
  • nuxt: Resolve unctx helper to an absolute path for server bundling (#35602)
  • nuxt: Apply unctx transform marker through magic-string (#35618)
  • schema: Enable HMR for defineNuxtComponent in JSX (#35620)
  • nuxt: Evict stale forwarded prefetch hints and fix key mismatch (#35621)
  • nuxt: Re-add computeIslandHash for backwards compatibility (353164bb5)
  • nitro: Correct nitro runtime config type key (bc3b53add)
  • schema: Resolve Vite cache directory from workspace (#35626)
  • nitro: Externalise dev SSR entry to avoid slow rollup re-parse (ff42c54a7)
  • nuxt: Drop extension on diagnostics import (c699cc0de)
  • nitro: Write prerender cache files atomically (#35619)
  • nitro: Don't import node:process in diagnostics (d73b5df6d)
  • nuxt: Apply latest navigation on concurrent navigateTo in built-in router (#35631)
  • nuxt: Update client URL to match SSR on fatal middleware error (#35637)

💅 Refactors

  • nitro,nuxt: Add explicit return types to runtime files (#35139)
  • nuxt: Resolve builder earlier + skip watcher for nuxt prepare (#35141)
  • nuxt: Delete cleared keys in clearNuxtData & clearNuxtState (#35142)
  • nuxt: Use jiti fsCache instead of deprecated cache (#35268)
  • nitro: Use walkResolver instead of deprecated resolveUnrefHeadInput (#35267)
  • nuxt: Add type predicates (#35208)
  • vite: Type vite-node-runner error data and resolve #vite-node types from source (#35334)
  • nuxt: Split head runtime plugin by environment (#35389)
  • nuxt: Use rolldown/utils instead of oxc-* dependencies (#34983)
  • kit,vite,webpack,nitro,schema: ssrStyles build output => lazy code provider (#35601)
  • nuxt,nitro: Use dev exports for no-build type-checking (#35605)
  • rspack: Use rsbuild to power rspack builder (#35489)
  • vite: Extract TemplateHMRPlugin from DevServerPlugin (#35579)

📖 Documentation

  • Add builder guide (#35576)
  • Correct and improve useAsyncData and useFetch (#35528)
  • Improving testing documentation for better understanding (#35526)
  • Fix incorrect transform signature in useAsyncData/useFetch type docs (#35403)
  • Add minimal version badges to API documentation (#34485)
  • Clarify usage of await with useFetch (#33745)
  • Add badges for new features (1839fe01d)
  • Remove enabled from useFetch docs (bf7810600)
  • Add tip about prerendering registration for pages (#35635)
  • Clarify that useFetch keys are unique per call site (#35645)
  • Clarify useFetch custom data error (#35647)

📦 Build

  • Use tsdown to build project (#35179)

🏡 Chore

✅ Tests

  • Strip host path from server bundle before measuring size (#35316)
  • Run playwright tests in dev mode too (#35329)
  • nuxt: Benchmark dev loadNuxt startup (#35382)
  • Update bundle size snapshot (64a834281)
  • Use h3 v1 syntax in test (1ea2eec07)
  • Use experimental.attachDebugInfo (7c2397ae6)
  • Update assertions (55f7ea025)
  • Pack with pnpm before running attw to honour publishConfig (e2f0dd69d)
  • Reduce flakiness on hydration tests (#35457)
  • Improve test stability on macos (#35462)
  • Force disable test mode for bundle size tests (#35558)
  • Pin typescript in basic-types fixture to dedupe vue (a3b824918)
  • webpack: Cover Vue loader scope hashes (824daffa0)
  • builders: Clean up Vue loader fixtures (ca18affc3)
  • Reenable webpack/rspack dev tests (#35571)
  • Await expectNoClientErrors (9f3123f60)
  • Mark tests as succeeding for rspack/webpack (513ad306e)
  • Add support for non-ascii filenames in public assets (#35636)
  • Minify server bundles in bundle size snapshot (#35638)
  • Run matrix-independent suites once across the fixture matrix (#35646)
  • Mark webpack/rspack tests as passing on 4.x (3ae095b29)

🤖 CI

  • Reenable bundle size tests 😱 (#35279)
  • Only run workflow in nuxt org (#35377)
  • Skip entire job, not just step (#35378)
  • Error handling for webhook triggers (#35379)
  • Update agentscan (#35397)
  • Only run benchmark tests in Nuxt org (#35414)
  • Don't auto-close team prs 🫣 (cd965f569)
  • Use vite-plus to manage pnpm (#35570)
  • Use parallel keyword for concurrent workflow steps (#35448)
  • Handle missing base branch (for stacked prs) (8d3daa34e)
  • Unlink issues from autoclosed PRs (#35615)

❤️ Contributors

  • Jordan Labrosse (@Jorgagu)
  • Luke Nelson (@luc122c)
  • Florian Heuberger (@Flo0806)
  • Daniel Roe (@danielroe)
  • Yannik Schröder (@yschroe)
  • Mateleo (@Mateleo)
  • Matej Černý (@cernymatej)
  • Eduardo San Martin Morote (@posva)
  • Darlan José Batista do Prado (@DarlanPrado)
  • Ulrich-Matthias Schäfer (@Fuzzyma)
  • Bochkarev Ivan (@Ibochkarev)
  • Julien Huang (@huang-julien)
  • Damian Głowala (@DamianGlowala)
  • KazariAI (@KazariAI)
  • Fahad Khan (@oniChan1226)
  • skoenfaelt (@skoenfaelt)
  • Cynthia Rey (@cyyynthia)
  • meomking (@CaptainWang98)
  • antlis (@antlis)
  • Néstor (@Nsttt)
  • Issayah (@VividLemon)
  • Alexander Lichter (@TheAlexLichter)
  • Anoesj Sadraee (@Anoesj)
  • Quentin Macq (@quentinmcq)
  • raminjafary (@raminjafary)
  • Kurt Medley (@HeavyMedl)
  • isksss (@isksss)
  • Matteo Gabriele (@MatteoGabriele)
  • Max (@onmax)
  • Harlan Wilton (@harlan-zw)

3.21.9 is the next patch release.

👉 Changelog

compare changes

🩹 Fixes

  • vite: Use spa entry for vite-node fallback (3.x) (c8410a16a)
  • kit,nuxt: Pass error as cause when we re-throw (#35387)
  • vite: Inline CSS for lazy components imported from #components (#35215)
  • nuxt: Validate <ClientOnly> fallback tag name (#35325)
  • vite: Extract server page CSS when inlineStyles is disabled (#35209)
  • nitro: Include nested layers in nitro transforms (#35327)
  • nitro: Export empty styles object with ssr: false (#35330)
  • nuxt: Emit auto-import paths as files, not directories (#35333)
  • nuxt: Suppress spurious slot warning in NuxtPage during nav (#35335)
  • vite: Preserve backslash separators in windows named pipe path (#35336)
  • nuxt: Detach in-flight promise on useAsyncData teardown (#35328)
  • nuxt: Respect per-page keepalive set via definePageMeta (#34526)
  • nuxt: Align useFetch error type with runtime (#35346)
  • nuxt: Unwrap getter body in useFetch (#35343)
  • rspack,webpack: Strip invalid webpack PURE annotations (#35386)
  • nuxt: Guard against -1 when unregistering a router hook (#35391)
  • nuxt: Clean up load/error listeners in preloadPayload (#35392)
  • nuxt: Avoid idle wait when refreshing data after hydration (#35358)
  • nuxt: Ensure page:start finishes before page:finish starts (#35408)
  • nuxt: Preserve plugin navigation during app boot (#35344)
  • vite: Add extension to vite-node-runner virtual import (e81a0c484)
  • nuxt: Make virtual module IDs stable across hosts (#35317)
  • nuxt: Hash serialized island props (#35356)
  • vite: Avoid emitted ssr styles file name collisions (#35432)
  • nuxt: Avoid syncing route too early when reused page remounts (#35440)
  • nuxt: Keep nested layout on deferred route during suspended layout switch (#35443)
  • vite: Set define for NODE_ENV and __VUE_PROD_DEVTOOLS__ (#35444)
  • nuxt: Restore deferred query route before page is mounted (#35442)
  • nuxt: Make islands hash backwards-compatible (#35452)
  • nuxt: Pass nuxt rootDir to typed-router context (#35565)
  • nitro: Only reload nitro after initial nitro build (6ba8e26a5)
  • schema: Enable HMR for defineNuxtComponent in JSX (#35620)
  • schema: Resolve Vite cache directory from workspace (#35626)
  • nitro: Write prerender cache files atomically (#35619)
  • nuxt: Apply latest navigation on concurrent navigateTo in built-in router (#35631)

📖 Documentation

  • Improving testing documentation for better understanding (#35526)
  • Add tip about prerendering registration for pages (#35635)
  • Clarify useFetch custom data error (#35647)
  • Drop reference to non-existent section (bd1943d04)
  • Update link to /3.x slug (71bdb972d)

🏡 Chore

  • Allow node version updates (#35297)

✅ Tests

  • Assert dev css delivery transport for server pages (9851e993b)
  • Count only render-blocking stylesheet links for page styles (b0e895a7f)
  • Await expectNoClientErrors (afffe7f09)

🤖 CI

  • Handle missing base branch (for stacked prs) (13a933f8a)
  • Unlink issues from autoclosed PRs (#35615)

❤️ Contributors

  • Daniel Roe (@danielroe)
  • Florian Heuberger (@Flo0806)
  • Mateleo (@Mateleo)
  • Luke Nelson (@luc122c)
  • Darlan José Batista do Prado (@DarlanPrado)
  • Julien Huang (@huang-julien)
  • skoenfaelt (@skoenfaelt)
  • Anoesj Sadraee (@Anoesj)
  • raminjafary (@raminjafary)
  • Kurt Medley (@HeavyMedl)
  • isksss (@isksss)
  • Alexander Lichter (@TheAlexLichter)
  • Damian Głowala (@DamianGlowala)
  • Sami El Achi (@samiashi)
v4.10.0

✨ Highlights

🌟 InputRating component

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>

📦 Bundled, offline-ready icons

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:

vite.config.ts
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.

♻️ Keep overlay state with unmountOnHide

The 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>

🚀 Features

  • ChatPrompt: add body slot and focus highlight (#6709) (123184b)
  • ChatTool: add actions prop for tool approval (#6694) (1a3a9dc)
  • ContentSearch/DashboardSearch: support unmountOnHide prop (#6523) (f03f98b)
  • ContentToc: scroll list independently and center active link (#6697) (64b9f7d)
  • Drawer: add close and closeIcon props (#6669) (53e88a4)
  • Editor: allow disabling starter kit for plain text (#6713) (76d613c)
  • Empty: add loading and loadingIcon props (#6707) (86cd25c)
  • InputRating: new component (#5757) (cba2c2c)
  • Modal/Slideover: support unmountOnHide prop (#6626) (4deb61b), closes #5839 #3605
  • module: pre-bundle used icons into @nuxt/icon client bundle (#6633) (8d46034)
  • Popover: support enableTouch prop (#6626) (54125f3), closes #2346
  • Prompt: add claude action (#6693) (7bdcb13)
  • prose: configurable heading anchors and copy button (#6735) (f419731)
  • ScrollArea: add getScrollElement virtualize option (#6650) (a84de85)
  • Table: add getScrollElement virtualize option (#6657) (7e6d0f7)
  • unplugin: pre-bundle used icons into the Vue/Vite build (#6635) (dcf7cbc)

🐛 Bug Fixes

  • AuthForm: track password visibility per field (#6638) (0faeb92)
  • BlogPost/ChangelogVersion: format date in UTC to prevent hydration mismatch (#6722) (fe5bb23)
  • Button: allow inline event handlers with non-void return types (#6668) (269080a)
  • Carousel: prevent reset when plugin props use inline objects (f41d11e), closes #6221
  • ChatMessages: re-evaluate streaming indicator on each render (#6673) (5c986dd)
  • components: forward $attrs to root element when to prop is absent (#6628) (d3b5f1d)
  • components: forward data-slot to component root (#6643) (c9c5232)
  • components: respect prefers-reduced-motion in animations (#6723) (f951529)
  • ContentNavigation: key items by identity to prevent leading icon flash (#6640) (09fb52b)
  • defineShortcuts: add missing arrowdown to shiftable keys (#6702) (2128414)
  • defineShortcuts: defer standalone shortcuts that prefix a chain (#6703) (76ee176)
  • Editor: prevent suggestion menu blinking on keystroke (#6712) (7e6d8b0)
  • FileUpload: add aria-disabled attribute when disabled (#6653) (c3b2996)
  • inertia: make useRoute().fullPath reactive across navigations (#6696) (c256997)
  • Link: apply rel prop to internal links (#6677) (276302e)
  • Link: fall back to original path when localePath fails (#6637) (906e8fd)
  • LocaleSelect: add missing keys in emoji mapping (#6629) (fab73ab)
  • module: avoid unhead v2-only hookOnce in colors plugin (#6658) (e4ca579)
  • SelectMenu/InputMenu: only re-highlight first item with create-item (#6689) (a71dece)
  • Separator: forward fall-through attributes to root (#6641) (88baabc)
  • theme: use logical properties for RTL (#6724) (3179012)
  • types: type prose components in app config (#6711) (d56e39a)
  • useComponentProps: let app config defaultVariants override withDefaults (#6686) (f5e58fb)
  • useFileUpload: keep dropzone type filter reactive to accept (#6699) (000aba4)
  • useResizable: recover from corrupted persisted storage (#6704) (4c60745)
  • useResizable: share resize logic between mouse and touch (#6705) (730a54c)
  • useScrollspy: unobserve previous headings on update (#6700) (5944067)
  • useToast: dedupe duplicate ids and handle max of 0 (#6698) (71c623b)

🔥 Performances

  • components: drop the redundant inner in component extend (#6647) (3bf1a92)
  • module: declare sideEffects for barrel tree-shaking (#6729) (2cc2849)
  • types: decouple useComponentProps from the component-types barrel (#6648) (6576fb8)
  • types: import cross-component types from source, not the barrel (#6646) (7eab4bc)
  • vue: skip rewriting unchanged templates (#6730) (14b6001)

❤️ Contributors

  • @benjamincanac
  • @solidusite
  • @sandros94
  • @Dtem4ik
  • @mikenewbon
  • @innocenzi
  • @faizkhairi
  • @reslear
  • @syazany
  • @yamachi4416
  • @martipops
  • @typed-sigterm

Full Changelog: https://github.com/nuxt/ui/compare/v4.9.0...v4.10.0