Nuxt 4 Guide: What Changed and How to Migrate from Nuxt 3

Nuxt 4 is the latest stable version of the framework, and if you are on Nuxt 3 the upgrade is smaller than a major version number usually suggests. Most of what changed is about structure and defaults, not a rewrite: a new app/ directory for your source code, a more consistent data-fetching layer, and tighter TypeScript project boundaries.
This guide covers what actually changed from Nuxt 3, then walks you through migrating an existing app step by step. If you just want to upgrade, jump to the migration checklist. If you want to understand what you are signing up for first, start with the what-changed table below.
What changed from Nuxt 3 to Nuxt 4
This table describes changes between the version starting with the most impactful, exciting, or potentially breaking and moves to more minor concerns.
| Area | Nuxt 3 (old default) | Nuxt 4 (new default) | What to do |
|---|---|---|---|
| Directory structure | App code (pages/, components/, composables/, layouts/, app.vue) lives at the project root |
Same app code lives under a new app/ directory; srcDir now defaults to app/ |
Move app source into app/, or let the migration codemod do it. Confirm which folders move vs. stay (see next row). |
| Root-level folders | Everything at root | server/, public/, modules/, and content directories stay at the root, not inside app/ |
Avoid moving server/, public/, modules/, and content directories |
| Shared code | Ad hoc sharing between client and server | New shared/ directory for code used by both app/ and server/. Must only include dependencies compatible in both environments! |
Move genuinely shared utilities and types into shared/. Or just use this convention moving forward. |
| Data fetching | useAsyncData / useFetch behavior looser; data deeply reactive |
More consistent, deduplicated data layer; components using the same key share one data object; data reactivity default changed (shallow by default) | Audit code that mutates fetched data or relies on deep reactivity, and components that share a fetch key. See the “What changed with data fetching in Nuxt 4?” FAQ section below. These changes have the most potential for breaking. |
| Granular inline styles | Nuxt inlines all CSS, including global styles | Nuxt only inlines styles defined in components | Leave as is. Is better in most use cases or revert to old behavior in Nuxt config. |
| TypeScript | Single project context | Separate TS project contexts for app, server, and shared code | Can leave as is but to reap full benefits follow the migration steps in the official guide |
| Normalized component names | KeepAlive and findComponent required component file name alone |
KeepAlive and findComponent now respect the folder + filename convention | Update references with KeepAlive and findComponent to the fully qualified component name |
| Layers module loading order | Layers modules had priority (root loaded first, layers next) | Root modules have priority | Comb your modules and adjust ordering with filename numbering if affected |
| Compatibility opt-in | n/a | Adopt Nuxt 4 defaults early from Nuxt 3 via future: { compatibilityVersion: 4 } |
Flip this in Nuxt 3 first to surface breakages before you commit to the full upgrade. |
The changes mentioned in the table above are some of the most impactful and not exhaustive but will cover the use cases of most users. To find an exhaustive list, visit the official migration guide.
Step-by-step migration checklist (Nuxt 3 → Nuxt 4)
- Read the official upgrade guide and pin the target version. Note the exact Nuxt 4 release you are upgrading to and its minimum Node.js version (verify the required Node LTS).
- Bump Node if needed. Match the Node version Nuxt 4 requires before touching anything else.
- Run the official upgrade command. Note that you may need to use nuxi@latest to ensure a version with support for the channel flag.
npx nuxi@latest upgrade --dedupe --channel=v3- Run the official migration codemod. Nuxt ships an automated migration recipe that moves files into
app/and applies known fixes to breaking changes.
npx codemod nuxt/4/migration-recipe- Confirm the new layout by hand. App code under
app/;server/,public/,modules/, and content directories still at the root. Fix anything the codemod missed. - Update paths, aliases, and config. Check custom
srcDiror directory overrides and any hardcoded paths that assumed the old root layout. Verify how~and@aliases resolve under the new structure. - Audit data fetching. Find components that share a
useAsyncData/useFetchkey, and any code that mutated fetched data in place or leaned on deep reactivity. Adjust for the shared, deduplicated data layer. - Move shared code into
shared/. Relocate utilities and types used by both client and server. - Benefit from the new TS contexts. Create the TS files, for the app, server, node, and shared contexts. You can copy/paste these from a newly created Nuxt 4 project.
- Update your modules. Bump each Nuxt module to a Nuxt 4 compatible version and verify one by one; a lagging module is the most common upgrade blocker.
- Build, test, and smoke-test SSR. Run the full test suite, do a production build, and manually check SSR and hydration on your highest-traffic pages.
Should you upgrade now?
- Already on Nuxt 3?
Upgrading is worth doing and, for most apps, quick. The structural changes are mechanical and the codemod handles the bulk of them. Turn on compatibility mode first (step 3) so you can adopt the new defaults gradually instead of in one risky jump.
- Starting a new project?
Begin on Nuxt 4 directly and use the app/ structure from day one. The TS experience is much better and dev server startup much faster on Windows!
- On a large app with many modules?
The upgrade itself is small, but your dependencies gate it. Before scheduling the work, confirm your critical modules have Nuxt 4 compatible releases.
- Still on Nuxt 2 or Bridge?
Nuxt 2 reached end of life on 2024-06-30 and it’s imperative you make the switch but it is a much bigger jump. Migrate to Nuxt 3 first, then follow this checklist.
Nuxt 4 Frequently Asked Questions (FAQ)
- Is Nuxt 4 a big rewrite or a hard upgrade?
No. It is an evolution of Nuxt 3. Most of the change is the new app/ directory and updated data-fetching defaults, and an automated codemod handles the file moves. Many apps upgrade in an afternoon.
- Do I have to use the new
app/directory?
The app/ layout is the new default and the recommended structure. You can keep the old layout (Nuxt will auto-detect it), but prefer adopting the new structure for anything long-lived (it’s an easy switch anyways).
- Can I try the Nuxt 4 changes before upgrading?
Yes. In Nuxt 3, set future: { compatibilityVersion: 4 } in nuxt.config. That opts you into the Nuxt 4 defaults so you can find and fix breakages while still on Nuxt 3.
- What usually breaks when upgrading to Nuxt 4?
Three things most often: incompatible modules, paths and config that assumed the old root layout, and code that relied on the old data-fetching reactivity or on components holding independent copies of the same fetch. Running the codemod in an app with a robust test suite catches most of it.
-
What changed with data fetching in Nuxt 4?
- Singleton data fetching layer (Moderate)
- Calls with the same key now share the same
data,error, andstatusrefs. - Calls with an explicit key must not have conflicting
deep,transform,pick,getCachedData, ordefaultoptions (this now triggers a warning). getCachedDatais now called on every fetch, including watcher-triggered fetches andrefreshNuxtData(previously skipped in those cases).getCachedDatanow receives a context object withctx.cause('initial' | 'refresh:hook' | 'refresh:manual' | 'watch').- Reactive keys (computed refs, plain refs, or getter functions) are now supported and trigger automatic refetching.
- Data is purged from memory when the last component using it unmounts.
- Opt out via
experimental.granularCachedData: falseandexperimental.purgeCachedData: false.
Shared prerender data (Medium)
- Payload data from
useAsyncData/useFetchis now shared across prerendered pages. - You must ensure each unique key always resolves to the same data (e.g. include dynamic route params in the key).
- Opt out via
experimental.sharedPrerenderData: false.
Default
dataanderrorvalues (Minimal)dataanderrornow default toundefinedinstead ofnull; update any null checks.- Opt out via
experimental.defaults.useAsyncData: { value: 'null', errorValue: 'null' }.
dedupeboolean removal (Minimal)refresh({ dedupe: true })must becomerefresh({ dedupe: 'cancel' }).refresh({ dedupe: false })must becomerefresh({ dedupe: 'defer' }).
Respect defaults when clearing (Minimal)
clear/clearNuxtDatanow resetsdatato your customdefaultvalue rather than unsetting it.- Opt out via
experimental.resetAsyncDataToUndefined: true.
pendingalignment (Medium)pendingis now a computed property that'strueonly whenstatusis'pending'.- With
immediate: false,pendingstaysfalseuntil the first request (previously alwaystrue). - Opt out via
experimental.pendingWhenIdle: true.
Key-change behavior (Medium)
useFetchnow matchesuseAsyncData: withimmediate: false, a key change only refetches if data was already fetched once.- With
immediate: false, you must callrefresh/executemanually the first time. - Opt out via
experimental.alwaysRunFetchOnKeyChange: true.
Shallow data reactivity (Minimal)
datais now ashallowRef, so mutating a property within the object no longer triggers reactivity (replacing the whole object still does).- Opt back into deep reactivity per-call with
{ deep: true }, or globally viaexperimental.defaults.useAsyncData: { deep: true }.
Conclusion
Nuxt 4 includes some great quality of life improvements with an easy upgrade story that most teams complete in a day or so. To get a first hand glance at the improvements and how they benefit you, I recommend checking out our course What’s New in Nuxt 4 where we we walk you through the most impactful changes step by step via video. New to Vue and Nuxt in general? Checkout the most comprehensive course around on the subject: the the Vue 3 MasterClass or it’s Nuxt specific counter-part: Mastering Nuxt.
No. It is an evolution of Nuxt 3. Most of the change is the new app/ directory and updated data-fetching defaults, and an automated codemod handles the file moves. Many apps upgrade in an afternoon.
directory?
The app/ layout is the new default and the recommended structure. You can override the source directory to keep the old layout, but confirm the exact override in current docs, and prefer adopting the new structure for anything long-lived.
Yes. In Nuxt 3, set future: { compatibilityVersion: 4 }
in nuxt.config. That opts you into the Nuxt 4 defaults so you can find and fix breakages while still on Nuxt 3.
Two things most often: paths and config that assumed the old root layout, and code that relied on the old data-fetching reactivity or on components holding independent copies of the same fetch. Running the codemod plus compatibility mode catches most of it.
Components that request the same data with the same key now share a single, deduplicated data object, and the reactivity default for fetched data changed. Audit anywhere you mutate fetched data in place. Verify the exact current behavior against the Nuxt docs.
Start learning Vue.js for free

Comments
Latest Vue School Articles
5 Component Design Patterns to Boost Your Vue.js Applications

Vibe Coding a Collaborative Editor with Comment Support with Nuxt UI and Jazz

Our goal is to be the number one source of Vue.js knowledge for all skill levels. We offer the knowledge of our industry leaders through awesome video courses for a ridiculously low price.
More than 250.000 users have already joined us. You are welcome too!
© All rights reserved. Made with ❤️ by BitterBrains, Inc.


