π Highlights
This release brings a major reworking of the async data layer, a new built-in component, better warnings, and performance improvements!
π Data Fetching Improvements
A major reorganization of Nuxt's data fetching layer brings significant improvements to useAsyncData
and useFetch
.
Although we have aimed to maintain backward compatibility and put breaking changes behind the experimental.granularCachedData
flag (disabled by default), we recommend testing your application thoroughly after upgrading. You can also disable experimental.purgeCachedData
to revert to the previous behavior if you are relying on cached data being available indefinitely after components using useAsyncData
are unmounted.
π Read the the original PR for full details (#31373), but here are a few highlights.
Consistent Data Across Components
All calls to useAsyncData
or useFetch
with the same key now share the underlying refs, ensuring consistency across your application:
<!-- ComponentA.vue -->
<script setup>
const { data: users, pending } = useAsyncData('users', fetchUsers)
</script>
<!-- ComponentB.vue -->
<script setup>
// This will reference the same data state as ComponentA
const { data: users, status } = useAsyncData('users', fetchUsers)
// When either component refreshes the data, both will update consistently
</script>
This solves various issues where components could have inconsistent data states.
Reactive Keys
You can now use computed refs, plain refs, or getter functions as keys:
const userId = ref('123')
const { data: user } = useAsyncData(
computed(() => `user-${userId.value}`),
() => fetchUser(userId.value)
)
// Changing the userId will automatically trigger a new data fetch
// and clean up the old data if no other components are using it
userId.value = '456'
Optimized Data Refetching
Multiple components watching the same data source will now trigger only a single data fetch when dependencies change:
// In multiple components:
const { data } = useAsyncData(
'users',
() => $fetch(`/api/users?page=${route.query.page}`),
{ watch: [() => route.query.page] }
)
// When route.query.page changes, only one fetch operation will occur
// All components using this key will update simultaneously
π Built-In Nuxt Components
<NuxtTime>
- A new component for safe time display
We've added a new <NuxtTime>
component for SSR-safe time display, which resolves hydration mismatches when working with dates (#31876):
<NuxtTime :time="new Date()" format="YYYY-MM-DD" />
The component accepts multiple time formats and gracefully handles both client and server rendering.
Enhanced <NuxtErrorBoundary>
The <NuxtErrorBoundary>
component has been converted to a Single File Component and now exposes error
and clearError
from the component - as well as in the error slot types, giving you greater ability to handle errors in your templates and via useTemplateRef
(#31847):
<NuxtErrorBoundary @error="handleError">
<template #error="{ error, clearError }">
<div>
<p>{{ error.message }}</p>
<button @click="clearError">Try again</button>
</div>
</template>
<!-- Content that might error -->
<MyComponent />
</NuxtErrorBoundary>
π Router Improvements
<NuxtLink>
now accepts a trailingSlash
prop, giving you more control over URL formatting (#31820):
<NuxtLink to="/about" trailing-slash>About</NuxtLink>
<!-- Will render <a href="/about/"> -->
π Loading Indicator Customization
You can now customize the loading indicator with new props directly on the component (#31532):
hideDelay
: Controls how long to wait before hiding the loading barresetDelay
: Controls how long to wait before resetting loading indicator state
<template>
<NuxtLoadingIndicator :hide-delay="500" :reset-delay="300" />
</template>
π Documentation as a Package
The Nuxt documentation is now available as an npm package! You can install @nuxt/docs
to access the raw markdown and YAML content used to build the documentation website (#31353).
π» Developer Experience Improvements
We've added several warnings to help catch common mistakes:
- Warning when server components don't have a root element #31365
- Warning when using the reserved
runtimeConfig.app
namespace #31774 - Warning when core auto-import presets are overridden #29971
- Error when
definePageMeta
is used more than once in a file #31634
π Enhanced Module Development
Module authors will be happy to know:
- A new
experimental.enforceModuleCompatibility
allows Nuxt to throw an error when a module is loaded that isn't compatible with it (#31657). It will be enabled by default in Nuxt v4. - You can now automatically register every component exported via named exports from a file with
addComponentExports
#27155
π₯ Performance Improvements
Several performance improvements have been made:
- Switched to
tinyglobby
for faster file globbing #31668 - Excluded
.data
directory from type-checking for faster builds #31738 - Improved tree-shaking by hoisting the
purgeCachedData
check #31785
β Upgrading
Our recommendation for upgrading is to run:
npx nuxi@latest upgrade --dedupe
This refreshes your lockfile and pulls in all the latest dependencies that Nuxt relies on, especially from the unjs ecosystem.
π Changelog
π Enhancements
- nuxt: Accept
hideDelay
andresetDelay
props for loading indicator (#31532) - nuxt: Warn server components need root element (#31365)
- docs: Publish raw markdown/yaml docs as
@nuxt/docs
(#31353) - kit,nuxt: Pass dotenv values from
loadNuxtConfig
to nitro (#31680) - nuxt,vite: Support disabling scripts in dev mode (#31724)
- nuxt: Warn if user uses reserved
runtimeConfig.app
namespace (#31774) - kit,schema: Allow throwing error if modules aren't compatible (#31657)
- nuxt: Extract
middleware
when scanning page metadata (#30708) - nuxt: Warn if core auto-import presets are overridden (#29971)
- nuxt: Scan named exports with
addComponentExports
(#27155) - nuxt: Convert
<NuxtErrorBoundary>
to SFC + exposeerror
/clearError
(#31847) - nuxt: Add
<NuxtTime>
component for ssr-safe time display (#31876) - nuxt: Add
trailingSlash
prop to<NuxtLink>
(#31820)
π₯ Performance
- kit,rspack,webpack: Switch to
tinyglobby
(#31668) - kit: Exclude
.data
directory from type-checking (#31738) - nuxt: Hoist
purgeCachedData
check to improve tree-shaking (#31785) - nuxt: Remove
oxc-parser
manual wasm fallback logic (#31484) - nuxt: Remove unecessary type check for useFetch (#31910)
π©Ή Fixes
- kit,vite: Ensure all
modulesDir
paths are added tofs.allow
(#31540) - nuxt: Pass slots through to lazy hydrated components (#31649)
- vite: Do not return 404 for dev server handlers which shadow
/_nuxt/
(#31646) - nuxt: Sync error types for
useLazyAsyncData
(#31676) - nuxt: Strip base url from
error.url
(#31679) - nuxt: Render inline styles before
app:rendered
is called (#31686) - nuxt: Update nitro imports (0bec0bd26)
- nuxt: Check for
fallback
attribute when stripping<DevOnly>
(c1d735c27) - vite: Invalidate files not present in module graph (ecae2cd54)
- nuxt: Do not add manifest preload when
noScripts
(c9572e953) - nuxt: Do not prompt to update
compatibilityDate
(#31725) - nuxt: Show brotli size by default when analyzing bundle (#31784)
- nuxt: Always pass
statusMessage
when rendering html error (#31761) - nuxt: Error when
definePageMeta
is used more than once (#31634) - nuxt: Parse
error.data
before renderingerror.vue
(#31571) - nuxt: Use single synced asyncdata instance per key (#31373)
- nuxt: Use
useAsyncData
in console log (#31801) - nuxt: Wait for suspense to resolve before handling
NuxtErrorBoundary
error (#31791) - vite: Disable
preserveModules
(#31839) - nuxt: Align
pending
withstatus
value for v4 (#25864) - nuxt: Consider full path when de-duplicating routes (#31849)
- nuxt: Augment
nuxt/app
in generated middleware and layouts declarations (#31808) - nuxt: Correct order of args passed to
withoutBase
(f956407bb) - vite: Dedupe
vue
in vite-node dev server (f3882e004) - ui-templates: Pass pointer events through spotlight div in error dev template (#31887)
- kit: Include user-defined types before internal ones in
tsconfig.json
(#31882) - nuxt: Do not purge nuxt data if active
useNuxtData
(#31893) - nuxt: Do not include components of key in
useFetch
watch sources (#31903) - nuxt: Use first existing
modulesDir
to store build cache files (#31907)
π Refactors
- nuxt: Use
shallowRef
for primitives as well (#31662) - nuxt: Move island renderer into its own event handler (#31386)
- nuxt: Remove unneeded import (#31750)
- nuxt: Use
_replaceAppConfig
when applying hmr (#31786) - nuxt: Use new unplugin filter options (#31868)
- schema: Do not generate types for
ConfigSchema
(#31894)
π Documentation
- Add note on extending auto-imports (#31640)
- Improve description of
app.vue
(#31645) - Use video-accordion video and add more videos (#31655)
- Add description of
templateParams
to seo docs (#31583) - Refine auto-imports documentation (#31700)
- Fix nuxt logo on website badge (#31704)
- Update tailwindcss link (b5741cb5a)
- Adjust description of
useHydration
(#31712) - Remove trailing slash (#31751)
- Remove comment about
callOnce
returning value (#31747) - Use
vs.
consistently (#31760) - Update nitro
addServerHandler
example (#31769) - Add supporting shared folder video (#31651)
- Update example for component auto-import in nuxt modules (#31757)
- Refine nuxt kit components documentation (#31714)
- Use trailing slash for vitest link (82de8bcf8)
- Fix casing (#31805)
- Add discord and nuxters links (#31888)
- Fix typos (#31898)
π¦ Build
- nuxt: Use
vue-sfc-transformer
to process sfcs (#31691)
π‘ Chore
- Update renovate config (565c0b98e)
- Upgrade webpack separately (e1f5db34f)
- Update internal ui-templates licence to MIT (f8059fb8b)
- Sort package.json (a6cbd71f8)
β Tests
- nuxt: Add customizable test api for pages tests (#31619)
- kit: Fix tests when running on Windows (#31694)
- Update page metadata snapshot (89a596075)
- Add basic runtime tests for
<NuxtErrorBoundary>
(4df92c45f) - Add version to mock nuxt (915fae2fd)
- Update assertion for
pendingWhenIdle
(08f2224c8) - Remove incorrect assertions (fdc4b5343)
- Update tests for purgeCachedData (c6411eb17)
π€ CI
- Add notify-nuxt-website workflow (#31726)
β€οΈ Contributors
- @beer (@iiio2)
- Julien Huang (@huang-julien)
- Daniel Roe (@danielroe)
- Saeid Zareie (@Saeid-Za)
- Bobbie Goede (@BobbieGoede)
- Damian GΕowala (@DamianGlowala)
- Maxime Pauvert (@maximepvrt)
- Leonid (@john-psina)
- Wind (@productdevbook)
- Arkadiusz Sygulski (@Aareksio)
- Rohan Dhimal (@drowhannn)
- xjccc (@xjccc)
- Robin (@OrbisK)
- Alex Liu (@Mini-ghost)
- Daniel Kelly (@danielkellyio)
- Jeffrey van den Hondel (@jeffreyvdhondel)
- Alexander Lichter (@TheAlexLichter)
- SΓ©bastien Chopin (@atinux)
- Yizack Rangel (@Yizack)
- JoaquΓn SΓ‘nchez (@userquin)
- IlyaL (@ilyaliao)
- Ben McCann (@benmccann)
- Anthony Fu (@antfu)
- Alexandru Ungureanu (@unguul)