# Experimental Features

> Enable Nuxt experimental features to unlock new possibilities.

Nuxt includes experimental features that you can enable in your configuration file.

Internally, Nuxt uses `@nuxt/schema` to define these experimental features. You can refer to the [API documentation](/docs/4.x/guide/going-further/experimental-features) or the [source code](https://github.com/nuxt/nuxt/blob/main/packages/schema/src/config/experimental.ts) for more information.

<note>

Note that these features are experimental and could be removed or modified in the future.

</note>

## alwaysRunFetchOnKeyChange

Whether to run `useFetch` when the key changes, even if it is set to `immediate: false` and it has not been triggered yet.

`useFetch` and `useAsyncData` will always run when the key changes if `immediate: true` or if it has been already triggered.

This flag is disabled by default, but you can enable this feature:

```ts [nuxt.config.ts]twoslash
export default defineNuxtConfig({
  experimental: {
    alwaysRunFetchOnKeyChange: true,
  },
})
```

## appManifest

Use app manifests to respect route rules on client-side.

This flag is enabled by default, but you can disable this feature:

```ts [nuxt.config.ts]twoslash
export default defineNuxtConfig({
  experimental: {
    appManifest: false,
  },
})
```

## asyncContext

Enable native async context to be accessible for nested composables in Nuxt and in Nitro. This opens the possibility to use composables inside async composables and reduce the chance to get the `Nuxt instance is unavailable` error.

```ts [nuxt.config.ts]twoslash
export default defineNuxtConfig({
  experimental: {
    asyncContext: true,
  },
})
```

<read-more to="https://github.com/nuxt/nuxt/pull/20918" icon="i-simple-icons-github" target="_blank">

See full explanation on the GitHub pull-request.

</read-more>

## asyncEntry

Enables generation of an async entry point for the Vue bundle, aiding module federation support.

```ts [nuxt.config.ts]twoslash
export default defineNuxtConfig({
  experimental: {
    asyncEntry: true,
  },
})
```

## externalVue

Externalizes `vue`, `@vue/*` and `vue-router` when building.

This flag is enabled by default, but you can disable this feature:

```ts [nuxt.config.ts]twoslash
export default defineNuxtConfig({
  experimental: {
    externalVue: false,
  },
})
```

<warning>

This feature will likely be removed in a near future.

</warning>

## extractAsyncDataHandlers

Extracts handler functions from `useAsyncData` and `useLazyAsyncData` calls into separate chunks for improved code splitting and caching efficiency.

```ts [nuxt.config.ts]twoslash
export default defineNuxtConfig({
  experimental: {
    extractAsyncDataHandlers: true,
  },
})
```

This feature transforms inline handler functions into dynamically imported chunks:

```vue
<!-- Before -->
<script setup>
const { data } = await useAsyncData('user', async () => {
  return await $fetch('/api/user')
})
</script>
```

```vue
<!-- After transformation -->
<script setup>
const { data } = await useAsyncData('user', () =>
  import('/generated-chunk.js').then(r => r.default()),
)
</script>
```

The benefit of this transformation is that we can split out data fetching logic — while still allowing the code to be loaded if required.

<important>

This feature is only recommended for **static builds** with payload extraction, and where data does not need to be re-fetched at runtime.

</important>

## emitRouteChunkError

Emits `app:chunkError` hook when there is an error loading vite/webpack chunks. Default behavior is to perform a reload of the new route on navigation to a new route when a chunk fails to load.

By default, Nuxt will also perform a reload of the new route when a chunk fails to load when navigating to a new route (`automatic`).

Setting `automatic-immediate` will lead Nuxt to perform a reload of the current route right when a chunk fails to load (instead of waiting for navigation). This is useful for chunk errors that are not triggered by navigation, e.g., when your Nuxt app fails to load a [lazy component](/docs/4.x/directory-structure/app/components#dynamic-imports). A potential downside of this behavior is undesired reloads, e.g., when your app does not need the chunk that caused the error.

You can disable automatic handling by setting this to `false`, or handle chunk errors manually by setting it to `manual`.

```ts [nuxt.config.ts]twoslash
export default defineNuxtConfig({
  experimental: {
    emitRouteChunkError: 'automatic', // or 'automatic-immediate', 'manual' or false
  },
})
```

## enforceModuleCompatibility

Whether Nuxt should throw an error (and fail to load) if a Nuxt module is incompatible.

This feature is disabled by default.

```ts [nuxt.config.ts]twoslash
export default defineNuxtConfig({
  experimental: {
    enforceModuleCompatibility: true,
  },
})
```

## restoreState

Allows Nuxt app state to be restored from `sessionStorage` when reloading the page after a chunk error or manual [`reloadNuxtApp()`](/docs/4.x/api/utils/reload-nuxt-app) call.

To avoid hydration errors, it will be applied only after the Vue app has been mounted, meaning there may be a flicker on initial load.

<important>

Consider carefully before enabling this as it can cause unexpected behavior,
and consider providing explicit keys to [`useState`](/docs/4.x/api/composables/use-state) as auto-generated keys may not match across builds.

</important>

```ts [nuxt.config.ts]twoslash
export default defineNuxtConfig({
  experimental: {
    restoreState: true,
  },
})
```

## inlineRouteRules

Define route rules at the page level using [`defineRouteRules`](/docs/4.x/api/utils/define-route-rules).

```ts [nuxt.config.ts]twoslash
export default defineNuxtConfig({
  experimental: {
    inlineRouteRules: true,
  },
})
```

Matching route rules will be created, based on the page's `path`.

<read-more to="/docs/4.x/api/utils/define-route-rules" icon="i-lucide-square-function">

Read more in `defineRouteRules` utility.

</read-more>

<read-more to="/docs/4.x/guide/concepts/rendering#hybrid-rendering" icon="i-lucide-medal">



</read-more>

## renderJsonPayloads

Allows rendering of JSON payloads with support for revivifying complex types.

This flag is enabled by default, but you can disable this feature:

```ts [nuxt.config.ts]twoslash
export default defineNuxtConfig({
  experimental: {
    renderJsonPayloads: false,
  },
})
```

## noVueServer

Disables Vue server renderer endpoint within Nitro.

```ts [nuxt.config.ts]twoslash
export default defineNuxtConfig({
  experimental: {
    noVueServer: true,
  },
})
```

## parseErrorData

Whether to parse `error.data` when rendering a server error page.

This flag is enabled by default, but you can disable this feature:

```ts [nuxt.config.ts]twoslash
export default defineNuxtConfig({
  experimental: {
    parseErrorData: false,
  },
})
```

## payloadExtraction

Controls how payload data is delivered for prerendered and cached (ISR/SWR) pages.

- `'client'` - Payload is inlined in HTML for the initial server render, and extracted to `_payload.json` files for client-side navigation. This avoids a separate network request on initial load while still enabling efficient client-side navigation.
- `true` - Payload is extracted to a separate `_payload.json` file for both the initial server render and client-side navigation.
- `false` - Payload extraction is disabled entirely. Payload is always inlined in HTML and no `_payload.json` files are generated.

The default is `true`, or `'client'` when `compatibilityVersion: 5` is set.

```ts [nuxt.config.ts]twoslash
export default defineNuxtConfig({
  experimental: {
    // Inline payload in HTML, extract for client-side navigation only
    payloadExtraction: 'client',
  },
})
```

Payload extraction also works for routes using ISR (Incremental Static Regeneration) or SWR (Stale-While-Revalidate) caching strategies. This allows CDNs to cache payload files alongside HTML, improving client-side navigation performance for cached routes.

```ts [nuxt.config.ts]twoslash
export default defineNuxtConfig({
  experimental: {
    payloadExtraction: 'client',
  },
  routeRules: {
    // Payload files will be generated for these cached routes
    '/products/**': { isr: 3600 },
    '/blog/**': { swr: true },
  },
})
```

## clientNodePlaceholder

Uses comment nodes (`<!--placeholder-->`) instead of `<div>` elements as placeholders for client-only components during server-side rendering.

When enabled, `.client.vue` components and `createClientOnly()` wrappers render an HTML comment on the server instead of an empty `<div>`. This fixes a Vue hydration issue where scoped styles may not be applied when the placeholder `<div>` and the actual component root share the same tag name.

<warning>

Enabling this means attributes (`class`, `style`, etc.) passed to `.client.vue` components will not appear in the SSR HTML. If you need styled placeholders to prevent layout shift, use `<ClientOnly>` with a `#fallback` slot instead.

</warning>

This flag is enabled when `future.compatibilityVersion` is set to `5` or higher, but you can also enable it explicitly:

```ts [nuxt.config.ts]twoslash
export default defineNuxtConfig({
  experimental: {
    clientNodePlaceholder: true,
  },
})
```

## clientFallback

Enables the experimental [`<NuxtClientFallback>`](/docs/4.x/api/components/nuxt-client-fallback) component for rendering content on the client if there's an error in SSR.

```ts [nuxt.config.ts]twoslash
export default defineNuxtConfig({
  experimental: {
    clientFallback: true,
  },
})
```

## crossOriginPrefetch

Enables cross-origin prefetch using the Speculation Rules API.

```ts [nuxt.config.ts]twoslash
export default defineNuxtConfig({
  experimental: {
    crossOriginPrefetch: true,
  },
})
```

<read-more to="https://wicg.github.io/nav-speculation/prefetch.html" icon="i-simple-icons-w3c" target="_blank">

Read more about the **Speculation Rules API**.

</read-more>

## viewTransition

Enables View Transition API integration with client-side router.

```ts [nuxt.config.ts]twoslash
export default defineNuxtConfig({
  experimental: {
    viewTransition: true,
  },
})
```

You can also pass an object to configure [view transition types](/docs/4.x/getting-started/transitions#view-transition-types), which allow different CSS animations based on the type of navigation:

```ts [nuxt.config.ts]twoslash
export default defineNuxtConfig({
  experimental: {
    viewTransition: {
      enabled: true,
      types: ['slide'],
    },
  },
})
```

<link-example target="_blank" to="https://stackblitz.com/edit/nuxt-view-transitions?file=app.vue">



</link-example>

<read-more to="https://developer.mozilla.org/en-US/docs/Web/API/View_Transition_API" icon="i-simple-icons-mdnwebdocs" target="_blank">

Read more about the **View Transition API**.

</read-more>

<read-more to="https://developer.chrome.com/blog/view-transitions-update-io24" icon="i-simple-icons-google" target="_blank">

Read more about the **View Transition API**.

</read-more>

## writeEarlyHints

Enables writing of early hints when using node server.

```ts [nuxt.config.ts]twoslash
export default defineNuxtConfig({
  experimental: {
    writeEarlyHints: true,
  },
})
```

## componentIslands

Enables experimental component islands support with [`<NuxtIsland>`](/docs/4.x/api/components/nuxt-island) and `.island.vue` files.

```ts [nuxt.config.ts]twoslash
export default defineNuxtConfig({
  experimental: {
    componentIslands: true, // false or 'local+remote'
  },
})
```

<read-more to="/docs/4.x/directory-structure/app/components#server-components">



</read-more>

<read-more to="https://github.com/nuxt/nuxt/issues/19772" icon="i-simple-icons-github" target="_blank">

You can follow the server components roadmap on GitHub.

</read-more>

## localLayerAliases

Resolve `~`, `~~`, `@` and `@@` aliases located within layers with respect to their layer source and root directories.

This flag is enabled by default, but you can disable this feature:

```ts [nuxt.config.ts]twoslash
export default defineNuxtConfig({
  experimental: {
    localLayerAliases: false,
  },
})
```

## typedPages

Enable the new experimental typed router.

```ts [nuxt.config.ts]twoslash
export default defineNuxtConfig({
  experimental: {
    typedPages: true,
  },
})
```

Out of the box, this will enable typed usage of [`navigateTo`](/docs/4.x/api/utils/navigate-to), [`<NuxtLink>`](/docs/4.x/api/components/nuxt-link), [`router.push()`](/docs/4.x/api/composables/use-router) and more.

You can even get typed params within a page by using `const route = useRoute('route-name')`.

<video-accordion title="Watch a video from Daniel Roe explaining type-safe routing in Nuxt" video-id="SXk-L19gTZk">



</video-accordion>

## watcher

Set an alternative watcher that will be used as the watching service for Nuxt.

Nuxt uses `chokidar-granular` by default, which will ignore top-level directories
(like `node_modules` and `.git`) that are excluded from watching.

You can set this instead to `parcel` to use `@parcel/watcher`, which may improve
performance in large projects or on Windows platforms.

You can also set this to `chokidar` to watch all files in your source directory.

Set to `'builder'` to reuse the active builder's own file watcher (for example,
Vite's `server.watcher`) instead of starting a second one. This reduces the
number of file watchers active in dev mode and becomes the default when
`future.compatibilityVersion` is `5`. If the active builder does not implement
its own watcher (currently webpack and rspack), Nuxt logs a warning and falls
back to its default selection.

```ts [nuxt.config.ts]twoslash
export default defineNuxtConfig({
  experimental: {
    watcher: 'chokidar-granular', // 'chokidar', 'parcel' or 'builder' are also options
  },
})
```

## sharedPrerenderData

Nuxt automatically shares payload *data* between pages that are prerendered. This can result in a significant performance improvement when prerendering sites that use `useAsyncData` or `useFetch` and fetch the same data in different pages.

You can disable this feature if needed.

```ts [nuxt.config.ts]twoslash
export default defineNuxtConfig({
  experimental: {
    sharedPrerenderData: false,
  },
})
```

<video-accordion title="Watch a video from Alexander Lichter about the experimental sharedPrerenderData" video-id="1jUupYHVvrU">



</video-accordion>

It is particularly important when enabling this feature to make sure that any unique key of your data
is always resolvable to the same data. For example, if you are using `useAsyncData` to fetch
data related to a particular page, you should provide a key that uniquely matches that data. (`useFetch`
should do this automatically for you.)

```ts
// This would be unsafe in a dynamic page (e.g. `[slug].vue`) because the route slug makes a difference
// to the data fetched, but Nuxt can't know that because it's not reflected in the key.
const route = useRoute()
const { data } = await useAsyncData(async (_nuxtApp, { signal }) => {
  return await $fetch(`/api/my-page/${route.params.slug}`, { signal })
})
// Instead, you should use a key that uniquely identifies the data fetched.
const { data } = await useAsyncData(route.params.slug, async (_nuxtApp, { signal }) => {
  return await $fetch(`/api/my-page/${route.params.slug}`, { signal })
})
```

## clientNodeCompat

With this feature, Nuxt will automatically polyfill Node.js imports in the client build using [`unenv`](https://github.com/unjs/unenv).

<note>

To make globals like `Buffer` work in the browser, you need to manually inject them.

```ts
import { Buffer } from 'node:buffer'

globalThis.Buffer ||= Buffer
```

</note>

## scanPageMeta

Nuxt exposing some route metadata defined in `definePageMeta` at build-time to modules (specifically `alias`, `name`, `path`, `redirect`, `props` and `middleware`).

This only works with static or strings/arrays rather than variables or conditional assignment. See [original issue](https://github.com/nuxt/nuxt/issues/24770) for more information and context.

By default page metadata is only scanned after all routes have been registered in `pages:extend`. Then another hook, `pages:resolved` will be called.

You can disable this feature if it causes issues in your project.

```ts [nuxt.config.ts]twoslash
export default defineNuxtConfig({
  experimental: {
    scanPageMeta: false,
  },
})
```

## cookieStore

Enables CookieStore support to listen for cookie updates (if supported by the browser) and refresh `useCookie` ref values.

This flag is enabled by default, but you can disable this feature:

```ts [nuxt.config.ts]twoslash
export default defineNuxtConfig({
  experimental: {
    cookieStore: false,
  },
})
```

<read-more to="https://developer.mozilla.org/en-US/docs/Web/API/CookieStore" icon="i-simple-icons-mdnwebdocs" target="_blank">

Read more about the **CookieStore**.

</read-more>

## buildCache

Caches Nuxt build artifacts based on a hash of the configuration and source files.

This only works for source files within `srcDir` and `serverDir` for the Vue/Nitro parts of your app.

This flag is disabled by default, but you can enable it:

```ts [nuxt.config.ts]twoslash
export default defineNuxtConfig({
  experimental: {
    buildCache: true,
  },
})
```

When enabled, changes to the following files will trigger a full rebuild:

```bash [Directory structure]
.nuxtrc
.npmrc
package.json
package-lock.json
yarn.lock
pnpm-lock.yaml
tsconfig.json
bun.lock
bun.lockb
```

In addition, any changes to files within `srcDir` will trigger a rebuild of the Vue client/server bundle. Nitro will always be rebuilt (though work is in progress to allow Nitro to announce its cacheable artifacts and their hashes).

<note>

A maximum of 10 cache tarballs are kept.

</note>

## checkOutdatedBuildInterval

Set the time interval (in ms) to check for new builds. Disabled when `experimental.appManifest` is `false`.

Set to `false` to disable.

```ts [nuxt.config.ts]twoslash
export default defineNuxtConfig({
  experimental: {
    checkOutdatedBuildInterval: 3600000, // 1 hour, or false to disable
  },
})
```

## extraPageMetaExtractionKeys

The `definePageMeta()` macro is a useful way to collect build-time meta about pages. Nuxt itself provides a set list of supported keys which is used to power some of the internal features such as redirects, page aliases and custom paths.

This option allows passing additional keys to extract from the page metadata when using `scanPageMeta`.

```vue
<script lang="ts" setup>
definePageMeta({
  foo: 'bar',
})
</script>
```

```ts
export default defineNuxtConfig({
  experimental: {
    extraPageMetaExtractionKeys: ['foo'],
  },
  hooks: {
    'pages:resolved' (ctx) {
      // ✅ foo is available
    },
  },
})
```

This allows modules to access additional metadata from the page metadata in the build context. If you are using this within a module, it's recommended also to [augment the `NuxtPage` types with your keys](/docs/4.x/directory-structure/app/pages#typing-custom-metadata).

## navigationRepaint

Wait for a single animation frame before navigation, which gives an opportunity for the browser to repaint, acknowledging user interaction.

It can reduce INP when navigating on prerendered routes.

This flag is enabled by default, but you can disable this feature:

```ts [nuxt.config.ts]twoslash
export default defineNuxtConfig({
  experimental: {
    navigationRepaint: false,
  },
})
```

## normalizeComponentNames

Nuxt updates auto-generated Vue component names to match the full component name you would use to auto-import the component.

If you encounter issues, you can disable this feature.

```ts [nuxt.config.ts]twoslash
export default defineNuxtConfig({
  experimental: {
    normalizeComponentNames: false,
  },
})
```

By default, if you haven't set it manually, Vue will assign a component name that matches
the filename of the component.

```bash [Directory structure]
├─ components/
├─── SomeFolder/
├───── MyComponent.vue
```

In this case, the component name would be `MyComponent`, as far as Vue is concerned. If you wanted to use `<KeepAlive>` with it, or identify it in the Vue DevTools, you would need to use this component.

But in order to auto-import it, you would need to use `SomeFolderMyComponent`.

By setting `experimental.normalizeComponentNames`, these two values match, and Vue will generate a component name that matches the Nuxt pattern for component naming.

## normalizePageNames

Ensure that page component names match their route names. This sets the `__name` property on page components so that Vue's `<KeepAlive>` can correctly identify them by name.

By default, Vue assigns component names based on the filename. For example, `pages/foo/index.vue` and `pages/bar/index.vue` would both have the component name `index`. This makes name-based `<KeepAlive>` filtering unreliable because multiple pages share the same name.

With `normalizePageNames` enabled, page components are named after their route (e.g. `foo` and `bar`), so you can use `<KeepAlive>` with `include`/`exclude` without manually adding `defineOptions({ name: '...' })` to each page.

This flag is enabled when `future.compatibilityVersion` is set to `5` or higher, but you can disable this feature:

```ts [nuxt.config.ts]twoslash
export default defineNuxtConfig({
  experimental: {
    normalizePageNames: false,
  },
})
```

```vue [app.vue]
<template>
  <NuxtPage :keepalive="{ include: ['foo'] }" />
</template>
```

## spaLoadingTemplateLocation

When rendering a client-only page (with `ssr: false`), we optionally render a loading screen (from `~/spa-loading-template.html`).

It can be set to `within`, which will render it like this:

```html
<div id="__nuxt">
  <!-- spa loading template -->
</div>
```

Alternatively, you can render the template alongside the Nuxt app root by setting it to `body`:

```html
<div id="__nuxt"></div>
<!-- spa loading template -->
```

This avoids a white flash when hydrating a client-only page.

## browserDevtoolsTiming

Enables performance markers for Nuxt hooks in browser devtools. This adds performance markers that you can track in the Performance tab of Chromium-based browsers, which is useful for debugging and optimizing performance.

This is enabled by default in development mode. If you need to disable this feature, it is possible to do so:

```ts [nuxt.config.ts]twoslash
export default defineNuxtConfig({
  experimental: {
    browserDevtoolsTiming: false,
  },
})
```

<read-more to="https://github.com/nuxt/nuxt/pull/29922" icon="i-simple-icons-github" target="_blank" color="gray">

See PR #29922 for implementation details.

</read-more>

<read-more to="https://developer.chrome.com/docs/devtools/performance/extension#tracks" icon="i-simple-icons-googlechrome" target="_blank" color="gray">

Learn more about Chrome DevTools Performance API.

</read-more>

## debugModuleMutation

Records mutations to `nuxt.options` in module context, helping to debug configuration changes made by modules during the Nuxt initialization phase.

This is enabled by default when `debug` mode is enabled. If you need to disable this feature, it is possible to do so:

To enable it explicitly:

```ts [nuxt.config.ts]twoslash
export default defineNuxtConfig({
  experimental: {
    debugModuleMutation: true,
  },
})
```

<read-more to="https://github.com/nuxt/nuxt/pull/30555" icon="i-simple-icons-github" target="_blank" color="gray">

See PR #30555 for implementation details.

</read-more>

## lazyHydration

This enables hydration strategies for `<Lazy>` components, which improves performance by deferring hydration of components until they're needed.

Lazy hydration is enabled by default, but you can disable this feature:

```ts [nuxt.config.ts]twoslash
export default defineNuxtConfig({
  experimental: {
    lazyHydration: false,
  },
})
```

<read-more to="/docs/4.x/directory-structure/app/components#delayed-or-lazy-hydration" icon="i-simple-icons-github" color="gray">

Read more about lazy hydration.

</read-more>

## templateImportResolution

Disable resolving imports into Nuxt templates from the path of the module that added the template.

By default, Nuxt attempts to resolve imports in templates relative to the module that added them. Setting this to `false` disables this behavior, which may be useful if you're experiencing resolution conflicts in certain environments.

This flag is enabled by default, but you can disable this feature:

```ts [nuxt.config.ts]twoslash
export default defineNuxtConfig({
  experimental: {
    templateImportResolution: false,
  },
})
```

<read-more to="https://github.com/nuxt/nuxt/pull/31175" icon="i-simple-icons-github" target="_blank" color="gray">

See PR #31175 for implementation details.

</read-more>

## templateRouteInjection

By default the route object returned by the auto-imported `useRoute()` composable is kept in sync with the current page in view in `<NuxtPage>`. This is not true for `vue-router`'s exported `useRoute` or for the default `$route` object available in your Vue templates.

By enabling this option a mixin will be injected to keep the `$route` template object in sync with Nuxt's managed `useRoute()`.

This flag is enabled by default, but you can disable this feature:

```ts [nuxt.config.ts]twoslash
export default defineNuxtConfig({
  experimental: {
    templateRouteInjection: false,
  },
})
```

## decorators

This option enables decorator syntax across your entire Nuxt/Nitro app.

When using the Vite builder (default), decorators are lowered via [Babel](https://babeljs.io/) using [`@babel/plugin-proposal-decorators`](https://babeljs.io/docs/babel-plugin-proposal-decorators). When using the webpack or rspack builders, decorators are lowered via [esbuild](https://github.com/evanw/esbuild/releases/tag/v0.21.3).

For a long time, TypeScript has had support for decorators via `compilerOptions.experimentalDecorators`. This implementation predated the TC39 standardization process. Now, decorators are a [Stage 3 Proposal](https://github.com/tc39/proposal-decorators), and supported without special configuration in TS 5.0+ (see [https://github.com/microsoft/TypeScript/pull/52582](https://github.com/microsoft/TypeScript/pull/52582) and [https://devblogs.microsoft.com/typescript/announcing-typescript-5-0-beta/#decorators](https://devblogs.microsoft.com/typescript/announcing-typescript-5-0-beta/#decorators)).

Enabling `experimental.decorators` enables support for the TC39 proposal, **NOT** for TypeScript's previous `compilerOptions.experimentalDecorators` implementation.

<warning>

Note that there may be changes before this finally lands in the JS standard.

</warning>

### Usage

```ts [nuxt.config.ts]twoslash
export default defineNuxtConfig({
  experimental: {
    decorators: true,
  },
})
```

When using the Vite builder or the Nitro server build, you will need to install additional Babel packages as dev dependencies:

<code-group>

```bash [npm]
npm install -D @babel/plugin-proposal-decorators @babel/plugin-syntax-jsx
```

```bash [pnpm]
pnpm add -D @babel/plugin-proposal-decorators @babel/plugin-syntax-jsx
```

```bash [yarn]
yarn add -D @babel/plugin-proposal-decorators @babel/plugin-syntax-jsx
```

</code-group>

<tip>

Nuxt will prompt you to install these automatically if they are not already present.

</tip>

```ts [app/app.vue]
function something (_method: () => unknown) {
  return () => 'decorated'
}

class SomeClass {
  @something
  public someMethod () {
    return 'initial'
  }
}

const value = new SomeClass().someMethod()
// this will return 'decorated'
```

## defaults

This allows specifying the default options for core Nuxt components and composables.

These options will likely be moved elsewhere in the future, such as into `app.config` or into the `app/` directory.

```ts [nuxt.config.ts]twoslash
export default defineNuxtConfig({
  experimental: {
    defaults: {
      nuxtLink: {
        componentName: 'NuxtLink',
        prefetch: true,
        prefetchOn: {
          visibility: true,
        },
      },
      useAsyncData: {
        deep: true,
      },
      useState: {
        resetOnClear: true,
      },
    },
  },
})
```

The `useState.resetOnClear` option controls whether [`clearNuxtState`](/docs/4.x/api/utils/clear-nuxt-state) resets state to its initial value (provided by the `init` function of [`useState`](/docs/4.x/api/composables/use-state)) instead of setting it to `undefined`. This defaults to `true` with `compatibilityVersion: 5`.

## purgeCachedData

Whether to clean up Nuxt static and asyncData caches on route navigation.

Nuxt will automatically purge cached data from `useAsyncData` and `nuxtApp.static.data`. This helps prevent memory leaks and ensures fresh data is loaded when needed, but it is possible to disable it.

This flag is enabled by default, but you can disable this feature:

```ts [nuxt.config.ts]twoslash
export default defineNuxtConfig({
  experimental: {
    purgeCachedData: false,
  },
})
```

<read-more to="https://github.com/nuxt/nuxt/pull/31379" icon="i-simple-icons-github" target="_blank" color="gray">

See PR #31379 for implementation details.

</read-more>

## prefetchPreloadTags

When a `<NuxtLink>` is prefetched and the destination route has [payload extraction](#payloadextraction) enabled (the default for prerendered and cached routes), forward any `<link rel="preload">` hints that the destination set via [`useHead`](/docs/4.x/api/composables/use-head) (or via modules like [`@nuxt/image`](https://image.nuxt.com)'s `<NuxtImg preload>`) into the current document.

The forwarded links are downgraded from `rel="preload"` to `rel="prefetch"` so they don't compete with the current page's critical resources. Only user-defined head tags are forwarded; build-time JS/CSS chunk preloads are already handled separately by the prefetch pipeline.

This flag is off by default because, combined with `prefetchOn: 'visibility'` (the `<NuxtLink>` default), it could trigger a lot of cross-route prefetches at once. Enable it once you are confident the destination preloads are worth forwarding for the links your users typically encounter.

```ts [nuxt.config.ts]twoslash
export default defineNuxtConfig({
  experimental: {
    prefetchPreloadTags: true,
  },
})
```

<read-more to="https://github.com/nuxt/nuxt/issues/34953" icon="i-simple-icons-github" target="_blank" color="gray">

See issue #34953 for motivation.

</read-more>

## granularCachedData

Whether to call and use the result from `getCachedData` when refreshing data for `useAsyncData` and `useFetch` (whether by `watch`, `refreshNuxtData()`, or a manual `refresh()` call.

This flag is enabled by default, but you can disable this feature:

```ts [nuxt.config.ts]twoslash
export default defineNuxtConfig({
  experimental: {
    granularCachedData: false,
  },
})
```

<read-more to="https://github.com/nuxt/nuxt/pull/31373" icon="i-simple-icons-github" target="_blank" color="gray">

See PR #31373 for implementation details.

</read-more>

## headNext

Use head optimisations:

- Add the capo.js head plugin in order to render tags in of the head in a more performant way.
- Uses the hash hydration plugin to reduce initial hydration

This flag is enabled by default, but you can disable this feature:

```ts [nuxt.config.ts]twoslash
export default defineNuxtConfig({
  experimental: {
    headNext: false,
  },
})
```

## pendingWhenIdle

For `useAsyncData` and `useFetch`, whether `pending` should be `true` when data has not yet started to be fetched.

This flag is disabled by default, but you can enable this feature:

```ts [nuxt.config.ts]twoslash
export default defineNuxtConfig({
  experimental: {
    pendingWhenIdle: true,
  },
})
```

## entryImportMap

By default, Nuxt improves chunk stability by using an import map to resolve the entry chunk of the bundle.

This injects an import map at the top of your `<head>` tag:

```html
<script type="importmap">{"imports":{"#entry":"/_nuxt/DC5HVSK5.js"}}</script>
```

Within the script chunks emitted by Vite, imports will be from `#entry`. This means that changes to the entry will not invalidate chunks which are otherwise unchanged.

<note>

Nuxt smartly disables this feature if you have configured `vite.build.target` to include a browser that doesn't support import maps, or if you have configured `vite.build.rollupOptions.output.entryFileNames` to a value that does not include `[hash]`.

</note>

If you need to disable this feature you can do so:

```ts [nuxt.config.ts]twoslash
export default defineNuxtConfig({
  experimental: {
    entryImportMap: false,
  },
  // or, better, simply tell vite your desired target
  // which nuxt will respect
  vite: {
    build: {
      target: 'safari13',
    },
  },
})
```

## typescriptPlugin

Enable enhanced TypeScript developer experience with the `@dxup/nuxt` module.

This experimental plugin provides improved TypeScript integration and development tooling for better DX when working with TypeScript in Nuxt applications.

This flag is disabled by default, but you can enable this feature:

```ts [nuxt.config.ts]twoslash
export default defineNuxtConfig({
  experimental: {
    typescriptPlugin: true,
  },
})
```

<important>

To use this feature, you need to:

- Have `typescript` installed as a dependency
- Configure VS Code to use your workspace TypeScript version (see [VS Code documentation](https://code.visualstudio.com/docs/typescript/typescript-compiling#_using-the-workspace-version-of-typescript))

</important>

<read-more to="https://github.com/KazariEX/dxup" icon="i-simple-icons-github" target="_blank">

Learn more about **@dxup/nuxt**.

</read-more>

## viteEnvironmentApi

Enable Vite 6's new [Environment API](https://vite.dev/guide/api-environment) for improved build configuration and plugin architecture.

When you set `future.compatibilityVersion` to `5`, this feature is enabled by default. You can also enable it explicitly for testing:

```ts [nuxt.config.ts]twoslash
export default defineNuxtConfig({
  experimental: {
    viteEnvironmentApi: true,
  },
})
```

The Vite Environment API provides better consistency between development and production builds, more granular control over environment-specific configuration, and improved performance.

<important>

Enabling this feature changes how Vite plugins are registered and configured. See the [Vite Environment API migration guide](/docs/4.x/getting-started/upgrade#migration-to-vite-environment-api) for details on updating your plugins.

</important>

<read-more to="https://vite.dev/guide/api-environment" target="_blank">

Learn more about Vite's Environment API.

</read-more>

## ssrStreaming

Enables SSR streaming to dramatically improve Time to First Byte (TTFB). When enabled, the server sends the HTML shell (including `<head>`, styles, preload hints, and entry scripts) immediately, then streams the rendered body content progressively using Vue's `renderToWebStream`.

```ts [nuxt.config.ts]twoslash
export default defineNuxtConfig({
  experimental: {
    ssrStreaming: true,
  },
})
```

Streaming is automatically disabled for bot and crawler user agents (such as Googlebot, Bingbot, etc.) to ensure search engines receive fully-rendered HTML for SEO safety. The default pattern matches indexing crawlers only; Lighthouse and other audit tools deliberately fall outside it so synthetic measurements reflect the same streamed response real users get. You can customize the bot detection regex:

```ts [nuxt.config.ts]twoslash
export default defineNuxtConfig({
  experimental: {
    ssrStreaming: {
      botRegex: /googlebot|bingbot|my-internal-crawler/i,
    },
  },
})
```

You can also control streaming per-route using `routeRules`:

```ts [nuxt.config.ts]twoslash
export default defineNuxtConfig({
  experimental: {
    ssrStreaming: true,
  },
  routeRules: {
    '/no-stream/**': { streaming: false },
  },
})
```

<warning>

**Automatic fallback to non-streamed rendering.** Streaming commits the response status and headers as soon as the shell is flushed, which is incompatible with features that need to mutate the response after render. Requests matching any of the following are not streamed; they use the buffered renderer, or short-circuit to a redirect or error response:

- `routeRules` setting `noScripts`, `cache`, `isr`, `swr`, `redirect`, or `streaming: false` for the route
- `ssr: false` routes (already SPA-rendered)
- Bot/crawler user agents (controlled via `botRegex`)
- Prerendered routes (`nuxi generate`)
- Server-side `navigateTo()` redirects from plugins, middleware, or page setup
- Fatal errors thrown during initial render (before the shell flushes)

</warning>

<warning>

**Response status and headers must be set before the shell is flushed.** Streaming commits the HTTP status and headers with the first byte, so anything that mutates the response after that point cannot reach the client. This is inherent to streaming, not a Nuxt-specific bug.

The boundary is the shell flush:

- **Reaches the client**: mutations from Nuxt and Nitro plugins, which run to completion before rendering begins.
- **Dropped**: `setResponseStatus()`, `useResponseHeader()`, `useCookie()` writes and h3 `setHeader()`/`appendResponseHeader()` calls made during component rendering (including after an `await` in route middleware or `<script setup>`), since that work happens after the shell is already on the wire.

To keep a response mutation, move it into a plugin, or opt the route out of streaming:

- `routeRules: { '/path': { streaming: false } }`: static, per route.
- the `render:route` hook with `ctx.prefersStream = false`: runtime, per request (e.g. for routes that conditionally set a 404).

In development the streaming handler logs a warning naming the dropped mutations and the route, so these never fail silently.

</warning>

<note>

If an error occurs during streaming after the HTTP status is already committed, `payload.error` is set and the closing tags are still emitted as a well-formed document so the client picks up the error during hydration and renders the error page. Errors thrown before the shell flushes fall through to the buffered error renderer with the correct status code.

</note>

<note>

**Route styles are streamed, JS hints are entry-only.** The shell is flushed before the route renders, so its `<head>` carries entry-chunk styles and hints only. Once render registers the page and layout modules, their CSS is streamed straight after the shell (inlined as `<style>` when `inlineStyles` is enabled, otherwise as stylesheet links), so page, layout, and top-level async-component styles arrive before the body paints (nested async components are a FOUC caveat, covered below). Route-specific JS chunks are not preloaded from the shell; the browser discovers them after parsing the entry script. Streaming improves TTFB on every route; LCP gains are largest on routes whose JS overlaps the entry chunk.

</note>

<note>

**Component islands are compatible with streaming.** Island slot content and selective-client (`nuxt-client`) components are normally stitched into the HTML in a post-render pass, which is impossible once the body has streamed past the island anchors. Instead, the renderer emits each island teleport as an inert `<template>` at the end of the document and relocates it into place with an inline script that runs before hydration. This is transparent to app code. The exception is apps built with `features.noScripts` and island components, which fall back to the buffered renderer since the relocation script cannot run.

</note>

### Module hooks

Modules participate in the streaming response via the existing `render:html` hook (now with a `streaming: true` flag on the second argument) plus a per-request decision hook and two streaming-only hooks:

- **render:route** fires once per request before rendering begins, for every render (streaming enabled or not). Read `ctx.canStream` to see whether streaming is possible for the route, and set `ctx.prefersStream = false` to force buffered rendering for this request, e.g. based on a cookie, auth state, or A/B bucket. The renderer streams only when `canStream && prefersStream`. This is the runtime escape hatch for the static `routeRules` / `botRegex` config.
- **render:html** fires once, before the shell flushes, with `streaming: true` on its second argument. Mutations to `htmlAttrs`, `head`, `bodyAttrs`, and `bodyPrepend` reach the wire. Mutations to `body`/`bodyAppend` are dropped, since the body is about to stream (a dev-mode warning is emitted). Modules that only mutate head fields (CSP injection, OG tags, analytics meta) work in streaming with no code changes.
- **render:html:chunk** fires for each chunk produced by the renderer before it is enqueued. Mutate `ctx.chunk: Uint8Array` to transform bytes (e.g. nonce injection); read `ctx.index` to identify the first chunk vs. subsequent ones.
- **render:html:close** fires after the body stream completes, before closing tags. Mutate `ctx.bodyAppend: string[]` to inject final markup (end-of-body analytics tags, server-rendered debug widgets, etc.).

```ts
// modules/streaming-csp/src/runtime/server-plugin.ts
import { defineNitroPlugin } from '#imports'

export default defineNitroPlugin((nitro) => {
  nitro.hooks.hook('render:html', (ctx, { event }) => {
    const nonce = event.context.cspNonce
    if (!nonce) { return }
    // Works for both streaming (pre-shell) and buffered (post-render) paths.
    for (let i = 0; i < ctx.head.length; i++) {
      ctx.head[i] = ctx.head[i].replace(/<script(?![^>]*\snonce=)/g, `<script nonce="${nonce}"`)
    }
  })
})
```

<note>

**CSP nonce.** The streaming renderer emits several inline scripts and styles that bypass unhead: the bootstrap queue, the IIFE, suspense head pushes, island-teleport relocation, and route `<style>` blocks. If a `nonce` is present on the rendered head scripts, the renderer reuses it on all of them automatically, so a strict `script-src`/`style-src 'nonce-…'` policy does not block streaming. A module only needs to put the nonce on the head scripts (as above); the `render:html:chunk` hook remains available for stamping scripts that components render into the body.

</note>

<warning>

**Dev-mode FOUC for SFC styles:** in development, Vite serves SFC `<style>` blocks as JavaScript modules that inject styles client-side after the module evaluates, with no corresponding `<link>` in the shell. With streaming, the browser starts painting the streamed DOM before those style-injection modules run, so SFC-defined styles flash unstyled briefly.

Workaround: put paint-critical styles in a global CSS file registered via `css: ['~/assets/main.css']`. Global CSS files are emitted as `<link rel="stylesheet">` in the shell `<head>` and apply before body content streams. SFC `<style>` blocks remain fine for component-scoped styling that doesn't gate the initial paint.

Production builds extract all styles to real CSS files (or inline via `features.inlineStyles`), so this only affects `nuxt dev`. Validate streaming visuals against `nuxt build && nuxt preview`.

</warning>

<warning>

**Production FOUC for nested async components:** the renderer inlines route CSS in a chunk sent straight after the shell. It can only inline the styles for components whose modules are already registered at that point: the page, the layout, and any async component placed directly inside a `<Suspense>` boundary (Vue instantiates those eagerly when render begins).

An async component that is rendered *inside another async component* is instantiated only once its parent resolves, after the first chunk has streamed. Its SFC `<style>` misses the post-shell styles chunk and is emitted in the closing HTML instead, behind the component's own DOM. The browser paints that component unstyled until the final chunk arrives.

Avoid it by keeping paint-critical styling out of deeply nested async components:

- Put styles that gate the initial paint in a global CSS file (`css: ['~/assets/main.css']`); these reach the shell `<head>`.
- Style with utility classes (Tailwind, UnoCSS): utility CSS lives in the entry stylesheet, not per-component `<style>` blocks.
- Keep async components that own paint-critical `<style>` directly under a `<Suspense>` boundary rather than nested behind another async parent.
- Or opt the route out of streaming with `routeRules: { '/path': { streaming: false } }`.

Non-paint-critical scoped styles on nested async components are fine: the brief flash only matters for above-the-fold content.

</warning>
