Back to blog
Tutorials

Nuxt 4: Everything You Need to Know About the Next Generation of the Vue Meta-Framework

Kim BoenderKim Boender
April 7, 2026 8 min read
Nuxt 4: Everything You Need to Know About the Next Generation of the Vue Meta-Framework

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 useFetch had 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.json

Nuxt 4 structure:

my-app/
├── app/
│   ├── components/
│   ├── composables/
│   ├── layouts/
│   ├── middleware/
│   ├── pages/
│   ├── plugins/
│   └── app.vue
├── server/
├── shared/
├── nuxt.config.ts
└── package.json

The 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/types files are more accurate and update faster during development
  • Component prop inference, TypeScript now correctly infers props from defineProps in auto-imported components
  • useRouteParams and useRouteQuery typed 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

FeatureNuxt 3Nuxt 4
Project StructureFlat rootapp/ subdirectory
Shared Code (client + server)Manual workaroundsshared/ directory
Data Fetching DeduplicationOpt-inDefault behavior
TypeScript route paramsManual castinguseRouteParams / useRouteQuery
Compatibility Mode,compatibilityVersion: 4
Nitro VersionNitro 2Nitro 3
Auto-import accuracyGoodImproved
Dev Server Cold Start~3.4s~2.1s
Module EcosystemMatureFully 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 dev

For 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

Frequently Asked Questions

Is Nuxt 4 backward compatible with Nuxt 3? +
Yes. Nuxt 4 ships with a `compatibilityVersion: 4` flag in `nuxt.config.ts` that lets you opt into new behaviors incrementally. Most Nuxt 3 projects can migrate without breaking changes, and all official and community modules remain compatible.
Do I have to restructure my project to use the new app/ directory? +
No, it's opt-in. Nuxt 4 still supports the flat Nuxt 3 structure. However, moving to the app/ directory is strongly recommended for new projects and is the future-facing standard that the Nuxt team is building toward.
What is the shared/ directory in Nuxt 4 and when should I use it? +
The shared/ directory is a new top-level folder for code that needs to be accessible on both the client (Vue) and server (Nitro) sides. It's the right place for shared TypeScript interfaces, validation schemas, and pure utility functions — eliminating the need for manual workarounds to share types between API routes and components.
How does the new data fetching behavior differ from Nuxt 3? +
In Nuxt 3, useFetch and useAsyncData could silently re-run on the client even when data was already fetched during SSR, causing double-fetching and hydration mismatches. Nuxt 4 makes deduplication the default — if data was fetched server-side and is present in the Nuxt payload, it won't be re-fetched on the client unless you explicitly opt out.
What version of Nitro does Nuxt 4 use? +
Nuxt 4 ships with Nitro 3, which brings improved cold-start performance, better Edge runtime compatibility, and more granular control over server route caching. You don't need to configure anything — upgrading to Nuxt 4 automatically gives you Nitro 3.

Try it yourself

JSON Formatter

Format, validate, and beautify JSON instantly

Open JSON Formatter