升级指南

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

升级 Nuxt

最新版本

要将 Nuxt 升级到最新版本,请使用 nuxt upgrade 命令。

npx nuxt upgrade

夜版发布渠道

要使用 Nuxt 的最新构建版本并测试新功能(尚未正式发布),请阅读夜版发布渠道指南。

测试 Nuxt 5

Nuxt 5 目前正在开发中。在发布之前,可以测试许多 Nuxt 5 从 Nuxt 版本 4.2+ 的重大更改。

选择使用 Nuxt 5

首先,将 Nuxt 升级到 最新版本

然后,您可以将 future.compatibilityVersion 设置为与 Nuxt 5 的行为一致:

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

当您将 future.compatibilityVersion 设置为 5 时,Nuxt 配置中的默认值将更改为选择 Nuxt v5 的行为,包括:

本节内容在最终发布之前可能会有所更改,因此如果您正在使用 future.compatibilityVersion: 5 测试 Nuxt 5,请定期回来查看。

重大或显著的变化将在下面注明,并附上向后/向前兼容的迁移步骤。

已移除 process.* 类型增强

🚦 影响级别:最小

变更内容

Nuxt 不再为 NodeJS.Process 增强 browserclientdevservertest 属性。为兼容性保留了这些标志位的构建时定义,但 TypeScript 不会将它们视为已知属性。

请优先使用 import.meta.*。这些标志位会在构建时被替换,并且仍然支持 tree-shaking。

迁移步骤

请在你的应用代码、模块和库中替换旧的检查方式:

// 之前
// eslint-disable-next-line nuxt/prefer-import-meta
if (process.server) {
  /* ... */
}

// 之后
if (import.meta.server) {
  /* ... */
}

nuxt/prefer-import-meta ESLint 规则会标记仍在使用的 process.*

区分大小写的路由

🚦 影响等级:最小

变更内容

使用 compatibilityVersion: 5 时,页面路由会与 URL 区分大小写匹配,这与 Nitro 保持一致。例如,/About 将不再匹配 pages/about.vue

迁移步骤

更新链接,使其与对应页面路由的大小写保持一致。若要保持不区分大小写的匹配:

nuxt.config.ts
export default defineNuxtConfig({
  router: {
    options: {
      sensitive: false,
    },
  },
})

迁移到 Vite 环境 API

🚦 影响级别:中等

变化内容

Nuxt 5 迁移到 Vite 6 的新 环境 API,该 API 正式化了环境的概念,并提供了对每个环境配置的更好控制。

之前,Nuxt 使用单独的客户端和服务器 Vite 配置。现在,Nuxt 使用共享的 Vite 配置,并配备特定于环境的插件,这些插件使用 applyToEnvironment() 方法来针对特定环境。

Vite 环境 API 在 Nuxt 5 中始终启用。experimental.viteEnvironmentApi 选项已被移除。

关键变化:

  1. 不再支持特定于环境的 extendViteConfig()extendViteConfig() 中的 serverclient 选项已被弃用,使用时将显示警告。
  2. 更改插件注册:使用 addVitePlugin() 注册的 Vite 插件,如果仅针对一个环境(通过传递 server: falseclient: false)将不会调用其 configconfigResolved 钩子。
  3. 共享配置vite:extendConfigvite:configResolved 钩子现在与共享配置一起工作,而不是单独的客户端/服务器配置。

变更原因

Vite 环境 API 提供了:

  • 更好的一致性,确保开发和生产构建之间的差异最小化
  • 更细粒度的环境特定配置控制
  • 改进的性能和插件架构
  • 支持自定义环境,而不仅仅是客户端和服务器

迁移步骤

1. 迁移到使用 Vite 插件

我们建议您使用 Vite 插件,而不是 extendViteConfigvite:configResolvedvite:extendConfig

// 之前
extendViteConfig((config) => {
  config.optimizeDeps.include.push('my-package')
}, { server: false })

nuxt.hook('vite:extendConfig' /* or vite:configResolved */, (config, { isClient }) => {
  if (isClient) {
    config.optimizeDeps.include.push('my-package')
  }
})

