---
title: "升级指南"
description: "了解如何升级到最新的 Nuxt 版本。"
canonical_url: "https://nuxt.zhcndoc.com/docs/4.x/getting-started/upgrade"
---
# 升级指南

> 了解如何升级到最新的 Nuxt 版本。

## 升级 Nuxt

### 最新版本

要将 Nuxt 升级到[最新版本](https://github.com/nuxt/nuxt/releases)，请使用 `nuxt upgrade` 命令。

::code-group{sync="pm"}
```bash [npm]
npx nuxt upgrade
```

```bash [yarn]
yarn nuxt upgrade
```

```bash [pnpm]
pnpm nuxt upgrade
```

```bash [bun]
bun x nuxt upgrade
```

```bash [deno]
deno x nuxt upgrade
```
::

### 夜版发布渠道

要使用最新的 Nuxt 构建版本，并在功能发布前进行测试，请阅读[夜版发布渠道](https://nuxt.zhcndoc.com/docs/4.x/guide/going-further/nightly-release-channel)指南。

## 测试 Nuxt 5

Nuxt 5 **目前正在开发中**。从 Nuxt 4.2+ 开始，你可以通过将 `future.compatibilityVersion` 设置为 `5` 来测试它的许多破坏性变更：

```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
  future: {
    compatibilityVersion: 5,
  },
})
```

Nuxt 配置中的各项默认值随后会改变，以启用 Nuxt 5 的行为。在 Nuxt 4 中，这包括：

- **Vite Environment API**：启用 [`experimental.viteEnvironmentApi`](https://nuxt.zhcndoc.com/docs/4.x/guide/going-further/experimental-features#viteenvironmentapi)，从而改变 Vite 插件的注册和配置方式
- **区分大小写的路由**：页面路由会严格匹配 URL 的大小写，与 Nitro 保持一致，因此 `/About` 不再匹配 `pages/about.vue`
- **规范化页面名称**：页面组件名称[与其路由名称匹配](https://nuxt.zhcndoc.com/docs/4.x/guide/going-further/experimental-features#normalizepagenames)，确保 `<KeepAlive>` 行为一致
- **可序列化的页面 meta**：[`experimental.extractSerializablePageMeta`](https://nuxt.zhcndoc.com/docs/4.x/guide/going-further/experimental-features#extractserializablepagemeta) 会在构建时提取可静态分析的 `definePageMeta` 值
- **客户端负载提取**：[`experimental.payloadExtraction`](https://nuxt.zhcndoc.com/docs/4.x/guide/going-further/experimental-features#payloadextraction) 会变为 `'client'`，将负载内联到预渲染页面的 HTML 中
- **`clearNuxtState` 重置为默认值**：`clearNuxtState` 会将状态重置为 [`useState`](https://nuxt.zhcndoc.com/docs/4.x/api/composables/use-state) 的初始值，而不是将其设为 `undefined`
- **非异步的 `callHook`**：`callHook` 可能返回 `void`，而不总是返回 `Promise`
- **`navigateTo` 提前返回**：[`experimental.navigateToEarlyReturn`](https://nuxt.zhcndoc.com/docs/4.x/guide/going-further/experimental-features#navigatetoearlyreturn) 会阻止中间件在重定向后继续执行
- **仅限服务器端的 head 组合式函数**：`useServerHead`、`useServerHeadSafe` 和 `useServerSeoMeta` 不再自动导入，`unhead.legacy` 会被忽略，Unhead 的旧版插件不再注册，并且始终启用 Capo 排序
- **默认关闭 PostCSS**：除非你在 `postcss.plugins` 中启用 `autoprefixer` 和 `cssnano`，否则不会再配置它们
- **带键的组合式函数需要 `source`**：没有字符串 `source` 的 [`optimization.keyedComposables`](https://nuxt.zhcndoc.com/docs/4.x/api/nuxt-config#keyedcomposables) 条目，不再通过其自动导入项进行匹配
- **注释节点占位符**：仅客户端组件使用[注释节点而不是 `<div>`](https://nuxt.zhcndoc.com/docs/4.x/guide/going-further/experimental-features#clientnodeplaceholder)作为 SSR 占位符
- **类型化页面**：启用 [`experimental.typedPages`](https://nuxt.zhcndoc.com/docs/4.x/guide/going-further/experimental-features#typedpages)，以支持经过类型检查的路由
- **错误响应格式**：启用 `experimental.inlineErrorRendering`，因此页面错误会返回错误页面，服务器路由错误会返回 JSON，且[不受请求标头影响](https://nuxt.com/docs/5.x/getting-started/upgrade#error-format-depends-on-where-the-error-is-thrown)
- **类型化 `$fetch`**：[`experimental.routeTypedFetch`](https://nuxt.zhcndoc.com/docs/4.x/guide/going-further/experimental-features#routetypedfetch) 会根据生成的路由类型为 `$fetch` 和 `useFetch` 提供类型
- **关闭 Nitro 自动导入**：服务器代码会[显式导入辅助函数](https://nuxt.com/docs/5.x/getting-started/upgrade#server-auto-imports-are-now-opt-in)，例如从 [`nuxt/server`](#moving-to-nuxtserver) 导入，而不是依赖自动导入兼容层。设置 `experimental.nitroAutoImports: true` 可继续使用自动导入
- **禁用 Vue Options API**：Options API 会从客户端包中编译移除，以减小其体积
- **更严格的副作用导入检查**：生成的 `tsconfig.json` 会启用 `noUncheckedSideEffectImports`
- **忽略 TypeScript `baseUrl`**：生成的 TypeScript 配置不再使用 `compilerOptions.baseUrl` 解析 Nuxt 别名

::note
此列表说明了该标志在 Nuxt 4 中会更改哪些内容。Nuxt 5 本身还包含此标志无法启用的更多变更，例如升级到 Nitro v3。
::

每项变更的迁移步骤，以及此次升级带来的其他所有内容，都记录在 [Nuxt 5 升级指南](https://nuxt.com/docs/5.x/getting-started/upgrade)中。

## 迁移到 `nuxt/server`

从 Nuxt 4.6 开始，服务器代码可以从 [`nuxt/server`](https://nuxt.zhcndoc.com/docs/4.x/guide/going-further/server-imports) 而不是 h3 导入处理程序和辅助函数，因此同一个文件可以在 Nuxt 4（`nitropack` v2 和 h3 v1）以及 [Nuxt 5](https://nuxt.com/docs/5.x/getting-started/upgrade)（Nitro v3 和 h3 v2）上运行。

在 Nuxt 4.6 中，请从 `nuxt/server` 显式导入 `defineEventHandler` 和辅助函数。同名的自动导入项属于 h3 v1，而 h3 的 `defineEventHandler` 传递给处理程序的事件，与 `nuxt/server` 辅助函数读取的事件不同。

```diff [server/api/hello.ts]
+ import { defineEventHandler, getQuery } from 'nuxt/server'
+
 export default defineEventHandler((event) => {
   const { name } = getQuery<{ name?: string }>(event)
   return { message: `Hello, ${name ?? 'world'}!` }
 })
```

在 Nuxt 5 中，这些同名项会从 `nuxt/server` 自动导入，因此在 Nuxt 4.6 中编写的显式导入可以继续原样使用。

### 与 h3 v1 的差异

| h3 v1                                                                                                | `nuxt/server`                                                                                                                                                        |
| ---------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `sendRedirect(event, location)` 会发送响应。                                                               | `sendRedirect(event, location)` 会返回响应体：`return sendRedirect(event, '/login')`。                                                                                       |
| `getRouterParam(event, name)` 和 `getRouterParams(event)` 会返回解码后的值。                                   | 它们会返回 URL 中显示的值。传入 `{ decode: true }` 可对其进行解码。                                                                                                                       |
| `handleCors(event, options)` 在处理预检请求时会返回 `true`。                                                     | `handleCors(event, options)` 会返回应由处理程序返回的预检 `Response`，或 `false`：`const preflight = handleCors(event, options); if (preflight) return preflight`。                    |
| `handleCors` 在运行时接受单个来源字符串。                                                                          | `origin` 可以是 `'*'`、`'null'`、来源或模式数组，也可以是函数；单个来源应包装在数组中。`maxAge` 是以秒为单位的字符串（例如 `'86400'`），或 `false`。请从 `nuxt/server` 导入 `CorsOptions` 类型。                             |
| `readValidatedBody` 和 `getValidatedQuery` 接受一个校验函数。                                                  | 它们接受一个 [Standard Schema](https://standardschema.dev) 或一个函数。                                                                                                          |
| `createError({ statusCode, statusMessage })`                                                         | `createError({ status, statusText })`                                                                                                                                |
| `isError(error)`                                                                                     | `isNuxtError(error)`                                                                                                                                                 |
| h3 的 `useSession`、`getSession`、`updateSession` 和 `clearSession` 会使用你传入的 `password` 密封一个 `h3` Cookie。 | 它们会密封一个 `nuxt-session` Cookie，默认使用从 `appSecret` 派生的密钥。两者不能互换：其中一种签发的会话无法由另一种解封。                                                                                      |
| `useRuntimeConfig(event)`                                                                            | `useRuntimeConfig()`。没有按请求读取配置的形式。                                                                                                                                   |
| 全局 `$fetch`                                                                                          | `nuxt/server` 不提供此功能。使用 `serverFetch(event, path)` 在进程内调用应用路由，它会返回 `Response`。对于其他 URL，原生 `fetch` 在非 2xx 状态时不会抛错，也不会解析 JSON：请检查 `response.ok` 并调用 `response.json()`。 |
| `event.path`                                                                                         | `getRequestURL(event).pathname` 或 `event.url.pathname`。                                                                                                              |
| `event.node`                                                                                         | `nuxt/server` 不提供此属性。请使用 `event.req` 和 `event.res`。                                                                                                                  |
| `getResponseHeader`、`setResponseHeader`、`appendResponseHeader`、`removeResponseHeader`                | `event.res.headers.get()`、`.set()`、`.append()`、`.delete()`                                                                                                           |
| `getMethod(event)`、`isMethod(event, method)`、`assertMethod(event, method)`                           | 比较 `event.req.method`。                                                                                                                                               |
| `readMultipartFormData(event)`                                                                       | `await event.req.formData()`。                                                                                                                                        |
| `readRawBody(event)`、`getRequestWebStream(event)`                                                    | 使用 `await event.req.arrayBuffer()`（或 `.text()`）读取原始请求体，使用 `event.req.body` 读取流。                                                                                      |
| `sanitizeStatusCode`                                                                                 | 不提供。                                                                                                                                                                 |

### `nuxt/server` 之外的辅助函数

对于需要使用 `nuxt/server` 未提供的 h3 或 Nitro 辅助函数（例如 `readMultipartFormData`、`proxyRequest`、`useStorage` 或 `defineCachedEventHandler`）的处理程序，请继续从 `h3`（或 `nitropack/runtime`）为该处理程序导入 `defineEventHandler` 和这些辅助函数。这样编写的代码与服务器运行时绑定，需要在 Nuxt 5 中重新检查。

```ts [server/api/upload.post.ts]
import { defineEventHandler, readMultipartFormData } from 'h3'

export default defineEventHandler(async (event) => {
  const parts = await readMultipartFormData(event)
  return { received: parts?.length ?? 0 }
})
```

### `NUXT_E8012`

当 `nuxt/server` 的辅助函数（例如 `readValidatedBody`、`getValidatedQuery`、`handleCors` 或 `useSession`）收到 h3 事件时，请求会失败并返回一条以 `[NUXT_E8012]` 开头的 `500` 错误消息。这意味着该处理程序是用 h3 的 `defineEventHandler` 定义的（通常是自动导入的那个）。请在与辅助函数相同的文件中从 `nuxt/server` 导入 `defineEventHandler`。

### 应用密钥

`nuxt/server` 的会话辅助函数和 `deriveSecret` 会读取 `runtimeConfig.appSecret`，你可以通过 `NUXT_APP_SECRET` 设置该值。在 Nuxt 4 中，`nitropack` v2 会解析环境变量覆盖值，因此仅由数字组成的密钥会被解析为数字；请参阅[应用密钥](https://nuxt.zhcndoc.com/docs/4.x/guide/going-further/runtime-config#application-secret)，了解如何避免此问题。

## 迁移到 Nuxt 4

Nuxt 4 包含了重要的改进和变更。本指南将帮助你将现有的 Nuxt 3 应用迁移到 Nuxt 4。

首先，升级到 Nuxt 4：

::code-group{sync="pm"}
```bash [npm]
npm install nuxt@^4.0.0
```

```bash [yarn]
yarn add nuxt@^4.0.0
```

```bash [pnpm]
pnpm add nuxt@^4.0.0
```

```bash [bun]
bun add nuxt@^4.0.0
```

```bash [deno]
deno add npm:nuxt@^4.0.0
```
::

升级后，大多数 Nuxt 4 行为现在都是默认行为。不过，如果你在迁移期间需要保持向后兼容，仍然可以配置某些功能。

以下部分详细介绍升级到 Nuxt 4 时的主要变更和所需的迁移步骤。

下文记录了具有破坏性或较为重要的变更，以及相应的迁移步骤和可用的配置选项。

### 使用 Codemods 迁移

为了简化升级流程，我们与 [Codemod](https://github.com/codemod/codemod) 团队合作，使用一些开源 codemod 自动完成了许多迁移步骤。

::note
如果遇到任何问题，请使用 `npx codemod feedback` 向 Codemod 团队报告 🙏
::

如需查看 Nuxt 4 codemod 的完整列表、每个 codemod 的详细信息及其源代码，以及多种运行方式，请访问 [Codemod Registry](https://app.codemod.com/registry)。

你可以使用以下 `codemod` recipe 运行本指南中提到的所有 codemod：

::code-group
```bash [npm]
# Using pinned version due to https://github.com/codemod/codemod/issues/1710
npx codemod@0.18.7 nuxt/4/migration-recipe
```

```bash [yarn]
# Using pinned version due to https://github.com/codemod/codemod/issues/1710
yarn dlx codemod@0.18.7 nuxt/4/migration-recipe
```

```bash [pnpm]
# Using pinned version due to https://github.com/codemod/codemod/issues/1710
pnpm dlx codemod@0.18.7 nuxt/4/migration-recipe
```

```bash [bun]
# Using pinned version due to https://github.com/codemod/codemod/issues/1710
bun x codemod@0.18.7 nuxt/4/migration-recipe
```

```bash [deno]
# Using pinned version due to https://github.com/codemod/codemod/issues/1710
deno x codemod@0.18.7 nuxt/4/migration-recipe
```
::

此命令会依次执行所有 codemod，你可以选择取消不想运行的 codemod。下文也会列出每个 codemod 及其对应的变更，你可以单独运行它们。

### 新的目录结构

🚦 **影响级别**：重要

Nuxt 现在默认使用新的目录结构，同时保持向后兼容（因此，如果 Nuxt 检测到你使用的是旧结构，例如顶层的 `app/pages/` 目录，就不会应用此新结构）。

👉 [查看完整 RFC](https://github.com/nuxt/nuxt/issues/26444)

#### 变更内容

- 新的 Nuxt 默认 `srcDir` 为 `app/`，大多数内容都将从此目录解析。
- `serverDir` 现在默认指向 `<rootDir>/server`，而不是 `<srcDir>/server`
- `layers/`、`modules/` 和 `public/` 默认相对于 `<rootDir>` 解析
- 如果使用 [Nuxt Content v2.13+](https://github.com/nuxt/content/pull/2649)，`content/` 将相对于 `<rootDir>` 解析
- 新增了 `dir.app`，用于查找 `router.options.ts` 和 `spa-loading-template.html`；默认值为 `<srcDir>/`
- 新增了 [`shared/`](https://nuxt.zhcndoc.com/docs/4.x/directory-structure/shared) 目录，用于 Vue 应用与 Nitro 服务器之间共享代码，并为 `shared/utils/` 和 `shared/types/` 提供自动导入

<details></details>

<summary>
v4 文件夹结构示例。
</summary>

```sh
.output/
.nuxt/
app/
  assets/
  components/
  composables/
  layouts/
  middleware/
  pages/
  plugins/
  utils/
  app.config.ts
  app.vue
  router.options.ts
content/
layers/
modules/
node_modules/
public/
shared/
  types/
  utils/
server/
  api/
  middleware/
  plugins/
  routes/
  utils/
nuxt.config.ts
```

::note
在此新结构中，`~` 别名现在默认指向 `app/` 目录（即你的 `srcDir`）。这意味着 `~/components` 会解析为 `app/components/`，`~/pages` 会解析为 `app/pages/`，以此类推。
::

👉 如需了解更多详情，请参阅[实现此变更的 PR](https://github.com/nuxt/nuxt/pull/27029)。

#### 变更原因

1. **性能** —— 将所有代码放在仓库根目录，会导致 `.git/` 和 `node_modules/` 文件夹被文件系统监视程序扫描或包含，从而引发问题；在非 Mac OS 系统上，这可能会明显延迟启动。
2. **IDE 类型安全** —— `server/` 和应用的其余部分运行在两个完全不同的上下文中，拥有不同的全局导入。确保 `server/` 不在应用其余部分所在的同一文件夹中，是确保 IDE 提供良好自动补全的重要第一步。

::video-accordion{title="观看 Vue School 关于新目录结构的视频" videoId="1031028378" platform="vimeo"}
::

#### 迁移步骤

1. 创建一个名为 `app/` 的新目录。
2. 将 `assets/`、`components/`、`composables/`、`app/layouts/`、`app/middleware/`、`app/pages/`、`app/plugins/` 和 `utils/` 文件夹，以及 `app.vue`、`error.vue`、`app.config.ts` 移到该目录下。如果你有 `app/router-options.ts` 或 `app/spa-loading-template.html`，这些路径保持不变。
3. 确保 `nuxt.config.ts`、`content/`、`layers/`、`modules/`、`public/`、`shared/` 和 `server/` 文件夹留在 `app/` 文件夹之外，即项目根目录中。
4. 请记得更新任何第三方配置文件，使其适配新的目录结构，例如 `tailwindcss` 或 `eslint` 配置（如果需要；`@nuxtjs/tailwindcss` 应能自动正确配置 `tailwindcss`）。

::tip
你可以运行 `npx codemod@latest nuxt/4/file-structure` 自动完成此迁移
::

但是，迁移并非必需。如果你希望保留当前的文件夹结构，Nuxt 应该能够自动检测到它。（如果没有，请提交 issue。）唯一的例外是你已经设置了自定义 `srcDir`。在这种情况下，请注意，你的 `modules/`、`public/`、`shared/` 和 `server/` 文件夹会从 `rootDir` 而不是自定义 `srcDir` 解析。如果有需要，可以通过配置 `dir.modules`、`dir.public` 和 `serverDir` 覆盖此行为。

你也可以通过以下配置强制使用 v3 文件夹结构：

```ts [nuxt.config.ts]
export default defineNuxtConfig({
  // This reverts the new srcDir default from `app` back to your root directory
  srcDir: '.',
  // This specifies the directory prefix for `router.options.ts` and `spa-loading-template.html`
  dir: {
    app: 'app',
  },
})
```

### 单例数据获取层

🚦 **影响级别**：中等

#### 变更内容

Nuxt 的数据获取系统（`useAsyncData` 和 `useFetch`）进行了大幅重组，以提升性能和一致性：

1. **相同 key 共享 refs**：现在，所有使用相同 key 调用 `useAsyncData` 或 `useFetch` 的地方都会共享相同的 `data`、`error` 和 `status` refs。这意味着，所有带有显式 key 的调用都不能使用互相冲突的 `deep`、`transform`、`pick`、`getCachedData` 或 `default` 选项。
2. **更灵活地控制 `getCachedData`**：现在每次获取数据时都会调用 `getCachedData` 函数，即使这是由 watcher 或调用 `refreshNuxtData` 触发的。（以前在这些情况下总是会获取新数据，而不会调用此函数。）为了更好地控制何时使用缓存数据、何时重新获取数据，该函数现在会收到一个包含请求触发原因的上下文对象。
3. **支持响应式 key**：现在可以使用计算 refs、普通 refs 或 getter 函数作为 key，从而支持自动重新获取数据（并单独存储数据）。
4. **数据清理**：当最后一个使用 `useAsyncData` 获取数据的组件卸载时，Nuxt 会移除这些数据，以避免内存持续增长。

#### 变更原因

这些变更旨在改善内存使用情况，并提高多个 `useAsyncData` 调用之间加载状态的一致性。

#### 迁移步骤

1. **检查不一致的选项**：检查是否有组件使用相同的 key，却指定了不同的选项或获取函数。
   ```ts
   // This will now trigger a warning
   const { data: users1 } = useAsyncData('users', () => $fetch('/api/users'), { deep: false })
   const { data: users2 } = useAsyncData('users', () => $fetch('/api/users'), { deep: true })
   ```

   你可以考虑将共享显式 key（且带有自定义选项）的 `useAsyncData` 调用提取到它们自己的组合式函数中：
   ```ts [app/composables/useUserData.ts]
   export function useUserData (userId: string) {
     return useAsyncData(
       `user-${userId}`,
       () => fetchUser(userId),
       {
         deep: true,
         transform: user => ({ ...user, lastAccessed: new Date() }),
       },
     )
   }
   ```
2. **更新 `getCachedData` 实现**：
   ```diff
   useAsyncData('key', fetchFunction, {
   -  getCachedData: (key, nuxtApp) => {
   -    return cachedData[key]
   -  }
   +  getCachedData: (key, nuxtApp, ctx) => {
   +    // ctx.cause - can be 'initial' | 'refresh:hook' | 'refresh:manual' | 'watch'
   +    
   +    // Example: Don't use cache on manual refresh
   +    if (ctx.cause === 'refresh:manual') return undefined
   +    
   +    return cachedData[key]
   +  }
   })
   ```

或者，你目前可以通过以下配置禁用此行为：

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

### 修正图层中的模块加载顺序

🚦 **影响级别**：最小

#### 变更内容

使用 [Nuxt 图层](https://nuxt.zhcndoc.com/docs/4.x/guide/going-further/layers)时，模块的加载顺序已得到修正。此前，项目根目录中的模块会在扩展图层中的模块之前加载，这与预期行为相反。

现在，模块会按正确顺序加载：

1. **首先加载图层模块**（按扩展顺序——较深的图层优先）
2. **最后加载项目模块**（优先级最高）

这会影响：

- `nuxt.config.ts` 中 `modules` 数组定义的模块
- 从 `modules/` 目录自动发现的模块

#### 变更原因

此变更确保：

- 扩展图层的优先级低于使用它的项目
- 模块执行顺序符合直观的图层继承模式
- 在多图层配置中，模块配置和钩子能按预期工作

#### 迁移步骤

**大多数项目无需更改**，因为此次变更修正了加载顺序，使其符合预期行为。

但是，如果你的项目依赖之前错误的顺序，可能需要：

1. **检查模块依赖**：检查是否有模块依赖特定的加载顺序
2. **调整模块配置**：如果模块配置是为了适配错误的顺序而设置的
3. **充分测试**：确保所有功能在修正后的顺序下都能按预期工作

新的正确顺序示例：

```ts
// Layer: my-layer/nuxt.config.ts
export default defineNuxtConfig({
  modules: ['layer-module-1', 'layer-module-2'],
})

// Project: nuxt.config.ts
export default defineNuxtConfig({
  extends: ['./my-layer'],
  modules: ['project-module-1', 'project-module-2'],
})

// Loading order (corrected):
// 1. layer-module-1
// 2. layer-module-2
// 3. project-module-1 (can override layer modules)
// 4. project-module-2 (can override layer modules)
```

如果你因为需要注册钩子而遇到模块顺序依赖问题，请考虑在需要调用钩子的模块中使用 [`modules:done` 钩子](https://nuxt.zhcndoc.com/docs/4.x/guide/modules/recipes-advanced)。此钩子会在所有其他模块加载后运行，因此可以安全使用。

👉 如需了解更多详情，请参阅 [PR #31507](https://github.com/nuxt/nuxt/pull/31507) 和 [issue #25719](https://github.com/nuxt/nuxt/issues/25719)。

### 路由元数据去重

🚦 **影响级别**：最小

#### 变更内容

你可以使用 `definePageMeta` 设置一些路由元数据，例如 `name`、`path` 等。以前，这些信息既可以通过路由访问，也可以通过路由元数据访问（例如 `route.name` 和 `route.meta.name`）。

现在，它们只能通过路由对象访问。

#### 变更原因

这是默认启用 `experimental.scanPageMeta` 的结果，也是一项性能优化。

#### 迁移步骤

迁移应该很简单：

```diff
  const route = useRoute()
  
- console.log(route.meta.name)
+ console.log(route.name)
```

### 规范化组件名称

🚦 **影响级别**：中等

Vue 现在会生成与 Nuxt 命名模式相匹配的组件名称。

#### 变更内容

默认情况下，如果你没有手动设置组件名称，Vue 会将其设为与组件文件名一致的名称。

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

此时，Vue 认为组件名为 `MyComponent`。如果你想和 `<KeepAlive>` 搭配使用，或在 Vue DevTools 中识别该组件，需要使用这个名称。

但 Nuxt 的自动导入会使用 `SomeFolderMyComponent` 作为名称。

此次变更后，这两个名称将保持一致，Vue 生成的组件名将匹配 Nuxt 的命名模式。

#### 迁移步骤

确保你在使用 `@vue/test-utils` 的 `findComponent` 进行测试，以及任何依赖组件名的 `<KeepAlive>` 中使用更新后的名称。

或者，你现在可以通过以下配置禁用此行为：

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

### Unhead v2

🚦 **影响级别**：最小

#### 变更内容

用于生成 `<head>` 标签的 [Unhead](https://unhead.unjs.io/) 已升级到版本 2。尽管大多数内容都兼容，但底层 API 有若干破坏性变更：

- 移除属性：`vmid`、`hid`、`children`、`body`
- 不再支持 Promise 类型的输入
- 默认使用 Capo.js 对标签进行排序

#### 迁移步骤

上述变更对应用影响较小。

如果遇到问题，请检查：

- 你是否使用了已移除的属性

```diff
useHead({
  meta: [{ 
    name: 'description', 
    // meta 标签无需 vmid 或 key    
-   vmid: 'description' 
-   hid: 'description'
  }]
})
```

- 如果你使用了[模板参数](https://unhead.unjs.io/docs/head/guides/plugins/template-params)或[别名标签排序](https://unhead.unjs.io/docs/head/guides/plugins/alias-sorting)，需要显式启用相关插件。

```ts
import { AliasSortingPlugin, TemplateParamsPlugin } from '@unhead/vue/plugins'

export default defineNuxtPlugin({
  setup () {
    const unhead = injectHead()
    unhead.use(TemplateParamsPlugin)
    unhead.use(AliasSortingPlugin)
  },
})
```

虽然并非必须，但建议将所有从 `@unhead/vue` 导入的内容改为从 `#imports` 或 `nuxt/app` 导入。

```diff
-import { useHead } from '@unhead/vue'
+import { useHead } from '#imports'
```

如果仍有问题，可通过启用 `head.legacy` 配置回退到 v1 行为：

```ts
export default defineNuxtConfig({
  unhead: {
    legacy: true,
  },
})
```

### SPA 加载屏幕的新 DOM 位置

🚦 **影响级别**：最小

#### 变更内容

客户端渲染页面（`ssr: false`）时，加载屏幕（默认值为 `~/app/spa-loading-template.html`；在 Nuxt 4 中改为 `~/spa-loading-template.html`）渲染在 Nuxt 应用根节点内：

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

现在，加载屏幕默认渲染在 Nuxt 应用根节点旁边：

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

#### 变更原因

这样能确保加载屏幕在 Vue 应用的 Suspense 解析完前一直存在，避免出现闪白。

#### 迁移步骤

如果你曾用 CSS 或 `document.querySelector` 定位加载屏幕元素，请更新选择器。你可以利用新的 `app.spaLoaderTag` 和 `app.spaLoaderAttrs` 配置来辅助定位。

或者，你可以通过以下配置恢复到旧行为：

```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
  experimental: {
    spaLoadingTemplateLocation: 'within',
  },
})
```

### 解析后的 `error.data`

🚦 **影响级别**：最小

之前抛出的错误对象中若有 `data` 属性，未自动解析。现在已自动解析，并可通过错误对象访问。虽然这是一项修复，但如果你依赖旧行为并手动解析，这将属于破坏性变更。

#### 迁移步骤

更新自定义 `error.vue`，删除对 `error.data` 的额外解析：

```diff
  <script setup lang="ts">
  import type { NuxtError } from '#app'

  const props = defineProps({
    error: Object as () => NuxtError
  })

- const data = JSON.parse(error.data)
+ const data = error.data
  </script>
```

### 更细粒度的内联样式

🚦 **影响级别**：中等

#### 变更内容

Nuxt 现在仅对 Vue 组件的样式进行内联，而不再内联全局 CSS。

之前，Nuxt 会内联包括全局样式在内的所有 CSS，并移除针对单独 CSS 文件的 `<link>`。现在仅对 Vue 组件相关的 CSS 内联（此前这些样式会产生独立的 CSS Chunk）。这能更好地平衡以下目标：减少网络请求数量（首屏加载时不会为每个页面或组件单独请求 `.css` 文件）、提升全局 CSS 缓存效率，以及减少初始请求体积。

#### 迁移步骤

此特性可通过配置完全控制。你可以通过设置 `inlineStyles: true` 恢复之前的行为，即对全局 CSS 也进行内联：

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

### 解析后扫描页面元数据

🚦 **影响级别**：最小

#### 变更内容

我们现在会在调用 `pages:extend` 钩子之后扫描页面元数据（在 `definePageMeta` 中定义），而不是在调用之前。

#### 变更原因

这样做是为了允许扫描用户希望在 `pages:extend` 中添加的页面的元数据。我们仍然提供了一个机会，即在新的 `pages:resolved` 钩子中更改或覆盖页面元数据。

#### 迁移步骤

如果你想覆盖页面元数据，请在 `pages:resolved` 中进行，而不是在 `pages:extend` 中。

```diff
  export default defineNuxtConfig({
    hooks: {
-     'pages:extend'(pages) {
+     'pages:resolved'(pages) {
        const myPage = pages.find(page => page.path === '/')
        myPage.meta ||= {}
        myPage.meta.layout = 'overridden-layout'
      }
    }
  })
```

或者，你可以通过以下方式恢复之前的行为：

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

### 共享预渲染数据

🚦 **影响级别**：中等

#### 变更内容

我们启用了一项此前的实验功能，用于在不同页面之间共享 `useAsyncData` 和 `useFetch` 调用的数据。详见[原始 PR](https://github.com/nuxt/nuxt/pull/24894)。

#### 变更原因

该功能会自动在预渲染页面之间共享负载中的 *数据*。当预渲染使用 `useAsyncData` 或 `useFetch`，并且在不同页面请求相同数据的站点时，能显著提升性能。

例如，如果你的网站在每个页面都需要调用 `useFetch`（比如用于获取菜单导航数据，或 CMS 中的站点设定），这些数据会在首次预渲染使用它的页面时只被请求一次，并缓存起来供其他页面的预渲染使用。

#### 迁移步骤

确保你数据的唯一键总是能解析到相同的数据。例如，如果你使用 `useAsyncData` 请求与特定页面相关的数据，应提供一个唯一匹配该数据的键。（`useFetch` 会自动为你处理）

```ts [app/pages/test/[slug\\].vue]
// 在动态页面（例如 `[slug].vue`）中，这样使用是不安全的，因为路由的 slug 会影响请求的数据，
// 而 Nuxt 无法感知这一点，因为它未反映在 key 中。
const route = useRoute()
const { data } = await useAsyncData(async () => {
  return await $fetch(`/api/my-page/${route.params.slug}`)
})
// 正确做法是使用唯一标识请求数据的 key。
const { data } = await useAsyncData(route.params.slug, async () => {
  return await $fetch(`/api/my-page/${route.params.slug}`)
})
```

或者，你可以禁用此功能：

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

### `useAsyncData` 和 `useFetch` 中默认的 `data` 和 `error` 值

🚦 **影响级别**：最小

#### 变更内容

从 `useAsyncData` 返回的 `data` 和 `error` 对象默认现在为 `undefined`。

#### 变更原因

之前 `data` 初始化为 `null`，但在 `clearNuxtData` 中被重置为 `undefined`。`error` 初始化为 `null`。此更改旨在提升一致性。

#### 迁移步骤

如果你之前检查过 `data.value` 或 `error.value` 是否为 `null`，可以改为检查其是否为 `undefined`。

::tip
你可以通过运行 `npx codemod@latest nuxt/4/default-data-error-value` 自动完成此步骤
::

### 移除调用 `useAsyncData` 和 `useFetch` 的 `refresh` 时 `dedupe` 选项的布尔值支持

🚦 **影响级别**：最小

#### 变更内容

之前调用 `refresh` 时可以传递 `dedupe: boolean`。它们分别是 `cancel`（`true`）和 `defer`（`false`）的别名。

```ts twoslash [app/app.vue]
// @errors: 2322
const { refresh } = await useAsyncData(() => Promise.resolve({ message: 'Hello, Nuxt!' }))

async function refreshData () {
  await refresh({ dedupe: true })
}
```

#### 变更原因

为使语义更清晰，我们移除了这些布尔别名。

该问题产生于为 `useAsyncData` 添加 `dedupe` 选项时，布尔值最终变成了相反的含义。

`refresh({ dedupe: false })` 表示 **不取消现有请求以优先执行该请求**。但在 `useAsyncData` 选项中传递 `dedupe: true` 表示 **若已有挂起请求则不发起新请求**。（详见[PR](https://github.com/nuxt/nuxt/pull/24564#pullrequestreview-1764584361)）

#### 迁移步骤

迁移很简单：

```diff
  const { refresh } = await useAsyncData(async () => ({ message: 'Hello, Nuxt 3!' }))
  
  async function refreshData () {
-   await refresh({ dedupe: true })
+   await refresh({ dedupe: 'cancel' })

-   await refresh({ dedupe: false })
+   await refresh({ dedupe: 'defer' })
  }
```

::tip
你可以通过运行 `npx codemod@latest nuxt/4/deprecated-dedupe-value` 自动完成此步骤
\:::

### 在 `useAsyncData` 和 `useFetch` 中清空 `data` 时尊重默认值

🚦 **影响级别**：最小

#### 变更内容

如果你为 `useAsyncData` 提供了自定义的 `default` 值，那么在调用 `clear` 或 `clearNuxtData` 时，将会使用该默认值重置，而不仅仅是简单地置为空。

#### 变更原因

用户通常会设置合适的空值（如空数组），以避免在遍历时检查 `null` 或 `undefined`。清空数据时应尊重这一设定。

### 清空 `useState` 时尊重默认值

🚦 **影响级别**：最小

#### 变更内容

当设置 `compatibilityVersion: 5` 时，`clearNuxtState` 会将状态重置为由 `useState` 的 `init` 函数提供的初始值，而不是将其设置为 `undefined`。这使得 `clearNuxtState` 的行为与已重置默认值的 `clearNuxtData` 保持一致。

#### 变更原因

当 `clearNuxtState` 将状态设为 `undefined` 时，依赖该状态的组合式函数可能会崩溃，因为它们期望状态始终具有有效形态（例如，不能对 `undefined` 访问属性）。重置为 `init` 提供的值可确保状态始终有可用的默认值。

#### 迁移步骤

如果你依赖 `clearNuxtState` 把状态设置为 `undefined`，可以显式传递 `{ reset: false }`：

```diff
- clearNuxtState('myKey')
+ clearNuxtState('myKey', { reset: false })
```

或者，你可以通过以下配置恢复之前的行为：

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

你也可以在不设置 `compatibilityVersion: 5` 的情况下，当前选择启用此行为：

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

### `useAsyncData` 和 `useFetch` 中 `pending` 值的对齐

🚦 **影响级别**：中等

`useAsyncData`、`useFetch`、`useLazyAsyncData` 和 `useLazyFetch` 返回的 `pending` 现在是计算属性，仅当 `status` 也处于挂起状态时才为 `true`。

#### 变更内容

当传入 `immediate: false` 时，`pending` 会在首次请求发起之前保持为 `false`，这与之前的行为不同（之前首次请求前 `pending` 总是为 `true`）。

#### 变更原因

此变更使 `pending` 的含义与 `status` 属性一致：当请求进行时，两者均为挂起状态。

#### 迁移步骤

如果你依赖 `pending` 属性，请确保逻辑考虑到新行为：`pending` 仅在 `status` 也处于挂起时为 `true`。

```diff
  <template>
-   <div v-if="!pending">
+   <div v-if="status === 'success'">
      <p>Data: {{ data }}</p>
    </div>
    <div v-else>
      <p>Loading...</p>
    </div>
  </template>
  <script setup lang="ts">
  const { data, pending, execute, status } = await useAsyncData(() => fetch('/api/data'), {
    immediate: false
  })
  onMounted(() => execute())
  </script>
```

你也可以暂时使用下列配置恢复旧行为：

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

### `useAsyncData` 和 `useFetch` 中 key 变更行为

🚦 **影响级别**：中等

#### 变更内容

在 `useAsyncData` 或 `useFetch` 中使用响应式 key 时，当 key 变化会自动重新请求数据。设置 `immediate: false` 时，`useAsyncData` 只有在数据已请求过一次后，key 变化才会重新请求。

之前，`useFetch` 的行为略有不同：在 key 变化时它会始终请求数据。

现在，`useFetch` 与 `useAsyncData` 行为一致——只有在数据已请求过一次后，key 变化才会重新请求。

#### 变更原因

确保 `useAsyncData` 与 `useFetch` 行为一致，避免意外请求。

若设置了 `immediate: false`，需要调用 `refresh` 或 `execute`，否则数据永远不会被请求。

#### 迁移步骤

此更改通常会提升预期行为，但如果你希望在非立即加载的 `useFetch` 中通过改变 key 或选项自动请求，现在需要手动首次触发。

```diff
  const id = ref('123')
  const { data, execute } = await useFetch('/api/test', {
    query: { id },
    immediate: false
  )
+ watch(id, () => execute(), { once: true })
```

若希望禁用此行为：

```ts
// 或在 Nuxt 配置中全局禁用
export default defineNuxtConfig({
  experimental: {
    alwaysRunFetchOnKeyChange: true,
  },
})
```

### `useAsyncData` 和 `useFetch` 中数据浅层响应性

🚦 **影响级别**：最小

从 `useAsyncData`、`useFetch`、`useLazyAsyncData` 和 `useLazyFetch` 返回的 `data` 对象现在是 `shallowRef`，而非普通的 `ref`。

#### 变更内容

新数据到达时，依赖 `data` 的内容仍然是响应的，因为整个对象会被替换。但如果代码修改了数据对象内部的属性，则不会触发响应性更新。

#### 变更原因

这对于深层嵌套的对象和数组带来**显著**的性能提升，因为 Vue 无需监控每个属性或数组项。且通常 `data` 应该是不可变的。

#### 迁移步骤

大多数情况下无需变化，但如果你依赖数据对象内部的响应性，有两种方案：

1. 在具体的组合式函数调用中开启深层响应性：
   ```diff
   - const { data } = useFetch('/api/test')
   + const { data } = useFetch('/api/test', { deep: true })
   ```
2. 在项目中全局更改默认行为（不推荐）：
   ```ts twoslash [nuxt.config.ts]
   export default defineNuxtConfig({
     experimental: {
       defaults: {
         useAsyncData: {
           deep: true,
         },
       },
     },
   })
   ```

  :::tip
  你可以通过运行 `npx codemod@latest nuxt/4/shallow-function-reactivity` 自动完成此步骤
  :::

### 在 `builder:watch` 中使用绝对路径监听

🚦 **影响级别**：最小

#### 变更内容

Nuxt 的 `builder:watch` 钩子现在发出的路径是绝对路径，而不是相对于项目 `srcDir` 的相对路径。

#### 变更原因

此变更支持监听 `srcDir` 之外的路径，并且更好地支持图层等复杂模式。

#### 迁移步骤

我们已经主动迁移了已知使用该钩子的官方 Nuxt 模块，详见 [issue #25339](https://github.com/nuxt/nuxt/issues/25339)。

如果你是模块作者，并想同时兼容 Nuxt v3 和 Nuxt v4，可以使用下面代码来适配路径：

```diff
+ import { relative, resolve } from 'node:fs'
  // ...
  nuxt.hook('builder:watch', async (event, path) => {
+   path = relative(nuxt.options.srcDir, resolve(nuxt.options.srcDir, path))
    // ...
  })
```

  :::tip
  你可以通过运行 `npx codemod@latest nuxt/4/absolute-watch-path` 自动完成此步骤
  :::

### 移除 `window.__NUXT__` 对象

#### 变更内容

在应用完成 hydration 后，我们移除了全局的 `window.__NUXT__` 对象。

#### 变更原因

这为多应用模式（[#21635](https://github.com/nuxt/nuxt/issues/21635)）铺平道路，并让我们专注于访问 Nuxt 应用数据的唯一途径 —— `useNuxtApp()`。

#### 迁移步骤

这些数据仍然可用，但需要通过 `useNuxtApp().payload` 访问：

```diff
- console.log(window.__NUXT__)
+ console.log(useNuxtApp().payload)
```

### 目录索引扫描

🚦 **影响级别**：中等

#### 变更内容

`app/middleware/` 文件夹内的子文件夹也会扫描其 `index` 文件，这些文件也会注册为项目中间件。

#### 变更原因

Nuxt 会自动扫描多个文件夹，包括 `app/middleware/` 和 `app/plugins/`。

`app/plugins/` 的子文件夹会扫描其 `index` 文件，我们希望在扫描目录时保持行为一致。

#### 迁移步骤

通常无需迁移，但如果你想恢复之前的行为，可以添加钩子来过滤掉这些中间件：

```ts
export default defineNuxtConfig({
  hooks: {
    'app:resolve' (app) {
      app.middleware = app.middleware.filter(mw => !/\/index\.[^/]+$/.test(mw.path))
    },
  },
})
```

### 模板编译更改

🚦 **影响级别**：最小

#### 变化内容

之前 Nuxt 使用 `lodash/template` 编译位于文件系统中的模板，并采用 `.ejs` 文件格式/语法。

此外，我们还提供了一些模板工具函数（如 `serialize`、`importName`、`importSources`），用于在这些模板中生成代码，但现在不再提供。

#### 变更原因

在 Nuxt v3 中，我们使用了更灵活高效的“虚拟”语法和 `getContents()` 函数。

另外，`lodash/template` 曾出现多次安全问题。尽管对 Nuxt 项目的影响不大（用于构建时，而非运行时，且使用可信代码），但仍可能在安全审计中被提及。`lodash` 本身依赖较重，而且大多数项目并未使用它。

最后，在 Nuxt 内置代码序列化函数并不理想。更推荐使用像 [unjs/knitwork](https://github.com/unjs/knitwork) 这样的项目：它们可以作为项目依赖，在其中可以直接报告和修复安全问题，而不需要更新 Nuxt。

#### 迁移步骤

我们已经为使用 EJS 语法的模块提交了 PR，但如果你需要自行处理，有三种兼容方案：

- 将字符串插值逻辑直接写入 `getContents()`。
- 使用自定义函数处理替换，如 [https://github.com/nuxt-modules/color-mode/pull/240。](https://github.com/nuxt-modules/color-mode/pull/240%E3%80%82)
- 将 `es-toolkit/compat`（lodash template 的替代品）作为项目依赖，而非 Nuxt：

```diff
+ import { readFileSync } from 'node:fs'
+ import { template } from 'es-toolkit/compat'
  // ...
  addTemplate({
    fileName: 'appinsights-vue.js'
    options: { /* 一些选项 */ }
-   src: resolver.resolve('./runtime/plugin.ejs'),
+   getContents({ options }) {
+     const contents = readFileSync(resolver.resolve('./runtime/plugin.ejs'), 'utf-8')
+     return template(contents)({ options })
+   },
  })
```

最后，如果你使用了模板工具函数（如 `serialize`、`importName`、`importSources`），可以用 `knitwork` 替代：

```ts
import { genDynamicImport, genImport, genSafeVariableName } from 'knitwork'

const serialize = (data: any) => JSON.stringify(data, null, 2).replace(/"\{(.+)\}"(?=,?$)/gm, r => JSON.parse(r).replace(/^\{(.*)\}$/, '$1'))

const importSources = (sources: string | string[], { lazy = false } = {}) => {
  return toArray(sources).map((src) => {
    if (lazy) {
      return `const ${genSafeVariableName(src)} = ${genDynamicImport(src, { comment: `webpackChunkName: ${JSON.stringify(src)}` })}`
    }
    return genImport(src, genSafeVariableName(src))
  }).join('\n')
}

const importName = genSafeVariableName
```

  :::tip
  你可以通过运行 `npx codemod@latest nuxt/4/template-compilation-changes` 自动完成此步骤
  \::。

  ### 默认 TypeScript 配置更改

  🚦 **影响级别**：最小

  #### 变化内容

  `compilerOptions.noUncheckedIndexedAccess` 的默认值由 `false` 改为 `true`。

  #### 变更原因

  这是之前 [3.12 配置更新](https://github.com/nuxt/nuxt/pull/27485) 的后续。改进默认配置主要遵循 [TotalTypeScript 的建议](https://www.totaltypescript.com/tsconfig-cheat-sheet)。

  #### 迁移步骤

  有两种方案：

  1. 对应用运行类型检查并修复新报错（推荐）。
  2. 在 `nuxt.config.ts` 中覆盖默认配置：
     <!-- @case-police-ignore tsConfig -->
     ```ts
     export default defineNuxtConfig({
       typescript: {
         tsConfig: {
           compilerOptions: {
             noUncheckedIndexedAccess: false,
           },
         },
       },
     })
     ```

  ### TypeScript 配置拆分

  🚦 **影响级别**：最小

  #### 变化内容

  Nuxt 现在会为不同环境生成独立的 TypeScript 配置文件，以提升类型检查体验：

  1. **新的 TypeScript 配置文件**：
     - `.nuxt/tsconfig.app.json` - 用于应用代码（Vue 组件、组合式函数等）
     - `.nuxt/tsconfig.server.json` - 用于服务端代码（Nitro/server 目录）
     - `.nuxt/tsconfig.node.json` - 用于构建时代码（模块、`nuxt.config.ts` 等）
     - `.nuxt/tsconfig.shared.json` - 用于共享代码（如类型和非环境依赖工具）
     - `.nuxt/tsconfig.json` - 旧版配置，保持向后兼容
  2. **向后兼容性**：扩展 `.nuxt/tsconfig.json` 的项目仍将继续正常使用。
  3. **项目引用选项（Project References）**：新项目或需要更好类型检查的项目可以采用。
  4. **针对不同环境的上下文类型检查**：每个环境都拥有适合的编译选项和包含/排除规则。
  5. **新增 `typescript.nodeTsConfig` 选项**：可定制 Node.js 构建时的 TypeScript 配置。

  #### 变更原因

  此变更带来多重好处：

  1. **更好的类型安全**：各环境具有匹配的全局变量和 API 检查。
  2. **提升 IDE 体验**：针对不同代码部分提供更好的智能提示和错误报告。
  3. **清晰分离**：避免服务端代码误引用客户端 API，反之亦然。
  4. **性能提升**：TypeScript 能更高效地检查限定范围的代码。

  例如：你的 `nuxt.config.ts` 中不再支持自动导入，之前 TypeScript 不会报错。IDE 虽能识别服务端目录的 `tsconfig.json`，但类型检查并未反映这一点。此变更补足了这一差异。

  #### 迁移步骤

  **无需迁移**，现有项目保持不变即可继续工作。

  若想利用更好的类型检查，可采用新的项目引用方式：

  1. 更新根目录 `tsconfig.json`，添加项目引用：
     ::::note
     如果你的 `tsconfig.json` 当前有一行 `"extends": "./.nuxt/tsconfig.json"`，**请在添加引用之前删除它**。项目引用与扩展是互斥的。
     ::::
     ```json
     {
       // 如果存在则移除 "extends": "./.nuxt/tsconfig.json"
       "files": [],
       "references": [
         { "path": "./.nuxt/tsconfig.app.json" },
         { "path": "./.nuxt/tsconfig.server.json" },
         { "path": "./.nuxt/tsconfig.shared.json" },
         { "path": "./.nuxt/tsconfig.node.json" }
       ]
     }
     ```
  2. 移除任何手写的服务端 `tsconfig.json`（例如 `server/tsconfig.json`）中对 `.nuxt/tsconfig.server.json` 的继承。
  3. 更新类型检查脚本以使用项目引用构建：
     ```diff
     - "typecheck": "nuxt prepare && vue-tsc --noEmit"
     + "typecheck": "nuxt prepare && vue-tsc -b --noEmit"
     ```
  4. 将所有类型增强代码归入相应上下文：
     - 应用端的增强放入 `app/` 目录。
     - 服务端的增强放入 `server/` 目录。
     - 应用与服务端共享的增强放入 `shared/` 目录。
     ::::warning
     增强代码如果放在 `app/`、`server/` 或 `shared/` 目录之外，将无法兼容新的项目引用机制。
     ::::
  5. 如需定制 TypeScript 配置，在配置中添加：
     <!-- @case-police-ignore tsConfig -->
     ```ts
     export default defineNuxtConfig({
       typescript: {
         // 自定义 tsconfig.app.json
         tsConfig: {
           // ...
         },
         // 自定义 tsconfig.shared.json
         sharedTsConfig: {
           // ...
         },
         // 自定义 tsconfig.node.json
         nodeTsConfig: {
           // ...
         },
       },
       nitro: {
         typescript: {
           // 自定义 tsconfig.server.json
           tsConfig: {
             // ...
           },
         },
       },
     })
     ```
  6. 更新 CI/构建脚本，确保使用新的项目引用方式。

  新配置为可选项：它能提供更好的类型安全与智能提示，同时对现有项目保持完整兼容。

  ### 移除实验性功能配置

  🚦 **影响级别**：最小

  #### 变化内容

  Nuxt 4 不再允许配置以下五个实验性功能：

  - `experimental.treeshakeClientOnly`：将默认开启（自 v3.0 起即默认）
  - `experimental.configSchema`：将默认开启（自 v3.3 起即默认）
  - `experimental.polyfillVueUseHead`：将默认关闭（自 v3.4 起即默认）
  - `experimental.respectNoSSRHeader`：将默认关闭（自 v3.4 起即默认）
  - `experimental.viteEnvironmentApi`：现在始终启用，且不可禁用

  #### 变更原因

  这些选项长期默认开启或关闭，目前没有理由保留为可配置状态。

  #### 迁移步骤

  - `polyfillVueUseHead` 可通过用户插件实现，[示例](https://github.com/nuxt/nuxt/blob/f209158352b09d1986aa320e29ff36353b91c358/packages/nuxt/src/head/runtime/plugins/vueuse-head-polyfill.ts#L10-L11)
  - `respectNoSSRHeader` 可通过服务器中间件实现，[示例](https://github.com/nuxt/nuxt/blob/c660b39447f0d5b8790c0826092638d321cd6821/packages/nuxt/src/core/runtime/nitro/no-ssr.ts#L8-L9)。

  ### 移除顶层 `generate` 配置项

  🚦 **影响级别**：最小

  #### 变化内容

  Nuxt 4 不再支持顶层的 `generate` 配置，包括其所有属性：

  - `generate.exclude` —— 排除预渲染路由
  - `generate.routes` —— 指定预渲染路由

  #### 变更原因

  顶层 `generate` 配置是 Nuxt 2 的遗留配置，现在优先使用 `nitro.prerender` 配置。

  #### 迁移步骤

  使用对应的 `nitro.prerender` 配置替代 `generate`：

  ```diff
  export default defineNuxtConfig({
  - generate: {
  -   exclude: ['/admin', '/private'],
  -   routes: ['/sitemap.xml', '/robots.txt']
  - }
  + nitro: {
  +   prerender: {
  +     ignore: ['/admin', '/private'],
  +     routes: ['/sitemap.xml', '/robots.txt']
  +   }
  + }
  })
  ```

    ::::read-more{to="https://nitro.zhcndoc.com/config#prerender"}
    了解更多 Nitro prerender 配置选项。
    ::::

  ### 标准化页面组件名称

  🚦 **影响级别**：最小

  #### 变化内容

  当设置 `future.compatibilityVersion` 为 `5`（或启用 `experimental.normalizePageNames`）时，页面组件名称将与其路由名称匹配，而不是使用文件名。例如，`pages/foo/index.vue` 组件名将变为 `foo`，而不是 `index`。

  #### 变更原因

  之前，Vue 会根据文件名为组件命名，导致多个页面（如 `pages/foo/index.vue` 和 `pages/bar/index.vue`）都拥有组件名 `index`。这会导致 `<KeepAlive>` 结合 `include`/`exclude` 过滤器时效果不可靠，并且需要在每个页面手动添加 `defineOptions({ name: '...' })`。

  #### 迁移步骤

  如果你依赖当前的组件名（例如在 `<KeepAlive>` 的 `include` 或 `exclude` 列表中），请更新为使用路由名称，而非文件名。

  ```diff
  <template>
    <NuxtPage :keepalive="{
  -   include: ['index']
  +   include: ['foo']
    }" />
  </template>
  ```

  若要禁用此行为：

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

  ## Nuxt 2 与 Nuxt 3+

  下表简要比较了 Nuxt 的三个版本：

  | 特性 / 版本             | Nuxt 2  | Nuxt Bridge | Nuxt 3+  |
  | ------------------- | ------- | ----------- | -------- |
  | Vue                 | 2       | 2           | 3        |
  | 稳定性                 | 😊 稳定   | 😊 稳定       | 😊 稳定    |
  | 性能                  | 🏎 快    | ✈️ 更快       | 🚀 最快    |
  | Nitro 引擎            | ❌       | ✅           | ✅        |
  | ESM 支持              | 🌙 部分支持 | 👍 改进       | ✅        |
  | TypeScript          | ☑️ 选用引入 | 🚧 部分支持     | ✅        |
  | 组合式 API             | ❌       | 🚧 部分支持     | ✅        |
  | 选项式 API             | ✅       | ✅           | ✅        |
  | 组件自动导入              | ✅       | ✅           | ✅        |
  | `<script setup>` 语法 | ❌       | 🚧 部分支持     | ✅        |
  | 自动导入                | ❌       | ✅           | ✅        |
  | webpack             | 4       | 4           | 5        |
  | Vite                | ⚠️ 部分支持 | 🚧 部分支持     | ✅        |
  | Nuxt CLI            | ❌ 旧版    | ✅ (nuxt)    | ✅ (nuxt) |
  | 静态站点支持              | ✅       | ✅           | ✅        |

  ## 从 Nuxt 2 升级到 Nuxt 3+

  迁移指南提供了 Nuxt 2 与 Nuxt 3+ 特性的对比，并指导如何适配你的应用。

    ::::read-more{to="https://nuxt.zhcndoc.com/docs/4.x/migration/overview"}
    查看**从 Nuxt 2 迁移到 Nuxt 3 的指南**。
    ::::

  ## 从 Nuxt 2 升级到 Nuxt Bridge

  若想逐步将 Nuxt 2 应用迁移到 Nuxt 3，可使用 Nuxt Bridge。它是一个兼容层，允许你在 Nuxt 2 中使用部分 Nuxt 3+ 特性，需主动启用。

    ::::read-more{to="https://nuxt.zhcndoc.com/docs/4.x/bridge/overview"}
    **从 Nuxt 2 迁移到 Nuxt Bridge**
    ::::

  \::
  :::
::


## Sitemap

See the full [sitemap](https://nuxt.zhcndoc.com/sitemap.md) for all pages.
