Skip to content
Release notesNuxt

Nuxt 4 alpha: the app/ directory and how to try it today

On 2 June the Nuxt team published a post called Roadmap to v4 and the first 4.0.0-alpha build. Stable Nuxt 4 is not out. Here is what is, and the safe way to test it.

vuejs.school team5 Jun 20257 min readNuxt 3.17 · Nuxt 4 alpha

On 2 June 2025 Daniel Roe published Roadmap to v4 on the Nuxt blog, and 4.0.0-alpha.1 went to npm the same day. Stable Nuxt 4 is not out; the plan is the end of June. Most of what Nuxt 4 changes has been available behind a flag in Nuxt 3 since 3.12.

If you maintain a Nuxt 3 app, you can test those changes today with one line in nuxt.config.ts, without leaving Nuxt 3.17. Installing the alpha package is the other way, and it belongs on a branch.

When stable Nuxt 4 is due, and why it ships now

The roadmap opens by admitting the original plan, June 2024, slipped by a year, and sets a new one: the alpha ships on 2 June 2025, with a stable release at the end of the month. Further down, the release candidate is pencilled in for 23 June, a date the team says it is aiming for. Read both as plans, not promises.

For the next few weeks the team plans pull requests to community modules in the nuxt/modules registry, a migration guide for module authors, a full upgrade guide for Nuxt 3 users, bug fixes only on the 3.x line during June, and a version switcher for the docs.

The reasoning for shipping now is practical. Teams already run compatibilityVersion: 4 in production, although the option was meant only for testing, and a stable release gives them a supported version. Splitting the work into two majors also makes the migration smoother. Nuxt 4 contains the changes tested behind the flag, and the major after it will adopt the next major version of Nitro, Nuxt's server engine, once that is ready, even if that is only a few months after Nuxt 4. Going forward, the team wants Nuxt releases decoupled from Vite and Nitro majors.

Support for Nuxt 3 and the release stages

Nuxt 3 does not stop when Nuxt 4 ships. The post commits to six months of maintenance for each release after its successor ships, and its table gives the dates:

  • Nuxt 3 continues receiving updates until the end of 2025.
  • Nuxt 4 is supported until mid-2026 (an estimate).

The post adds that the window will be extended if the ecosystem has trouble migrating.

It also says what each pre-release stage means:

  • Alpha: more breaking changes are planned, and bugs or friction are expected.
  • Beta: no breaking changes are planned, though one may still land if necessary.
  • Release candidate: bug fixes only.

What Nuxt 4 changes, and what only the alpha removes

The roadmap lists four headline changes, all of which Nuxt 3 users could already test with compatibilityVersion: 4:

  • A new directory structure: application code moves into app/.
  • Improved data fetching: useAsyncData and useFetch share state by key, clean up after themselves and refetch on reactive keys.
  • Consistent component names: Vue DevTools and <KeepAlive> now see the same name that Nuxt's auto-import uses.
  • Head management on Unhead v2, with deprecated features removed.

It names two more changes still to come: preparation for Vite's Environment API with a single dev server, and better handling of the server, client and shared type environments.

The alpha package goes further than the flag. As its release notes list, it makes compatibilityVersion: 4 the default and removes compatibilityVersion: 3. It also drops Nuxt 2 and Bridge support, and removes the assets and public import aliases, which Nuxt 3.17 keeps with or without the flag. The package requires Node 20 (20.11.1 or later) or Node 22 and later, so Node 18, which Nuxt 3.17 still accepts, is out.

Move application code into app/

The folder change is the one you will notice first. This is the layout the upgrade guide describes:

app/
  assets/
  components/
  composables/
  layouts/
  middleware/
  pages/
  plugins/
  utils/
  app.config.ts
  app.vue
  error.vue
content/
layers/
modules/
public/
server/
  api/
  middleware/
  plugins/
  routes/
  utils/
nuxt.config.ts

The Vue side of the app moves under app/, including app.vue, error.vue and app.config.ts. server/, public/, modules/, layers/, content/ and nuxt.config.ts stay at the root. The guide gives two reasons:

  • Faster startup. File watchers that scan the project root also scan .git/ and node_modules/, which slows startup, mostly on Windows and Linux.
  • Better editor autocompletion. server/ code runs in a different context, with different auto-imports, from the rest of the app. Keeping the two apart gives the editor a cleaner picture of what is in scope.