// 之后
addVitePlugin(() => ({
  name: 'my-plugin',
  config (config) {
    // 这里可以设置全局 vite 配置
  },
  configResolved (config) {
    // 这里可以访问完整解析后的 vite 配置
  },
  configEnvironment (name, config) {
    // 这里可以设置特定环境的 vite 配置
    if (name === 'client') {
      config.optimizeDeps ||= {}
      config.optimizeDeps.include ||= []
      config.optimizeDeps.include.push('my-package')
    }
  },
  applyToEnvironment (environment) {
    return environment.name === 'client'
  },
}))
2. 迁移 Vite 插件以使用环境

您可以使用插件中的新 applyToEnvironment 钩子,而不是使用 addVitePlugin 结合 server: falseclient: false

// 之前
addVitePlugin(() => ({
  name: 'my-plugin',
  config (config) {
    config.optimizeDeps.include.push('my-package')
  },
}), { client: false })

// 之后
addVitePlugin(() => ({
  name: 'my-plugin',
  config (config) {
    // 这里可以设置全局 vite 配置
  },
  configResolved (config) {
    // 这里可以访问完整解析后的 vite 配置
  },
  configEnvironment (name, config) {
    // 这里可以设置特定环境的 vite 配置
    if (name === 'client') {
      config.optimizeDeps ||= {}
      config.optimizeDeps.include ||= []
      config.optimizeDeps.include.push('my-package')
    }
  },
  applyToEnvironment (environment) {
    return environment.name === 'client'
  },
}))
了解更多关于 Vite 环境 API 的内容

迁移到 Vite 8

🚦 影响级别:中等

变化内容

Nuxt 5 从 Vite 7 升级到 Vite 8,其中用 Rolldown 替换了 esbuild 和 Rollup 作为底层打包器。这带来显著更快的构建速度,但包含若干破坏性更改。

与 Vite 环境 API 迁移不同,此更改无法通过设置 future.compatibilityVersion: 5 提前启用。如果想提前测试 Vite 8 兼容性,可在 package.json 中添加 "vite": "^8.0.0-beta.15" 的依赖覆盖。

大多数迁移由 Nuxt 内部处理,但有些面向用户的变化需要关注:

  • vite.esbuildvite.optimizeDeps.esbuildOptions 已弃用,替换为 vite.oxcvite.optimizeDeps.rolldownOptions。Vite 8 目前自动转换这些选项,但未来将移除旧选项。
  • build.rollupOptions 弃用,改用 build.rolldownOptions
  • CommonJS 互操作行为改变。如果使用 CJS 模块,请查阅 Vite 8 迁移指南
查看完整 Vite 8 迁移指南及破坏性更改

迁移到 Nitro v3

🚦 影响级别:重大

发生了什么变化

Nuxt 5 升级到了 Nitro v3,这是服务器引擎的一次重大重写。Nitro v3 基于 srvxh3 v2 构建,全面采用 Web 标准的 Request/Response API。这带来了性能提升和更一致的 API,但服务器端代码存在若干破坏性变更。

我们仍在继续完善 Nitro v3 集成,因此你应预期会有更多变化,以及额外工作来让迁移更加顺利。
阅读 Nitro v3 测试版公告以获取完整概览。
查看完整的 Nitro v3 迁移指南以了解所有破坏性变更。

以下章节重点介绍对 Nuxt 应用开发者和模块作者最相关的变化。

包及导入路径变化

nitropack 包更名为 nitro。所有导入路径都发生了变化:

之前之后
nitropacknitro
nitropack/typesnitro/types
nitropack/runtimenitro
h3(用于服务器工具)nitro/h3

服务器路由内的自动导入(如 defineEventHandlergetQueryreadBodyuseRuntimeConfig 等)继续可用且无需更改。

如果代码中有显式导入,需要更新:

- import { defineEventHandler, getQuery } from 'h3'
+ import { defineEventHandler, getQuery } from 'nitro/h3'

模块作者注意,类型扩展必须针对新的模块路径:

