Kim BoenderNuxt 4: Everything You Need to Know About the Next Generation of the Vue Meta-Framework
Kim Boender
Nuxt has long been the gold standard for building full-stack Vue applications. Server-side rendering, file-based routing, auto-imports, and a thriving module ecosystem, it's the framework that makes Vue feel complete. And now, Nuxt 4 pushes things even further with a leaner project structure, more predictable data fetching, and significant quality-of-life improvements for TypeScript developers.
Whether you're starting a new project or upgrading an existing Nuxt 3 app, this guide covers everything you need to hit the ground running.
Why Nuxt 4?
Nuxt 3 was a massive leap, the move to Vite, Nitro, the Composition API, and <script setup> brought the framework into the modern era. But as the community used it at scale, a few rough edges emerged:
- The flat root structure made large projects messy
- Data fetching with
useFetchhad subtle caveats around client-side re-execution - TypeScript path resolution inside complex monorepos was painful
- Some Nuxt internals were leaking into application code in unexpected ways
Nuxt 4 doesn't reinvent the wheel, it refines and consolidates what worked, while fixing what didn't.
The New app/ Directory Structure
This is the most visible change in Nuxt 4. Your application code now lives inside an app/ subdirectory instead of the project root. This might sound trivial, but it has a profound impact on how clean and navigable your project feels.
Nuxt 3 structure:
my-app/
├── components/
├── composables/
├── layouts/
├── middleware/
├── pages/
├── plugins/
├── server/
├── app.vue
├── nuxt.config.ts
└── package.jsonNuxt 4 structure:
my-app/
├── app/
│ ├── components/
│ ├── composables/
│ ├── layouts/
│ ├── middleware/
│ ├── pages/
│ ├── plugins/
│ └── app.vue
├── server/
├── shared/
├── nuxt.config.ts
└── package.jsonThe server/ directory stays at the root (it's a Nitro concern, not a Vue concern), and a new shared/ directory has been introduced for utilities and types that need to span both the client and server sides, no more awkward workarounds for sharing a TypeScript interface between your API routes and your Vue components.
Opting In During the Transition
If you're migrating from Nuxt 3, you don't have to restructure everything on day one. Nuxt 4 provides a compatibility flag in nuxt.config.ts:
// nuxt.config.ts
export default defineNuxtConfig({
future: {
compatibilityVersion: 4,
},
})With this enabled, Nuxt will look for your app code in the app/ directory while still falling back gracefully. It's the same strategy the Nuxt team used to ship the Vite transition, and it works beautifully.
Smarter Data Fetching
Data fetching in Nuxt has always been powerful, but useFetch and useAsyncData had a subtle footgun: by default, they would re-run on the client even if the data was already fetched on the server. This led to double-fetching bugs and confusing hydration states.
Nuxt 4 introduces deduplicated, context-aware data fetching as the default behavior.
<script setup lang="ts">
// This now correctly deduplicates between server and client
const { data: posts } = await useFetch('/api/posts')
</script>
<template>
<ul>
<li v-for="post in posts" :key="post.id">{{ post.title }}</li>
</ul>
</template>The getCachedData Default
Under the hood, useAsyncData now ships with a sensible getCachedData default. If data was fetched during SSR and is available in the Nuxt payload, it won't be re-fetched on the client unless you explicitly clear it or the key changes. This alone eliminates an entire category of bugs seen in production Nuxt 3 apps.
You can still opt into client-side re-fetching when needed:
const { data, refresh } = await useAsyncData('user-profile', () =>
$fetch('/api/me'), {
// Force a refresh on client navigation
getCachedData: () => null,
}
)The shared/ Directory: Cross-Boundary Code
One of the most requested features in Nuxt 3 was a clean way to share types and utilities between your Vue app and your Nitro server routes. The shared/ directory in Nuxt 4 is the official answer.
shared/
├── types/
│ └── post.ts
└── utils/
└── formatDate.ts// shared/types/post.ts
export interface Post {
id: number
title: string
publishedAt: string
author: string
}// server/api/posts.get.ts
import type { Post } from '~~/shared/types/post'
export default defineEventHandler((): Post[] => {
return [
{ id: 1, title: 'Hello Nuxt 4', publishedAt: '2026-01-01', author: 'Daniel' },
]
})<!-- app/pages/index.vue -->
<script setup lang="ts">
import type { Post } from '~~/shared/types/post'
const { data: posts } = await useFetch<Post[]>('/api/posts')
</script>No more #imports hacks or duplicated type definitions. The ~~/ alias resolves to the project root, giving you clean, explicit imports from the shared layer.
Improved TypeScript Experience
TypeScript support in Nuxt 4 has been significantly tightened:
- Stricter auto-import types, generated
.nuxt/typesfiles are more accurate and update faster during development - Component prop inference, TypeScript now correctly infers props from
definePropsin auto-imported components useRouteParamsanduseRouteQuerytyped helpers, no more manual casting when reading route parameters
// Typed route params, no more `route.params.id as string`
const { id } = useRouteParams<{ id: string }>()
// Typed query strings
const { page, sort } = useRouteQuery<{ page: string; sort: 'asc' | 'desc' }>()These helpers are thin wrappers, but the TypeScript ergonomics they unlock are genuinely delightful.
Performance: Where Does Nuxt 4 Stand?
Nuxt 4's dev server cold start is noticeably faster thanks to improved module resolution and a tighter integration with Vite's dependency pre-bundling. HMR, especially for composables and layouts, is substantially snappier. Production build times are roughly comparable but benefit from Nitro's continued improvements under the hood.
Nuxt 4 vs Nuxt 3: Feature Comparison
| Feature | Nuxt 3 | Nuxt 4 |
|---|---|---|
| Project Structure | Flat root | app/ subdirectory |
| Shared Code (client + server) | Manual workarounds | shared/ directory |
| Data Fetching Deduplication | Opt-in | Default behavior |
| TypeScript route params | Manual casting | useRouteParams / useRouteQuery |
| Compatibility Mode | , | compatibilityVersion: 4 |
| Nitro Version | Nitro 2 | Nitro 3 |
| Auto-import accuracy | Good | Improved |
| Dev Server Cold Start | ~3.4s | ~2.1s |
| Module Ecosystem | Mature | Fully compatible |
Migrating from Nuxt 3: Step-by-Step
Migrating is intentionally low-friction. Here's the recommended path:
Step 1, Enable Compatibility Mode
// nuxt.config.ts
export default defineNuxtConfig({
future: {
compatibilityVersion: 4,
},
})Run your app. Fix any deprecation warnings that surface.
Step 2, Create the app/ Directory
mkdir app
mv components composables layouts middleware pages plugins app.vue app/Step 3, Move Shared Code
Identify any utilities or types used by both your Vue components and your server routes. Move them into shared/ and update imports to use ~~/shared/.
Step 4, Audit Data Fetching
With the new deduplication defaults, review any useAsyncData or useFetch calls that explicitly relied on client re-fetching. Add getCachedData: () => null where you intentionally want a client-side refresh.
Step 5, Run & Test
npx nuxi upgrade
npx nuxi devFor most Nuxt 3 projects, the migration takes less than an afternoon. The Nuxt team has gone out of their way to make this a painless upgrade, a far cry from the Nuxt 2 → 3 experience.
The Module Ecosystem: Fully Compatible
One concern with any major version bump is whether the module ecosystem keeps up. Nuxt 4 is fully backward compatible with all Nuxt 3 modules. The @nuxt/ official modules (Image, Content, UI, DevTools) have all been updated, and community modules continue to work without changes.
The vast majority of the ecosystem just works. The small slice requiring updates are mostly niche modules that hooked into internal Nuxt APIs, and their maintainers are actively shipping fixes.
Should You Start New Projects on Nuxt 4 Today?
Yes. Nuxt 4 is stable, and the team's track record on stability is excellent. The new app/ structure is better for project organization from day one, and you'll want to be building habits around shared/ and the improved data fetching defaults rather than learning workarounds that no longer apply.
For existing Nuxt 3 projects, the compatibility mode makes the migration risk low. The improvements, especially around data fetching correctness and TypeScript ergonomics, are worth the move.
Wrapping Up
Nuxt 4 isn't a rewrite, it's a graduation. The framework takes everything that made Nuxt 3 excellent and files off the rough edges that practitioners have been dealing with for the past two years. The new app/ directory gives large projects room to breathe, the shared/ layer solves a real pain point for full-stack teams, and the data fetching improvements eliminate an entire class of subtle production bugs.
If Vue is your frontend of choice, Nuxt 4 is the best version of the framework yet. Give it a spin.
npx nuxi@latest init my-nuxt4-app
cd my-nuxt4-app
npx nuxi dev