Skip to content

Commit 40d9bdb

Browse files
authored
feat: add announcement blog post for nuxt v4.4 (#2205)
1 parent d38ce2d commit 40d9bdb

4 files changed

Lines changed: 277 additions & 0 deletions

File tree

content/blog/43.v4-4.md

Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
---
2+
title: Nuxt 4.4
3+
description: Nuxt 4.4 brings custom useFetch/useAsyncData factories, vue-router v5, a new accessibility announcer, typed layout props, build profiling, smarter payload handling, and much more.
4+
navigation: false
5+
image: /assets/blog/v4.4.png
6+
authors:
7+
- name: Daniel Roe
8+
avatar:
9+
src: https://github.com/danielroe.png
10+
to: https://bsky.app/profile/danielroe.dev
11+
date: 2026-03-12T00:00:00.000Z
12+
category: Release
13+
---
14+
15+
## 🏭 `createUseFetch` and `createUseAsyncData`
16+
17+
You can now create **custom instances** of `useFetch` and `useAsyncData` with your own default options ([#32300](https://github.com/nuxt/nuxt/pull/32300)).
18+
19+
```ts [composables/api.ts]
20+
// Simple defaults
21+
export const useClientFetch = createUseFetch({
22+
server: false,
23+
})
24+
25+
// Dynamic defaults with full control over merging
26+
export const useApiFetch = createUseFetch((currentOptions) => {
27+
const runtimeConfig = useRuntimeConfig()
28+
29+
return {
30+
...currentOptions,
31+
baseURL: currentOptions.baseURL ?? runtimeConfig.public.baseApiUrl,
32+
}
33+
})
34+
```
35+
36+
Then use them exactly like `useFetch` – they're fully typed and support all the same options:
37+
38+
```vue [pages/dashboard.vue]
39+
<script setup lang="ts">
40+
// Uses your baseURL from runtimeConfig automatically
41+
const { data: users } = await useApiFetch('/users')
42+
</script>
43+
```
44+
45+
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 &ndash; which means you can compose interceptors, headers, and other complex options however you need.
46+
47+
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 &ndash; so they work seamlessly with SSR, just like `useAsyncData` and `useFetch`.
48+
49+
There's also `createUseAsyncData` for the same pattern with `useAsyncData`.
50+
51+
:read-more{to="/docs/api/composables/create-use-async-data"}
52+
53+
## 🗺️ Vue Router v5
54+
55+
We've upgraded to [vue-router v5](https://github.com/vuejs/router) ([#34181](https://github.com/nuxt/nuxt/pull/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.
56+
57+
For most apps, this should be a transparent upgrade. If you're using `unplugin-vue-router` directly, you can remove it from your dependencies.
58+
59+
The next step will be taking typed routes out of experimental status. 👀
60+
61+
## 💪 Typed Layout Props in `definePageMeta`
62+
63+
You can now pass props to your layouts directly from `definePageMeta` ([#34262](https://github.com/nuxt/nuxt/pull/34262)). This means your layouts can be parameterised per-page without needing to use `provide`/`inject` or other workarounds. Check out the [updated docs](/docs/guide/directory-structure/layouts#layout-props) for the full details.
64+
65+
```ts [pages/dashboard.vue]
66+
definePageMeta({
67+
layout: {
68+
name: 'panel',
69+
props: {
70+
sidebar: true,
71+
title: 'Dashboard',
72+
},
73+
},
74+
})
75+
```
76+
77+
Even better &ndash; the props are **fully typed** ([#34409](https://github.com/nuxt/nuxt/pull/34409)). If your layout defines props, you'll get autocomplete and type-checking in `definePageMeta`.
78+
79+
```vue [layouts/panel.vue]
80+
<script setup lang="ts">
81+
defineProps<{
82+
sidebar?: boolean
83+
title?: string
84+
}>()
85+
</script>
86+
```
87+
88+
:read-more{to="/docs/guide/directory-structure/layouts"}
89+
90+
## 🗣️ `useAnnouncer` Composable
91+
92+
Accessibility got a major boost with the new `useAnnouncer` composable and `<NuxtAnnouncer>` component ([#34318](https://github.com/nuxt/nuxt/pull/34318)). While `useRouteAnnouncer` handles page navigation for screen readers, many apps need to announce **dynamic in-page changes** &ndash; form submissions, loading states, search results, and more.
93+
94+
::code-group
95+
```vue [app.vue]
96+
<template>
97+
<NuxtAnnouncer />
98+
<NuxtRouteAnnouncer />
99+
<NuxtLayout>
100+
<NuxtPage />
101+
</NuxtLayout>
102+
</template>
103+
```
104+
105+
```vue [components/ContactForm.vue]
106+
<script setup lang="ts">
107+
const { polite, assertive } = useAnnouncer()
108+
109+
async function submitForm() {
110+
try {
111+
await $fetch('/api/contact', { method: 'POST', body: formData })
112+
polite('Message sent successfully')
113+
}
114+
catch (error) {
115+
assertive('Error: Failed to send message')
116+
}
117+
}
118+
</script>
119+
```
120+
::
121+
122+
::note
123+
This is part of our [accessibility roadmap](https://github.com/nuxt/nuxt/issues/23255). You don't need to use it everywhere &ndash; 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.
124+
::
125+
126+
:read-more{to="/docs/api/composables/use-announcer"}
127+
128+
## 🚀 Migrate to `unrouting`
129+
130+
We've migrated Nuxt's file-system route generation to [`unrouting`](https://github.com/unjs/unrouting) ([#34316](https://github.com/nuxt/nuxt/pull/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.
131+
132+
This also makes route generation more deterministic &ndash; it's no longer sensitive to page file ordering.
133+
134+
## 🍫 Smarter Payload Handling for Cached Routes
135+
136+
When a cached route (ISR/SWR) is rendered at runtime with payload extraction enabled, the browser immediately fetches `_payload.json` as a second request &ndash; 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.
137+
138+
This release addresses this with two changes ([#34410](https://github.com/nuxt/nuxt/pull/34410)):
139+
140+
1. A new `payloadExtraction: 'client'` mode that inlines the full payload in the initial HTML response while still generating `_payload.json` for client-side navigation
141+
2. A runtime in-memory LRU payload cache so that `_payload.json` requests can be served without a full re-render
142+
143+
```ts [nuxt.config.ts]
144+
export default defineNuxtConfig({
145+
experimental: {
146+
payloadExtraction: 'client',
147+
},
148+
})
149+
```
150+
151+
::note
152+
`payloadExtraction: 'client'` will become the default with `compatibilityVersion: 5`. The runtime cache is active for all users.
153+
::
154+
155+
:read-more{to="/docs/guide/going-further/experimental-features#payloadextraction"}
156+
157+
## 🍪 `refresh` Option for `useCookie`
158+
159+
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](https://github.com/nuxt/nuxt/pull/33814)):
160+
161+
```ts
162+
const session = useCookie('session-id', {
163+
maxAge: 60 * 60,
164+
refresh: true,
165+
})
166+
167+
// Extends expiration each time, even with the same value
168+
session.value = session.value
169+
```
170+
171+
:read-more{to="/docs/api/composables/use-cookie"}
172+
173+
## ♻️ `useState` Reset to Default
174+
175+
`useState` and `clearNuxtState` now support resetting to the initial value instead of clearing to `undefined` ([#33527](https://github.com/nuxt/nuxt/pull/33527)). This aligns with how `useAsyncData` handles resets and is more intuitive for state management.
176+
177+
```ts
178+
const count = useState('counter', () => 0)
179+
count.value = 42
180+
181+
// Resets to 0 (the init value), not undefined
182+
clearNuxtState('counter')
183+
```
184+
185+
:read-more{to="/docs/api/utils/clear-nuxt-state"}
186+
187+
## 🕵️‍♂️ Better Import Protection
188+
189+
Inspired by features in [TanStack Start](https://tanstack.com/start/latest/docs/framework/react/guide/import-protection), import protection now shows **suggestions** and a full **trace** of where a problematic import originated ([#34454](https://github.com/nuxt/nuxt/pull/34454)). This makes it much easier to debug why a server-only import ended up in your client bundle.
190+
191+
For example, if you accidentally import from a server route in a component:
192+
193+
![More useful import protection error](/assets/blog/import-protection.png)
194+
195+
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.
196+
197+
We plan to continue work on improving our error messages. 🪵
198+
199+
## 🔮 View Transitions Types
200+
201+
You can now define [view transition types](https://developer.chrome.com/blog/view-transitions-update-io24#view-transition-types) in Nuxt's experimental view transitions support ([#31982](https://github.com/nuxt/nuxt/pull/31982)). This lets you use different transition styles for different navigation patterns (forwards vs. backwards, tabs vs. pages, etc.).
202+
203+
:read-more{to="/docs/getting-started/transitions#view-transitions-api-experimental"}
204+
205+
## 💡 Improved `optimizeDeps` Hints
206+
207+
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](https://github.com/nuxt/nuxt/pull/34320)). It also warns about unresolvable entries at startup.
208+
209+
## 🏷️ Normalised Page Component Names (Experimental)
210+
211+
A new experimental option normalises page component names to match route names ([#33513](https://github.com/nuxt/nuxt/pull/33513)), which can help with consistency in devtools and debugging.
212+
213+
```ts [nuxt.config.ts]
214+
export default defineNuxtConfig({
215+
experimental: {
216+
normalizeComponentNames: true,
217+
},
218+
})
219+
```
220+
221+
:read-more{to="/docs/guide/going-further/experimental-features#normalizecomponentnames"}
222+
223+
## ⚡ Build Profiling
224+
225+
Ever wondered where your build time goes? You can now get a detailed performance breakdown of your Nuxt builds ([#34468](https://github.com/nuxt/nuxt/pull/34468), [nuxt/cli#1243](https://github.com/nuxt/cli/pull/1243)):
226+
227+
```bash
228+
nuxt build --profile
229+
```
230+
231+
This produces a report showing duration, RSS delta, and heap delta for every build phase, module, and bundler plugin:
232+
233+
![Build timings report printed to console](/assets/blog/build-profiler.png)
234+
235+
It also profiles individual modules and bundler plugins, making it easy to spot bottlenecks. Three output formats are written:
236+
237+
- **Chrome Trace** (`.nuxt/perf-trace.json`) &ndash; open in `chrome://tracing` or Perfetto for a visual timeline
238+
- **JSON report** (`.nuxt/perf-report.json`) &ndash; machine-readable data for tracking over time
239+
- **CPU profile** (`nuxt-build.cpuprofile`) &ndash; open in Chrome DevTools or VS Code for flame graphs
240+
241+
For even more detail, use `--profile=verbose` to print timing breakdowns to the console.
242+
243+
:read-more{to="/docs/api/commands/build"}
244+
245+
We'll be using this feature to make Nuxt even faster &ndash; and if performance is something you care about, this might be a good opportunity to contribute!
246+
247+
## 🔥 Performance Improvements
248+
249+
- **14,000x faster module ID parsing** &ndash; replaced `new URL()` + regex chain with a single `indexOf` + slice ([#34451](https://github.com/nuxt/nuxt/pull/34451))
250+
- **Disabled NuxtLink visibility prefetching in dev** &ndash; stops Vite from discovering and reloading deps unnecessarily during development ([#34325](https://github.com/nuxt/nuxt/pull/34325))
251+
252+
## ⬆︎ Upgrading
253+
254+
Our recommendation for upgrading is to run:
255+
256+
```sh
257+
npx nuxt upgrade --dedupe
258+
```
259+
260+
This will deduplicate your lockfile and help ensure you pull in updates from other dependencies that Nuxt relies on, particularly in the unjs ecosystem.
261+
262+
::tip
263+
Check out our [upgrade guide](/docs/getting-started/upgrade) if upgrading from an older version.
264+
::
265+
266+
## 👉 Full Release Notes
267+
268+
::read-more
269+
---
270+
icon: i-simple-icons-github
271+
target: _blank
272+
to: https://github.com/nuxt/nuxt/releases/tag/v4.4.0
273+
---
274+
Read the full release notes of Nuxt `v4.4.0`.
275+
::
276+
277+
Thank you to all of the many contributors to this release! 💚
324 KB
Loading
189 KB
Loading

public/assets/blog/v4.4.png

495 KB
Loading

0 commit comments

Comments
 (0)