- declare module 'nitropack/types' {
+ declare module 'nitro/types' {
    interface NitroRouteRules {
      myModule?: { /* ... */ }
    }
  }

错误处理:status/statusText 替代 statusCode/statusMessage

h3 v2 重命名了错误属性以符合 Web 标准:

  createError({
-   statusCode: 404,
-   statusMessage: 'Not Found',
+   status: 404,
+   statusText: 'Not Found',
  })

在服务器路由中,错误类由 createError 变为 HTTPError

- import { createError } from 'h3'
+ import { HTTPError } from 'nitro/h3'

  export default defineEventHandler(() => {
-   throw createError({ statusCode: 400, statusMessage: 'Bad request' })
+   throw new HTTPError({ status: 400, statusText: 'Bad request' })
  })
在应用的 Vue 部分(app/ 目录),Nuxt 的 createError 组合式函数依旧可用,并且推荐用于抛出错误。

服务器事件 API 变化(h3 v2)

H3Event 对象现采用 Web 标准 API:

请求属性:

- event.path              // 字符串
+ event.url.pathname      // URL 对象 - 使用 .pathname,.search,.hash

- event.method            // 字符串
+ event.req.method        // 通过 Web Request 对象

- event.node.req.headers  // Node.js IncomingHttpHeaders
+ event.req.headers       // Web Headers API(.get()、.set()、.has())

响应属性:

- event.node.res.statusCode = 200
+ event.res.status = 200

- event.node.res.statusMessage = 'OK'
+ event.res.statusText = 'OK'

- setResponseHeader(event, 'x-custom', 'value')
+ event.res.headers.set('x-custom', 'value')

- appendResponseHeader(event, 'set-cookie', cookie)
+ event.res.headers.append('set-cookie', cookie)

useRuntimeConfig() 不再接受 event

在 Nitro v3 中,useRuntimeConfig() 在服务器路由中不再需要(也不接受)传入 event 参数:

  export default defineEventHandler((event) => {
-   const config = useRuntimeConfig(event)
+   const config = useRuntimeConfig()
  })

路由规则:statusCode 重命名为 status

如果定义了重定向路由规则,属性名称发生了变化:

  export default defineNuxtConfig({
    routeRules: {
      '/old-page': {
-       redirect: { to: '/new-page', statusCode: 302 },
+       redirect: { to: '/new-page', status: 302 },
      },
    },
  })

模块作者的其他变化

  • Nitro 插件导入:使用 import { definePlugin } from 'nitro' 进行显式导入(自动导入仍然有效)。
  • 运行时钩子nitroApp.hooks.hook('beforeResponse', ...)nitroApp.hooks.hook('afterResponse', ...) 已被 nitroApp.hooks.hook('response', ...) 替代。
  • nitro/app 中的 getRouteRules():在服务器上,Nitro 辅助函数从 getRouteRules(event) 更改为 getRouteRules(method, pathname),返回 { routeRules }

移除 experimental.externalVue

🚦 影响级别:极小

变化内容

experimental.externalVue 选项已被移除。当未启用 vue.runtimeCompiler 时,Vue 编译器依赖(@babel/parser@vue/compiler-core@vue/compiler-dom@vue/compiler-ssrestree-walker)现在始终在服务器包中被替换为模拟代理。

变更原因

随着迁移到 Nitro v3,所有依赖项默认都会打包到服务器输出中(不像 Nitro v2 那样将 node_modules 外部化)。externalVue 选项最初设计用于将 Vue 保持为外部依赖,这是为了避免打包多个 Vue 副本,但由于 Nitro v3 无论如何都会打包所有内容,该选项变得无效。

Vue 的服务器构建包含完整的编译器工具链,会不必要地将 @babel/parser(465KB)和其他编译器包拉入服务器包中。这些编译器包仅在启用 vue.runtimeCompiler 进行运行时模板编译时才需要。

通过始终模拟这些编译器依赖,默认服务器包大小减少了约 860KB(约 59%)。

迁移步骤

如果你之前显式设置了 experimental.externalVue,现在应该将其移除。

  export default defineNuxtConfig({
    experimental: {
-     externalVue: false,
    },
  })
如果你使用 vue.runtimeCompiler: true,真正的编译器包仍会像以前一样包含在内。

@vitejs/plugin-vue-jsx 现为可选

🚦 影响级别:极小

变化内容

@vitejs/plugin-vue-jsx 不再随 @nuxt/vite-builder 默认安装。它现在是可选的对等依赖,仅在构建过程中遇到 .jsx.tsx 文件时按需加载。

如果你的项目使用 JSX/TSX 组件,Nuxt 将自动检测并提示你安装该包。

变更原因

@vitejs/plugin-vue-jsx 插件会引入大量依赖树(Babel、@vue/babel-plugin-jsx 等),对于不使用 JSX 的项目来说是不必要的。将其设为可选可减少默认安装大小并加快大多数 Nuxt 项目的依赖解析速度。

迁移步骤

如果你的项目使用 .jsx.tsx 文件,请将 @vitejs/plugin-vue-jsx 添加为开发依赖:

npm install -D @vitejs/plugin-vue-jsx

或者,Nuxt 将在开发期间首次处理 JSX/TSX 文件时自动提示你安装。

如果你的项目不使用 JSX,则无需更改。

移除旧版 _renderResponse 支持

🚦 影响级别:极小

发生了什么变化

不再支持对 ssrContext._renderResponse 的兼容回退检测。现在仅使用 Nuxt 导航组合函数设置的内部 ssrContext['~renderResponse']

变更原因

ssrContext 上的 _renderResponse 属性在内部 API 迁移到 ~renderResponse(见 #33896)后,仅作为兼容回退保留。TODO 注释指出应在 Nuxt v5 中移除。

迁移步骤

如果你之前直接设置过 ssrContext._renderResponse(这从未是公开 API),请改用 ssrContext['~renderResponse']。Nuxt 导航组合函数已使用新属性,因此如果你使用 navigateTo 或路由中间件,则无需更改。

非异步 callHook

🚦 影响级别:极小

变化内容

升级到 hookable v6 后,callHook 现在可能返回 void,而非一直返回 Promise<void>。这是一项性能改进,可在没有钩子或仅有同步钩子的情况下避免不必要的 Promise 分配。

默认情况下(兼容版本为 4),Nuxt 使用 Promise.resolve() 包装 callHook,以确保现有的 .then().catch() 链式调用正常工作。设置兼容版本为 5 时,将移除此包装。

此变动影响构建时的 Nuxt 钩子(模块使用)和运行时的钩子(应用代码中使用)。

变更原因

Hookable v6 避免不必要的 Promise 创建,使钩子调用速度提高 20–40 倍,利于有大量钩子的应用。

迁移步骤

如果你(或你使用的模块)在 callHook 上使用 .then().catch(),请改用 await

- nuxtApp.callHook('my:hook', data).then(() => { ... })
+ await nuxtApp.callHook('my:hook', data)
- nuxtApp.hooks.callHook('my:hook', data).catch(err => { ... })
+ try { await nuxtApp.hooks.callHook('my:hook', data) } catch (err) { ... }
你可以通过设置 future.compatibilityVersion: 5(参见测试 Nuxt 5)或显式启用 experimental.asyncCallHook: false 来提前测试此功能。

或者,确保 callHook 始终返回 Promise:

nuxt.config.ts
export default defineNuxtConfig({
  experimental: {
    asyncCallHook: true,
  },
})

客户端专属注释占位符

🚦 影响级别:极小

变化内容

使用 compatibilityVersion: 5 时,客户端专属组件(.client.vue 文件和 createClientOnly() 包装器)现在在服务器上渲染 HTML 注释(<!--placeholder-->)而非空 <div> 元素。

  • nuxt.config.tsmodules 数组中定义的模块
  • modules/ 目录中自动发现的模块

当占位符 <div> 与实际组件根元素共享相同标签名时,Vue 运行时在激活期间会跳过重新应用 setScopeId。这会导致组件挂载后作用域样式丢失。使用注释节点可完全避免标签名冲突。

此改动确保:

如果你依赖占位符 <div> 来继承属性(classstyle 等)以用于布局(例如,预留空间防止布局偏移),请将组件包装在 <ClientOnly> 中并添加 #fallback 插槽:

- <MyComponent class="placeholder" style="min-height: 200px" />
+ <ClientOnly>
+   <MyComponent />
+   <template #fallback>
+     <div class="placeholder" style="min-height: 200px"></div>
+   </template>
+ </ClientOnly>
你可以通过设置 future.compatibilityVersion: 5(参见测试 Nuxt 5)或显式启用 experimental.clientNodePlaceholder: true 来提前测试此功能。

或者,你可以通过以下配置恢复之前的 <div> 占位符行为:

nuxt.config.ts
export default defineNuxtConfig({
  experimental: {
    clientNodePlaceholder: false,
  },
})

更严格的副作用导入

🚦 影响级别:最小

变更内容

compatibilityVersion: 5 下,Nuxt 生成的 tsconfig.json 会启用 noUncheckedSideEffectImports。这是 TypeScript 7 中的默认设置,因此提前采用它可以让你的项目在升级前保持一致。

启用此选项后,TypeScript 无法解析到模块的仅副作用导入(import './setup')现在会变成类型错误,而之前会被忽略。这只影响类型检查(nuxt typecheck 和你的编辑器),不影响运行时行为。

变更原因

未解析的副作用导入以前会被静默忽略,因此拼写错误或被删除的文件仍可能通过类型检查。对它们进行标记可以捕获这些错误,并与 TypeScript 7 的默认行为保持一致。

迁移步骤

如果类型检查现在对一个非代码资源的副作用导入报错(例如 import '~/assets/styles.css'),请添加一个环境模块声明,让 TypeScript 知道该导入是有效的:

types.d.ts
declare module '*.css' {}
你可以通过在 nuxt.config 中禁用该选项来恢复到之前的行为:
nuxt.config.ts
export default defineNuxtConfig({
  typescript: {
    tsConfig: {
      compilerOptions: {
        noUncheckedSideEffectImports: false,
      },
    },
  },
})

默认禁用 Vue Options API

🚦 影响级别:最小

变更内容

compatibilityVersion: 5 时,Nuxt 会将 Vue 的 __VUE_OPTIONS_API__ 特性标志设为 false,从而将 Vue 的 Options API 运行时从客户端 bundle 中编译移除。

变更原因

尽管大多数 Nuxt 应用都使用 Composition API 和 <script setup> 编写,但 Options API 运行时仍会被打包进每个客户端 bundle。移除它可以缩减客户端 bundle(在一个最小应用中约可减少 6 kB 压缩前体积 / 2 kB gzip 后体积)。

迁移步骤

如果你的任何组件(或某个依赖的组件)使用了 Options API(export default { data() {}, methods: {}, ... }),请在你的 nuxt.config 中重新启用它:

nuxt.config.ts
export default defineNuxtConfig({
  vue: {
    optionsApi: true,
  },
})
defineNuxtComponent 不受影响:它的 asyncDatahead 选项是通过 setup() 处理的,而不是通过 Vue Options API,因此无论该标志如何设置都可以正常工作。

默认启用类型化页面

🚦 影响级别:最小

变更内容

compatibilityVersion: 5 时,experimental.typedPages 会默认启用。Nuxt 会根据你的 pages/ 目录生成带类型的路由名称和路径,因此像 useRoutenavigateTo<NuxtLink>router.push 这样的组合式函数都会针对你实际的路由进行类型检查。

变更原因

类型化路由可以在类型检查阶段而不是运行时捕获失效链接和过时的路由名称,而且它现在已经成为 vue-router 的原生能力,因此作为默认设置是合理的。

迁移步骤

如果你引用了不存在的路由(例如 to 属性中的拼写错误,或者在 pages/ 目录外动态定义的路由),类型检查现在会报错。请修正该引用,或者为你在运行时添加的路由扩展生成的路由类型。

你可以通过设置 future.compatibilityVersion: 5 提前测试此功能(参见 Testing Nuxt 5),或者通过显式启用 experimental.typedPages: true 来开启它。

或者,你也可以选择退出,并通过以下方式恢复到之前的行为:

nuxt.config.ts
export default defineNuxtConfig({
  experimental: {
    typedPages: false,
  },
})