Migration is optional. If Nuxt finds the old structure, such as a pages/ folder at the root, it keeps using it. When you are ready, one codemod moves the folders:

npx codemod@latest nuxt/4/file-structure

Afterwards, check any tool that points at the old paths, such as a Tailwind content list or an ESLint config.

Check data fetching for the new defaults

Most of the data-fetching rework already reached you in Nuxt 3.17, flag or no flag: shared refs for the same key, reactive keys, the getCachedData context and cleanup on unmount. What compatibilityVersion: 4 changes are defaults, and the first one shows up in templates.

data and error now start as undefined instead of null. Take a products page that calls useFetch('/api/products') (a server/api/products.get.ts route that returns an array) and tests for null:

pages/products.vueBreaks
<template>
  <p v-if="error !== null">Something went wrong.</p>
  <ul v-else-if="data !== null">
    <li v-for="product in data" :key="product.id">{{ product.name }}</li>
  </ul>
</template>

Under the new default, error is undefined when nothing failed, and undefined !== null is true. The page shows "Something went wrong." on every load and never reaches the list. Test for truthiness instead, and the template is correct under both versions:

pages/products.vueWorks
<script setup lang="ts">
const { data, error, status } = await useFetch('/api/products')
</script>

<template>
  <p v-if="error">Something went wrong.</p>
  <p v-else-if="status === 'pending'">Loading</p>
  <ul v-else-if="data">
    <li v-for="product in data" :key="product.id">{{ product.name }}</li>
  </ul>
</template>

Three more defaults move. Each has an experimental option, listed in the guide, that restores the old behaviour:

  • pending is now a computed that is true only while status is 'pending'. With immediate: false it starts as false, so the guide's advice is to read status instead.
  • data is shallow by default (deep: false). That is faster for deeply nested objects and arrays, and it means replacing data.value triggers updates while mutating a nested field does not.
  • A custom getCachedData (the option that can return stored data instead of fetching) runs on every fetch, including refresh(), refreshNuxtData() and refetches started by a watcher, not only the first load. Its third argument says why it was called, so you can skip the cache on a manual refresh.

The null checks also have a codemod: npx codemod@latest nuxt/4/default-data-error-value rewrites them for you.

Turn on Nuxt 4 behaviour in Nuxt 3.17

The safe way to try all of this is the flag, available since Nuxt 3.12. Stay on 3.17, add one line to nuxt.config.ts, and run your test suite:

nuxt.config.ts
export default defineNuxtConfig({
  future: {
    compatibilityVersion: 4,
  },
})

Your folders can stay where they are: Nuxt detects the root pages/ and keeps the Nuxt 3 layout. The new defaults do apply. If one of them breaks something, the guide shows the experimental option that turns it back off while you fix the cause. For example, experimental: { normalizeComponentNames: false } helps when a test's findComponent() looks a component up by its file name (MyComponent) rather than its auto-import name (SomeFolderMyComponent). If your app extends layers, set the flag in each layer that opts in; the guide says this will not be needed after the stable release.

To apply every migration at once, the Codemod team maintains a recipe that runs each codemod in turn and lets you deselect any:

npx codemod@latest nuxt/4/migration-recipe

Install the alpha on a branch

If you want the real package, npm has it under the alpha tag. On a branch:

npm install nuxt@alpha --save-exact

Without --save-exact, npm saves the range ^4.0.0-alpha.1, which also accepts later builds such as the next alpha. Pinning keeps you on the build you tested while breaking changes are still planned. The nuxi upgrade command in Nuxt CLI 3.25 knows the stable and nightly channels but not an alpha one, so install the tag directly. If you want the newest build rather than the tagged one, the nuxt-nightly package's latest tag tracks the 4.x branch.

Expect modules to lag. The roadmap says the team plans to open pull requests to community modules, so until those land, some modules may fail to install or warn about an unsupported Nuxt version. The roadmap asks for reports to Nuxt and to the modules you use, so send a module's problem to that module's repository.

What to do this week

For a production app, do not install the alpha. Turn on compatibilityVersion: 4 on a branch, run the migration recipe, run your tests, and fix what breaks. When the stable release arrives, the version bump will be the smallest part of the upgrade. For a new project, start with the flag and the app/ folder from the first commit, so there is nothing to move later. And for the stable date, trust the release page when it exists, not the calendar: the end of June is a plan, and the last one slipped.

Check it stuck3 questions from the Layouts and page meta quiz.
Practice now
Was this helpful?

Keep reading