
Explore the most impactful features from Nuxt 3.0 to 3.17 you may have missed — with clear examples, explanations, and upgrade notes for Nuxt 4 users.

Get notified when we release new tutorials, lessons, and other expert Nuxt content.
Now that we're on Nuxt 4, let's take a step back and see what features you may have missed from the minor releases of Nuxt 3.
With so many updates, it's easy to miss the gems that could transform your development workflow.
I've compiled every major feature from Nuxt 3.0 through 3.17, organized by category. Each includes code examples, explains what it does, and shows you when to use it.
So let's go!
Note for Nuxt 4 users: Many experimental features from Nuxt 3.x are now stable in Nuxt 4! We've marked the key differences throughout this guide. Nuxt 4 also introduces a new app/ directory structure for better organization and performance. You can find the upgrade guide here.
When generating static sites, Nuxt can now deduplicate identical data fetches between pages. This means if multiple pages fetch the same data, Nuxt fetches it once and reuses the result.
In Nuxt 3.x, you need to enable this with an experimental flag:
export default defineNuxtConfig({
experimental: {
sharedPrerenderData: true
}
})
But if you're using Nuxt 4, this feature is now stable and enabled by — no configuration needed!
This dramatically speeds up nuxt generate builds. If you have a blog where every page fetches the same navigation data, Nuxt now fetches it once instead of on every page render.
This shines on static sites with repeated queries across multiple pages.
The usePreviewMode() composable makes it simple to implement draft content previews. When preview mode is active, all data fetching bypasses cached payloads and refetches fresh data.
const { enabled, state } = usePreviewMode()
// `enabled` is true if preview mode is on
// `state` can hold global preview state if needed
This is invaluable for CMS integrations. Editors can see live drafts without stale cached content getting in the way.
Use this when you have draft content or editorial review workflows.
Every useAsyncData and useFetch result now includes a clear() function to reset the data back to undefined.
<script setup lang="ts">
const { data, clear } = await useFetch('/api/products')
watch(() => useRoute().path, (newPath) => {
if (newPath === '/') clear()
})
</script>
This provides a simple way to invalidate data when you need fresh results. If you navigate away and back to a page, calling clear() forces a refetch.
In Nuxt 4, this feature gets even better. The data layer is now much smarter with automatic data sharing between components using the same key, automatic cleanup when components unmount, and reactive keys that trigger refetches without manual intervention.
Great for resetting lists, forms, or any data that should refresh between navigations.
The callOnce composable runs a block of code exactly once across SSR and hydration. This prevents the double execution that normally happens when code runs on server then again on client.
<script setup>
const siteConfig = useState('config')
await callOnce(async () => {
siteConfig.value = await $fetch('/api/website-config')
})
</script>
In v3.15, callOnce gained a navigation mode to run once per page navigation:
await callOnce(() => {
counter.value++
}, { mode: 'navigation' })
This prevents duplicate initialization code and is great for logging page views or setting up singletons.
Ideal for code that needs to run exactly once during SSR/hydration, or once per navigation.
You can now plug in custom caching logic for useAsyncData and useFetch via the getCachedData option.
const nuxtApp = useNuxtApp()
const { data } = await useAsyncData('myKey', fetcher, {
getCachedData: key => nuxtApp.payload.static[key] ?? nuxtApp.payload.data[key]
})
This gives you advanced control to avoid refetching data you know hasn't changed. You might integrate with a Vue store, localStorage, or in-memory cache.
This unlocks custom caching strategies beyond Nuxt's defaults.
Nuxt now supports "route groups" to organize pages without affecting URLs. Directories wrapped in parentheses are ignored in route paths.
pages/
├─ index.vue # renders at "/"
└─ (marketing)/
├─ about.vue # renders at "/about"
└─ contact.vue # renders at "/contact"
This keeps URLs clean while logically grouping files. You can group related pages for maintainability without impacting SEO or navigation.
This is a lifesaver in large apps where you want to organize pages by feature or team without polluting your URL structure.
You can mark specific pages to render only on client or server by adding a .client.vue or .server.vue suffix.
pages/
├─ dashboard.client.vue # skips SSR, client-only
└─ report.server.vue # pure HTML, no hydration
Client-only pages behave like wrapping content in <ClientOnly> but skip SSR entirely. Server-only pages render pure HTML and deliver it as static content on navigation.
Client-only pages work well for browser-heavy features like dashboards. Server-only pages shine for admin reports or content that doesn't need interactivity.
You can now enforce middleware via routeRules in your config. Map URL patterns to run or disable specific middleware.
export default defineNuxtConfig({
routeRules: {
'/admin/**': { appMiddleware: ['auth'] },
'/admin/login': { appMiddleware: { auth: false } }
}
})
This provides centralized guard configuration. Instead of scattering middleware logic across individual pages, you can protect entire route patterns in one place.
Works beautifully for authentication, analytics, or any logic that applies to route patterns.
<NuxtLink> gained flexible prefetch controls. You can now trigger prefetch on user interaction instead of just viewport visibility.
<!-- Prefetch on hover/focus only -->
<NuxtLink to="/about" prefetch-on="interaction">
About Us
</NuxtLink>
<!-- Prefetch on either visibility or hover -->
<NuxtLink to="/contact"
:prefetch-on="{ visibility: true, interaction: true }">
Contact
</NuxtLink>
You can set global defaults in your config. In Nuxt 3.x, this requires an experimental flag:
export default defineNuxtConfig({
experimental: {
defaults: {
nuxtLink: { prefetchOn: 'interaction' }
}
}
})
Nuxt 4 makes this configuration stable and enhances it with better SEO capabilities and improved link prefetching performance.
This gives you control over the trade-off between bandwidth and perceived performance. Prefetch-on-interaction avoids unnecessary network use for offscreen links while still speeding up navigation when users are likely to click.
Fine-tune prefetch behavior based on your app's navigation patterns.
Nuxt added support for the native View Transitions API to create smooth page transitions.
In Nuxt 3.x, you enable this as an experimental feature:
export default defineNuxtConfig({
experimental: { viewTransition: true }
})
Nuxt 4 makes this feature stable! You can now enable it without the experimental flag:
export default defineNuxtConfig({
app: { viewTransition: true }
})
You can control transitions per page:
<script setup>
definePageMeta({ viewTransition: false })
</script>
When enabled, elements can seamlessly animate between pages. Nuxt respects user prefers-reduced-motion settings by default.
Excellent for marketing sites or content sites where smooth transitions improve the user experience.
Enable typed routing to get auto-completion and type-checking for route paths and parameters.
In Nuxt 3.x, you enable this as an experimental feature:
export default defineNuxtConfig({
experimental: { typedPages: true }
})
Nuxt 4 makes this feature stable and works even better with improved TypeScript support. The framework now creates separate TypeScript projects for app code, server code, and configuration, giving you more accurate type inference and autocompletion.
Now useRoute('users-id') will have a typed params object with id: string if you have a /users/[id].vue route. This catches typos in route names at compile time.
Especially valuable in medium-to-large apps where type safety for navigation prevents bugs.
Nuxt can defer hydration of specific components for better performance. Components hydrate when they become visible, when the browser is idle, or on user interaction.
<!-- Only hydrate when visible in viewport -->
<ExpensiveChart hydrate-on-visible />
<!-- Hydrate after 2 seconds -->
<VideoPlayer :hydrate-after="2000" />
<!-- Hydrate on first user interaction -->
<DropdownMenu hydrate-on-interaction="mouseover" />
You can listen for the @hydrated event to know when components finish hydrating.
This improves initial load time by reducing the workload during the critical rendering path. Non-critical components don't attach event listeners until needed.
Perfect for heavy components below the fold or interactive widgets that don't need immediate interactivity.
You can now designate specific elements inside server components to hydrate on the client using the nuxt-client directive.
<template>
<article>
<h1>{{ post.title }}</h1>
<p>{{ post.content }}</p>
<!-- Only this form hydrates on client -->
<CommentForm nuxt-client />
</article>
</template>
This creates mostly static, SEO-friendly pages with selective interactivity. The post content doesn't ship client JS, but the form does.
Ideal for optimal performance when you need mostly static content with sprinkles of interactivity.
Nuxt DevTools are now bundled by default. Simply run your app in development to get an in-browser DevTools UI.
export default defineNuxtConfig({
devtools: { enabled: true }
})
Open DevTools with Alt+D or click the Nuxt logo. You get a custom panel to inspect Nuxt state, routes, modules, and more.
Essential in development — it's like Vue DevTools but specifically for Nuxt.
Nuxt now captures server-side console output and replays it in your browser console during development.
If you console.log() in a page or API route on the server, you'll see it in your browser devtools with a [ssr] tag. You can opt-out with features: { devLogs: false } in config.
This makes debugging much simpler. You can see data-fetch outputs or error messages from the SSR phase right in your browser.
A game-changer during development when debugging server-side logic.
Nuxt now integrates deeply with Chrome DevTools. When profiling your app, you can see exactly when Nuxt lifecycle hooks occur and how long they take.
This provides granular performance insight. You can identify slow hooks or bottlenecks in the Nuxt lifecycle directly in Chrome's Performance panel.
Invaluable when optimizing apps to pinpoint which part of Nuxt's process needs attention.
Hot Module Reload now supports more cases. Changes to auto-generated files like routes, plugins, or page metadata defined via definePageMeta will hot-reload without a full page refresh.
This speeds up development by reducing cases where you need manual reloads. Tweaking navigation structure or metadata is now as instant as styling changes.
Enabled by default and dramatically improves development flow.
Generate stable IDs that match between server and client for accessible form labels.
It was added in Nuxt 3.10, then was moved into the Vue framework itself for Vue 3.5.
<script setup>
const emailId = useId()
const passwordId = useId()
</script>
<template>
<label :for="emailId">Email</label>
<input :id="emailId" type="email">
<label :for="passwordId">Password</label>
<input :id="passwordId" type="password">
</template>
This prevents hydration mismatches with accessible label/for pairs.
Essential for any form fields where you need proper label associations.
Nuxt now includes an off-screen route announcer that updates screen readers on navigation.
<template>
<NuxtLoadingIndicator />
<NuxtRouteAnnouncer />
<NuxtPage />
</template>
This announces page navigations to screen readers ("Navigated to Page Title"), greatly improving SPA accessibility.
Should always be included in your app.vue for better accessibility.
Nuxt introduced an experimental Rspack integration as a third builder option besides Vite and Webpack.
export default defineNuxtConfig({
builder: 'rspack' // opt-in
})
Rspack is a Rust-based bundler that's extremely fast, especially for large projects. Early tests show significantly quicker cold builds and HMR.
Worth trying if your project's bottleneck is bundler speed, particularly in large monorepos.
Nuxt ships a build manifest that enables client-side route rules and smarter payload loading.
export default defineNuxtConfig({
routeRules: {
'/about': { redirect: '/about-us' }
}
})
This prevents 404 errors during generation and makes client redirects work during navigation.
Nuxt changed the default filename pattern for JavaScript chunks to use only hashes (e.g., _nuxt/12345.js instead of _nuxt/SomeChunk.12345.js).
This avoids ad blockers that might block files based on names like "ads.js" or "tracking.js" in the filename.
This is the default behavior and prevents broken functionality for users running ad blockers.
This is more of a Nitro feature, but you can return a Response or stream directly from server handlers.
export default () => new Response(
new ReadableStream({
start(controller) {
controller.enqueue('event: ping\\\\ndata: ok\\\\n\\\\n')
}
}),
{ headers: { 'content-type': 'text/event-stream' } }
)
This enables efficient streaming responses for Server-Sent Events (SSE), large downloads, or proxies.
Using <NuxtImg> or <NuxtPicture> triggers auto-installation of the Nuxt Image module.
<NuxtImg
src="/banner.jpg"
sizes="sm:100vw md:50vw lg:800px"
/>
This gives you best-practice image performance by default with one less manual setup step.
A server-safe #teleports mount point now exists in the app shell.
<Teleport to="#teleports">
<Toast />
</Teleport>
This makes modals and menus render correctly with SSR, avoiding hydration issues.
Perfect for teleported content like modals, tooltips, or notifications.
Nuxt auto-registers modules from ~/modules and restarts on changes.
export default defineNuxtModule({
setup(options, nuxt) {
// augment routes, auto-imports, etc.
}
})
This makes building internal modules simpler without boilerplate.
Nuxt 3's journey from 3.0 to 3.17 was packed with innovations that transformed how we build Vue applications. From smart data fetching and route groups to delayed hydration and accessibility improvements, each feature solved real problems developers face every day.
The beauty of these features isn't just what they do individually, it's how they work together.
You can combine server components with selective hydration for optimal performance, use route groups with typed pages for better organization, or mix preview mode with shared prerender data for seamless editorial workflows.
If you're still on Nuxt 3.x, don't worry! Migration to Nuxt 4 is smooth and many have already done it.
The best part is that we're just getting started.
The Nuxt team is continuing to push the boundaries of what's possible with Vue and server-side rendering, always with exceptional DX at the forefront.
So pick a few features from this list that solve your current pain points, give them a try, and see how they transform your development workflow!
