4.4.0/4.4.2 is the next minor release (v4.4.2 was published with no changes due to a publishing failure)
👀 Highlights
🏭 createUseFetch and createUseAsyncData
You can now create custom instances of useFetch and useAsyncData with your own default options (#32300).
// composables/api.ts
// Simple defaults
export const useClientFetch = createUseFetch({
server: false,
})
// Dynamic defaults with full control over merging
export const useApiFetch = createUseFetch((currentOptions) => {
const runtimeConfig = useRuntimeConfig()
return {
...currentOptions,
baseURL: currentOptions.baseURL ?? runtimeConfig.public.baseApiUrl,
}
})
Then use them exactly like useFetch – they're fully typed and support all the same options:
<!-- pages/dashboard.vue -->
<script setup lang="ts">
// Uses your baseURL from runtimeConfig automatically
const { data: users } = await useApiFetch('/users')
</script>
When you pass a plain object, your usage options automatically override the defaults. When you pass a function, you get full control over how options are merged – which means you can compose interceptors, headers, and other complex options however you need.
Under the hood, this is powered by a new Nuxt ad-hoc module that scans your composables directory and automatically registers your custom instances for key injection – so they work seamlessly with SSR, just like useAsyncData and useFetch.
There's also createUseAsyncData for the same pattern with useAsyncData.
📖 Read more: createUseAsyncData
🗺️ Vue Router v5
We've upgraded to vue-router v5 (#34181), which removes the dependency on unplugin-vue-router. This is the first major vue-router upgrade since Nuxt 3, and it comes with a bunch of improvements under the hood.
For most apps, this should be a transparent upgrade. If you're using unplugin-vue-router directly, you can remove it from your dependencies.
The next step will be taking typed routes out of experimental status. 👀
💪 Typed Layout Props in definePageMeta
You can now pass props to your layouts directly from definePageMeta (#34262). This means your layouts can be parameterised per-page without needing to use provide/inject or other workarounds. Check out the updated docs for the full details.
// pages/dashboard.vue
definePageMeta({
layout: {
name: 'panel',
props: {
sidebar: true,
title: 'Dashboard',
},
},
})
Even better – the props are fully typed (#34409). If your layout defines props, you'll get autocomplete and type-checking in definePageMeta.
<!-- layouts/panel.vue -->
<script setup lang="ts">
defineProps<{
sidebar?: boolean
title?: string
}>()
</script>
📖 Read more: Layouts
🗣️ useAnnouncer Composable
Accessibility got a major boost with the new useAnnouncer composable and <NuxtAnnouncer> component (#34318). While useRouteAnnouncer handles page navigation for screen readers, many apps need to announce dynamic in-page changes – form submissions, loading states, search results, and more.
<!-- app.vue -->
<template>
<NuxtAnnouncer />
<NuxtRouteAnnouncer />
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</template>
<!-- components/ContactForm.vue -->
<script setup lang="ts">
const { polite, assertive } = useAnnouncer()
async function submitForm() {
try {
await $fetch('/api/contact', { method: 'POST', body: formData })
polite('Message sent successfully')
}
catch (error) {
assertive('Error: Failed to send message')
}
}
</script>
Note: This is part of our accessibility roadmap. You don't need to use it everywhere – for many interactions, moving focus to new content or using native form validation is sufficient. useAnnouncer is most useful when content changes dynamically without a corresponding focus change.
📖 Read more: useAnnouncer
🚀 Migrate to unrouting
We've migrated Nuxt's file-system route generation to unrouting (#34316), which uses a trie data structure for constructing routes. The cold start is roughly the same (~8ms vs ~6ms for large apps), but dev server changes are up to 28x faster when you're not adding/removing pages, and ~15% faster even when you are.
This also makes route generation more deterministic – it's no longer sensitive to page file ordering.
🍫 Smarter Payload Handling for Cached Routes
When a cached route (ISR/SWR) is rendered at runtime with payload extraction enabled, the browser immediately fetches _payload.json as a second request – which triggers a full SSR re-render of the same page. In serverless environments, this can spin up a second lambda before the first response has even finished streaming.
This release addresses this with two changes (#34410):
- A new
payloadExtraction: 'client' mode that inlines the full payload in the initial HTML response while still generating _payload.json for client-side navigation
- A runtime in-memory LRU payload cache so that
_payload.json requests can be served without a full re-render
// nuxt.config.ts
export default defineNuxtConfig({
experimental: {
payloadExtraction: 'client',
},
})
Note: payloadExtraction: 'client' will become the default with compatibilityVersion: 5. The runtime cache is active for all users.
📖 Read more: Experimental payloadExtraction
🍪 refresh Option for useCookie
If you're using cookies for session management, you've probably run into the problem of needing to extend a cookie's expiration without changing its value. The new refresh option makes this simple (#33814):
const session = useCookie('session-id', {
maxAge: 60 * 60,
refresh: true,
})
// Extends expiration each time, even with the same value
session.value = session.value
📖 Read more: useCookie
♻️ useState Reset to Default
useState and clearNuxtState now support resetting to the initial value instead of clearing to undefined (#33527). This aligns with how useAsyncData handles resets and is more intuitive for state management.
const count = useState('counter', () => 0)
count.value = 42
// Resets to 0 (the init value), not undefined
clearNuxtState('counter')
📖 Read more: clearNuxtState
🕵️♂️ Better Import Protection
Inspired by features in TanStack Start, import protection now shows suggestions and a full trace of where a problematic import originated (#34454). This makes it much easier to debug why a server-only import ended up in your client bundle.
For example, if you accidentally import from a server route in a component:
The trace shows the import chain (the component was imported from a page), the exact line of code, and actionable suggestions for how to fix it.
We plan to continue work on improving our error messages. 🪵
🔮 View Transitions Types
You can now define view transition types in Nuxt's experimental view transitions support (#31982). This lets you use different transition styles for different navigation patterns (forwards vs. backwards, tabs vs. pages, etc.).
📖 Read more: View Transitions API
💡 Improved optimizeDeps Hints
When Vite discovers new dependencies at runtime and triggers page reloads, Nuxt now shows a clear, copy-pasteable nuxt.config.ts snippet to pre-bundle them (#34320). It also warns about unresolvable entries at startup.
🏷️ Normalised Page Component Names (Experimental)
A new experimental option normalises page component names to match route names (#33513), which can help with consistency in devtools and debugging.
// nuxt.config.ts
export default defineNuxtConfig({
experimental: {
normalizeComponentNames: true,
},
})
📖 Read more: normalizeComponentNames
⚡ Build Profiling
Ever wondered where your build time goes? You can now get a detailed performance breakdown of your Nuxt builds (#34468, nuxt/cli#1243):
nuxt build --profile
This produces a report showing duration, RSS delta, and heap delta for every build phase, module, and bundler plugin:
It also profiles individual modules and bundler plugins, making it easy to spot bottlenecks. Three output formats are written:
- Chrome Trace (
.nuxt/perf-trace.json) – open in chrome://tracing or Perfetto for a visual timeline
- JSON report (
.nuxt/perf-report.json) – machine-readable data for tracking over time
- CPU profile (
nuxt-build.cpuprofile) – open in Chrome DevTools or VS Code for flame graphs
For even more detail, use --profile=verbose to print timing breakdowns to the console.
📖 Read more: nuxt build
We'll be using this feature to make Nuxt even faster – and if performance is something you care about, this might be a good opportunity to contribute!
🔥 Performance Improvements
- 14,000x faster module ID parsing – replaced
new URL() + regex chain with a single indexOf + slice (#34451)
- Disabled NuxtLink visibility prefetching in dev – stops Vite from discovering and reloading deps unnecessarily during development (#34325)
✅ Upgrading
Our recommendation for upgrading is to run:
npx nuxt upgrade --dedupe
This will deduplicate your lockfile and help ensure you pull in updates from other dependencies that Nuxt relies on, particularly in the unjs ecosystem.
Tip: Check out our upgrade guide if upgrading from an older version.
👉 Changelog
compare changes
🚀 Enhancements
- nuxt: Migrate to unrouting for nuxt file system -> route generation (#34316)
- nuxt: Allow setting layout props in
definePageMeta (#34262)
- nuxt: Upgrade to vue-router v5 (#34181)
- nuxt: Add
useAnnouncer composable and <NuxtAnnouncer> component (#34318)
- nuxt: Add reset to default functionality for
useState and clearNuxtState (#33527)
- nuxt,schema: Experimentally normalise page component names to match route (#33513)
- nuxt: Add
refresh option to useCookie (#33814)
- nuxt: Export page composables from
#app/composables/pages (#33453)
- vite: Improved optimizeDeps hints (#34320)
- nuxt: Add View Transitions Types support (#31982)
- nuxt: Typed props for
layout property in definePageMeta (#34409)
- deps: Upgrade
hookable to v6 (#33905)
- deps: Update to std-env v4 (#34457)
- nitro,nuxt: Add suggestions + trace to import protection (#34454)
- kit,nitro,nuxt,schema,vite: Add build profiling (#34468)
- nuxt,kit,schema: Add a factory function for
useFetch and useAsyncData (#32300)
🔥 Performance
- nuxt: Disable NuxtLink visibility prefetching in dev mode (#34325)
- nuxt,vite,webpack: Use string parsing for module ids (#34451)
- nitro,schema: Inline payload in HTML for cached routes + add
payloadExtraction: 'client' (#34410)
🩹 Fixes
- schema: Do not bundle types (#34300)
- nuxt: Always inject explicit prerender routes into prerender queue (#34311)
- vite: Resolve package CSS paths in dev manifest (#34303)
- nuxt: Wait for layout transition to finish before scrolling to top (#34206)
- kit: Prevent duplicate
moduleDependencies options (#34330)
- nuxt: Retain promise methods after destructuring (#34319)
- nitro: Encode forward slashes in payload (#34375)
- nuxt: Do not cache
_payload.json in dev mode (#34365)
- nuxt: Accept refs as inputs in
NuxtLink.useLink (#34380)
- vite: Add nitro dev handler after vite proxy middleware (#34349)
- nuxt: Hydrate prerendered pages with initial route + replace after hydration (#34211)
- nitro: Escape strings rendered within js payload script tags (d52a4fdd7)
- vite: Recreate ViteNodeServer after config restart (#34396)
- nitro: Allow navigation from error page to external protocols (#34405)
- nitro,nuxt,schema: Don't extract payloads w/
ssr: false (#34327)
- nuxt: Replace destr with JSON.parse in cookie encode/decode (#34452)
- vite: Normalise development ssr app stacktrace (#33989)
- vite: Update ssr styles plugin for rolldown compatibility (#34435)
- nuxt: Script block extract regex conflict (#34412)
- vite: Move
rollup-plugin-visualizer to optional peer dep (#34458)
- nuxt: Restore island head entries from payload during hydration (#34491)
- kit: Share
asyncNuxtStorage across kit instances (#34492)
- nuxt: Close BroadcastChannel and add error handling in refreshCookie (#34508)
- nuxt: Check file freshness before truncating in cache restore (#34509)
- nuxt: Never preload manifest (#34511)
- nitro: Validate island handler context (#34510)
- nuxt: Pass deleteCount to splice in preloadRouteComponents (#34514)
- nuxt: Fix cookie expiration timeout for long-lived cookies (#34513)
- nuxt: Handle rejected promise in view transition abort (#34515)
💅 Refactors
- nuxt: Nullish coalesce instead of non-null assertions (#34331)
- nitro,vite: Set ssr export conditions + vue feature flags within nitro builder (#34379)
- nitro,nuxt: Add
/h3 subpath export for forward-compat (#34383)
- nuxt: Use JSON.parse instead of
destr (#34037)
- vite: Use vite's
transformWithEsbuild for analysis (#34447)
- nitro,vite: Use babel for experimental decorator support (#34465)
- webpack: Remove unused
event.context.webpack code (#34502)
📖 Documentation
- Update instructions for typedPages with public hoist pattern (#34285)
- Add alternative editor LSP setup guidance (#34322)
- Improve
<NuxtImg> fetch priority example (#34341)
- Update Cloudflare dashboard path (#34372)
- Clarify --preset option in nuxt build command (#34335)
- Add shared/ directory to Nuxt 4 upgrade guide (#34391)
- Update link redirects + reduce checking flakiness (01aa49659)
- Add import for fileURLToPath in example code (#34401)
- Capitalise title (#34395)
- Update testing guide to test-utils v4 (#34408)
- Add pending back for
useFetch + useLazyFetch docs (#34449)
- Correct md heading level (#34467)
- Fix twoslash code examples (#34469)
- Remove duplicated word (5a22feaec)
- Add typed layout props docs (#34507)
🏡 Chore
- Prevent layout shift on banner image readme (#34309)
- Use npmx links 💖 (#34340)
- Use
nuxt/.github pr template (5502ccd7b)
- Update link for nuxt ossf badge (#34406)
- Remove unnecessary workspace resolutions (#34453)
- Migrate npm badges and links to npmx.dev (71eb13c21)
- Publish 4.x releases to
4x and latest tags (1bc9bfd10)
- Enforce consistent return-await usage (#34490)
- vite: Optional chaining for nitroApp.hooks (24a12c810)
- kit: Cast nitroConfig to
any (cc380ba3b)
- Expand
engines.node following #34458 (#34458)
- Update
installed-check and engines.node (0c5869c2d)
- Lint (31028d2e0)
✅ Tests
- Add page path gen benchmark (#34315)
- Add dev server benchmark for page route generation (#34323)
- Skip bundle tests when running in stubbed mode (#34450)
- Remove curly braces from inlined css expectations (c9ab2747f)
- Update bundle size (f8e243a57)
- Update bundle size (102f7ffe1)
- Update bundle size (dcebd1800)
🤖 CI
- Optimise ci workflows (#34274)
- Run benchmarks on pushes to
main (6abf31062)
- Avoid checkout in
issue-needs-reproduction (7849b34c8)
- Merge all issue labelled workflows (fdd30349a)
- Fail fast on matrices for merge groups (1008d3afd)
- More perf optimisations for merge queues (297e4e526)
- Move build step after runtime/unit tests (#34388)
- Update default branches (ba251c697)
- Bump matrix timeout (9026c16c8)
- Give
issues: write permission to needs-reproduction workflow (ba3412b49)
- Include
v5 prefix in skip-changelog filter (dbc1a8666)
- Don't run actions against release PRs (961589297)
- Run ci on push to 4.x/3.x (9240729e8)
- Only run provenance check on PRs (5ba227449)
❤️ Contributors