升级指南

学习如何升级到最新的 Nuxt 版本。

升级 Nuxt

最新发布

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

npx nuxi upgrade

每日构建频道

要使用最新的 Nuxt 版本并在发布之前测试新功能,请阅读关于 每日构建频道 的指南。

每个每日版本频道的 latest 标签当前跟踪的是 Nuxt v4 分支,这意味着目前可能包含破坏性更改 - 请小心!您可以选择加入 3.x 分支的每日版本,使用 "nuxt": "npm:nuxt-nightly@3x"

测试 Nuxt 4

Nuxt 4 的发布日期 待宣布。它依赖于在 Nitro 主要版本发布后,社区有足够的时间进行适当测试。您可以在 这个 PR 跟踪 Nitro 发布的进度。

在发布之前,您可以测试 Nuxt 4 从 Nuxt 版本 3.12+ 的许多破坏性更改。

观看 Alexander Lichter 演示如何选择加入 Nuxt 4 的破坏性更改的 视频。

选择加入 Nuxt 4

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

然后您可以设置您的 compatibilityVersion 以匹配 Nuxt 4 的行为:

目前,您需要在每一层中定义兼容性版本,这样才能选择加入 Nuxt 4 行为。 Nuxt 4 发布后,将不再需要这样做。
nuxt.config.ts
export default 
defineNuxtConfig
({
future
: {
compatibilityVersion
: 4,
}, // 要重新启用 _所有_ Nuxt v3 行为,请设置以下选项: // srcDir: '.', // dir: { // app: 'app' // }, // experimental: { // scanPageMeta: 'after-resolve', // sharedPrerenderData: false, // compileTemplate: true, // resetAsyncDataToUndefined: true, // templateUtils: true, // relativeWatchPaths: true, // normalizeComponentNames: false, // spaLoadingTemplateLocation: 'within', // defaults: { // useAsyncData: { // deep: true // } // } // }, // features: { // inlineStyles: true // }, // unhead: { // renderSSRHeadOptions: { // omitLineBreaks: false // } // } })

当您将 compatibilityVersion 设置为 4 时,你的 Nuxt 配置中的默认值将更改为选择加入 Nuxt v4 行为,但您可以在测试时逐步重新启用 Nuxt v3 行为,遵循上面注释掉的行。如果有问题,请提报,以便我们能在 Nuxt 或生态系统中解决它们。

迁移到 Nuxt 4

破坏性或重大更改将在此处注明,并附有向后/向前兼容的迁移步骤。

在最终发布之前,此部分可能会更改,因此如果您正在测试使用 compatibilityVersion: 4 的 Nuxt 4,请定期查看此处。

使用 Codemods 进行迁移

为了方便升级过程,我们与 Codemod 团队合作,利用一些开源的 codemods 自动化许多迁移步骤。

如果您遇到任何问题,请通过 npx codemod feedback 向 Codemod 团队报告 🙏

有关 Nuxt 4 codemods 的完整列表、每一个 codemod 的详细信息、其来源和各种运行方式,请访问 Codemod Registry

您可以使用以下 codemod 配方运行本指南中提到的所有 codemod:

npx codemod@latest nuxt/4/migration-recipe

此命令将按顺序执行所有 codemod,并提供取消选择您不希望运行的任何 codemod 的选项。每个 codemod 也在下面列出,并可以单独执行。

新目录结构

🚦 影响级别:重大

Nuxt 现在默认采用新的目录结构,并且具有向后兼容性(如果 Nuxt 检测到您使用旧的结构,例如顶层的 pages/ 目录,则此新结构将不适用)。

👉 查看完整 RFC

更改内容
  • 新的 Nuxt 默认 srcDir 默认是 app/,大多数内容都是从这里解析的。
  • serverDir 现在默认设置为 <rootDir>/server 而不是 <srcDir>/server
  • layers/modules/public/ 默认相对于 <rootDir> 解析
  • 如果使用 Nuxt Content v2.13+content/ 将相对于 <rootDir> 解析
  • 新增了 dir.app,这是我们查找 router.options.tsspa-loading-template.html 的目录 - 默认设置为 <srcDir>/
示例 v4 文件夹结构。
.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/
server/
  api/
  middleware/
  plugins/
  routes/
  utils/
nuxt.config.ts

👉 有关更多详细信息,请参见 实现此更改的 PR

更改原因
  1. 性能 - 将您的所有代码放置在项目根目录中,会导致 .git/node_modules/ 文件夹被文件系统监视器扫描/包含,从而显著延迟在非 Mac 操作系统上的启动时间。
  2. IDE 类型安全 - server/ 和其余应用程序在两个完全不同的上下文中运行,具有不同的全局导入可用,确保 server/ 不与应用程序的其他部分位于同一文件夹内,这是确保您在 IDE 中获得良好自动补全的第一步。
迁移步骤
  1. 创建一个名为 app/ 的新目录。
  2. assets/components/composables/layouts/middleware/pages/plugins/utils/ 文件夹,以及 app.vueerror.vueapp.config.ts 移动到该目录下。如果您有 app/router-options.tsapp/spa-loading-template.html,这些路径保持不变。
  3. 确保您的 nuxt.config.tscontent/layers/modules/public/server/ 文件夹保留在项目根目录外,而不在 app/ 文件夹内。
  4. 请记得更新任何第三方配置文件,以便与新的目录结构配合使用,例如您的 tailwindcsseslint 配置(如果需要 - @nuxtjs/tailwindcss 应该会自动正确配置 tailwindcss)。
您可以通过运行 npx codemod@latest nuxt/4/file-structure 来自动化此迁移。

但是,迁移并不是必须的。如果您希望保留当前的文件夹结构,Nuxt 应该能够自动检测到。(如果没有,请提出问题。)唯一的例外是如果您 已经 有一个自定义的 srcDir。在此情况下,您应该意识到您的 modules/public/server/ 文件夹将从 rootDir 解析,而不是从您自定义的 srcDir。如果需要,您可以通过配置 dir.modulesdir.publicserverDir 来覆盖此行为。

您还可以使用以下配置强制采用 v3 文件夹结构:

nuxt.config.ts
export default defineNuxtConfig({
  // 这将把新的 srcDir 默认值从 `app` 恢复为您的根目录
  srcDir: '.',
  // 这指定 `app/router.options.ts` 和 `app/spa-loading-template.html` 的目录前缀
  dir: {
    app: 'app'
  }
})

组件名称规范化

🚦 影响级别:适中

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

更改内容

默认情况下,如果您没有手动设置,Vue 将为组件分配与组件文件名匹配的组件名称。

目录结构
├─ components/
├─── SomeFolder/
├───── MyComponent.vue

在这种情况下,就 Vue 而言,组件名称将是 MyComponent。如果您想与其一起使用 <KeepAlive>,或在 Vue DevTools 中标识它,您将需要使用这个名称。

但是为了自动导入它,您需要使用 SomeFolderMyComponent

通过此更改,这两个值将匹配,Vue 将生成与 Nuxt 组件命名模式匹配的组件名称。

迁移步骤

确保在使用 @vue/test-utils 中的 findComponent 的任何测试中使用更新的名称,并在任何依赖于组件名称的 <KeepAlive> 中使用。

或者,目前您可以通过以下方式禁用此行为:

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

SPA 加载屏幕的新 DOM 位置

🚦 影响级别:最小

更改内容

当呈现仅客户端页面(ssr: false)时,我们可选地在 Nuxt 应用根内呈现加载屏幕(来自 app/spa-loading-template.html):

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

现在,我们默认将在 Nuxt 应用根旁边呈现模板:

<div id="__nuxt"></div>
<!-- spa loading template -->
更改原因

这允许 spa 加载模板在 Vue 应用 suspense 解析之前保留在 DOM 中,从而防止出现白屏闪烁。

迁移步骤

如果您是使用 CSS 或 document.queryElement 针对 spa 加载模板,您将需要更新您的选择器。为此,您可以使用新的 app.spaLoaderTagapp.spaLoaderAttrs 配置选项。

或者,您可以用以下配置恢复到以前的行为:

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

更加细致的内联样式

🚦 影响级别:适中

Nuxt 现在只会将样式内联于 Vue 组件,而非全局 CSS。

更改内容

以前,Nuxt 会将所有 CSS(包括全局样式)内联,并删除指向分离 CSS 文件的 <link> 元素。现在,Nuxt 只会对 Vue 组件执行此操作(之前产生单独的 CSS 块)。我们认为这是减少单独网络请求的较好平衡(和以前一样,初始加载时不会为每个页面或每个组件分开请求单独的 .css 文件),并允许缓存单个全局 CSS 文件,减少初始请求的文档下载大小。

迁移步骤

此功能是完全可配置的,您可以通过将 inlineStyles: true 设置为全局 CSS 以及每个组件的 CSS 来恢复以前的行为。

nuxt.config.ts
export default 
defineNuxtConfig
({
features
: {
inlineStyles
: true
} })

解析后扫描页面元数据

🚦 影响级别:最小

更改内容

我们现在在调用 pages:extend 钩子后扫描页面元数据(在 definePageMeta 中定义)。

更改原因

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

迁移步骤

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

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

或者,您可以用以下配置恢复到以前的行为:

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

共享预渲染数据

🚦 影响级别:中等

更改内容

我们启用了之前的实验性功能,以在不同页面之间共享来自 useAsyncDatauseFetch 调用的数据。查看 原始 PR

更改原因

此功能自动在预渲染的页面之间共享负载数据。这可以显著提高预渲染使用 useAsyncDatauseFetch 的站点的性能,这些站点在不同页面中获取相同的数据。

例如,如果您的站点需要每个页面都有 useFetch 调用(例如,从菜单获取导航数据,或者从 CMS 获取站点设置),则这些数据只会在预渲染第一个使用它的页面时获取一次,然后缓存以便在预渲染其他页面时使用。

迁移步骤

确保您的数据的任何唯一键始终可以解析为相同的数据。例如,如果您使用 useAsyncData 获取与特定页面相关的数据,则应提供一个唯一匹配该数据的键。(useFetch 应该会为您自动执行此操作。)

app/pages/test/[slug].vue
// 在动态页面(例如 `[slug].vue`)中,这样做是不安全的,因为路由 slug 会影响提取的数据,
// 但 Nuxt 无法知道,因为它未反映在键中。
const route = useRoute()
const { data } = await useAsyncData(async () => {
  return await $fetch(`/api/my-page/${route.params.slug}`)
})
// 相反,您应该使用一个唯一识别提取数据的键。
const { data } = await useAsyncData(route.params.slug, async () => {
  return await $fetch(`/api/my-page/${route.params.slug}`)
})

或者,您可以通过以下方式禁用此功能:

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

useAsyncDatauseFetchdataerror 的默认值

🚦 影响级别:最小

更改内容

useAsyncData 返回的 dataerror 对象现在默认值为 undefined

更改原因

之前 data 被初始化为 null,但在 clearNuxtData 中重置为 undefinederror 被初始化为 null。此更改是为了提高一致性。

迁移步骤

如果您在检查 data.valueerror.value 是否为 null,您可以将这些检查更新为检查 undefined

您可以通过运行 npx codemod@latest nuxt/4/default-data-error-value 来自动化此步骤。

如果您遇到任何问题,可以通过以下方法恢复到以前的行为:

nuxt.config.ts
export default 
defineNuxtConfig
({
experimental
: {
defaults
: {
useAsyncData
: {
value
: 'null',
errorValue
: 'null'
} } } })

如果您这样做,请报告一个问题,因为我们不打算将其保留为可配置的。

移除在 useAsyncDatauseFetch 中调用 refresh 时已弃用的 boolean 值的 dedupe 选项

🚦 影响级别:最小

更改内容

以前可以传递 dedupe: boolean 作为 refresh 的参数。这些是 canceltrue)和 deferfalse)的别名。

app.vue
const { 
refresh
} = await
useAsyncData
(async () => ({
message
: 'Hello, Nuxt 3!' }))
async function
refreshData
() {
await
refresh
({ dedupe: true })
Type 'true' is not assignable to type 'DedupeOption | undefined'.
}
更改原因

这些别名已被移除,以提高清晰度。

问题出现在将 dedupe 作为 useAsyncData 的选项添加时,当我们移除布尔值时,它们最终成为 相反的

refresh({ dedupe: false }) 意味着“在此新请求中不要 取消 现有请求”。但在 useAsyncData 的选项中传递 dedupe: true 意味着“如果有现有的待处理请求,则不要发出任何新请求”。(请参见 PR

迁移步骤

迁移应该很简单:

  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' })
  }
您可以通过运行 npx codemod@latest nuxt/4/deprecated-dedupe-value 来自动化此步骤。

useAsyncDatauseFetch 中清除 data 时尊重默认值

🚦 影响级别:最小

更改内容

如果您为 useAsyncData 提供了自定义的 default 值,此值将在调用 clearclearNuxtData 时被使用,并将重置为其默认值,而不仅仅是未设置。

更改原因

用户通常会设置一个适当的空值,例如空数组,以避免在迭代时检查 null / undefined。这在重置/清除数据时应被尊重。

迁移步骤

如果您遇到任何问题,可以暂时用以下方式恢复到以前的行为:

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

如果您这样做,请报告一个问题,因为我们不打算将其保留为可配置的。

useAsyncDatauseFetch 中浅层数据响应性

🚦 影响级别:最小

useAsyncDatauseFetchuseLazyAsyncDatauseLazyFetch 返回的 data 对象现在是一个 shallowRef,而不是 ref

更改内容

当新数据被提取时,任何依赖于 data 的东西仍然会是响应式的,因为整个对象被替换。但是如果您的代码更改了该数据结构中的某个属性,这将不会在应用程序中触发任何响应性。

更改原因

这带来了 显著的 性能提升,特别是对于深层嵌套的对象和数组,因为 Vue 不需要观察每一个属性/数组的修改。在大多数情况下,data 也应该是不可变的。

迁移步骤

在大多数情况下,不需要进行迁移,但是如果您依赖于数据对象的响应性,那么您有两个选项:

  1. 您可以逐个可组合地选择启用深度响应性:
    - const { data } = useFetch('/api/test')
    + const { data } = useFetch('/api/test', { deep: true })
    
  2. 您可以在项目范围内更改默认行为(不推荐):
    nuxt.config.ts
    export default 
    defineNuxtConfig
    ({
    experimental
    : {
    defaults
    : {
    useAsyncData
    : {
    deep
    : true
    } } } })
如果您需要的话,可以通过运行 npx codemod@latest nuxt/4/shallow-function-reactivity 来自动化此步骤。

builder:watch 中的绝对监视路径

🚦 影响级别:最小

更改内容

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

更改原因

这使我们能够支持监视位于 srcDir 之外的路径,并为层和其他更复杂的模式提供更好的支持。

迁移步骤

我们已经主动迁移了我们知道使用此钩子的公共 Nuxt 模块。请参见 问题 #25339

然而,如果您是使用 builder:watch 钩子的模块作者,并希望保持向后/向前兼容,您可以使用以下代码确保您的代码在 Nuxt v3 和 Nuxt v4 中正常工作:

+ import { relative, resolve } from 'node:fs'
  // ...
  nuxt.hook('builder:watch', async (event, path) => {
+   path = relative(nuxt.options.srcDir, resolve(nuxt.options.srcDir, path))
    // ...
  })
您可以通过运行 npx codemod@latest nuxt/4/absolute-watch-path 来自动化此步骤。

移除 window.__NUXT__ 对象

更改内容

我们将在应用程序完成水合后移除全局的 window.__NUXT__ 对象。

更改原因

这为多应用模式 (#21635) 开辟了可能,并使我们能够专注于单一的访问 Nuxt 应用数据的方法 - useNuxtApp()

迁移步骤

这些数据仍然可用,但是可以通过 useNuxtApp().payload 访问:

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

目录索引扫描

🚦 影响级别:中等

更改内容

您的 middleware/ 文件夹中的子文件夹也会被扫描以查找 index 文件,并且这些文件现在也会在您的项目中注册为中间件。

更改原因

Nuxt 会自动扫描许多文件夹,包括 middleware/plugins/

我们希望使此行为在扫描的目录之间保持一致,因此在 plugins/ 文件夹中的子文件夹会扫描 index 文件。

迁移步骤

可能不需要迁移,但是如果您希望恢复到之前的行为,可以添加一个钩子以过滤这些中间件:

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

模板编译更改

🚦 影响级别:最小

更改内容

之前,Nuxt 使用 lodash/template 编译位于文件系统上的模板,采用 .ejs 文件格式/语法。此外,我们提供了一些模板工具(serializeimportNameimportSources),这些工具现在正在被移除。

更改原因

在 Nuxt v3 中,我们转向一种“虚拟”语法,使用 getContents() 函数,更加灵活且性能更高。

此外,lodash/template 遇到了一系列安全问题。这些问题实际上不适用于 Nuxt 项目,因为它在构建时而不是运行时被使用,并且是由可信代码使用。但它们仍然会出现在安全审计中。此外,lodash 是一个庞大的依赖,且大部分项目都未使用。

最后,直接在 Nuxt 中提供代码序列化函数并不理想。相反,我们维护像 unjs/knitwork 这样的项目,这些项目可以作为您的项目的依赖,而安全问题可以直接被报告及解决,而无需升级 Nuxt 本身。

迁移步骤

我们已提出 PR 来更新使用 EJS 语法的模块,但如果您需要自己执行此操作,您有三个向后/向前兼容的替代方案:

  • 将您的字符串插值逻辑直接移动到 getContents() 中。
  • 使用自定义函数处理替换,如在 https://github.com/nuxt-modules/color-mode/pull/240。
  • 使用 es-toolkit/compat(一个可以替代 lodash 模板的直接替代品),作为 项目的依赖,而不是 Nuxt:
+ import { readFileSync } from 'node:fs'
+ import { template } from 'es-toolkit/compat'
  // ...
  addTemplate({
    fileName: 'appinsights-vue.js'
    options: { /* some options */ },
-   src: resolver.resolve('./runtime/plugin.ejs'),
+   getContents({ options }) {
+     const contents = readFileSync(resolver.resolve('./runtime/plugin.ejs'), 'utf-8')
+     return template(contents)({ options })
+   },
  })

最后,如果您使用模板工具(serializeimportNameimportSources),您可以用来自 knitwork 的工具按如下方式替换它们:

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
您可以通过运行 npx codemod@latest nuxt/4/template-compilation-changes 来自动化此步骤。

移除实验性功能

🚦 影响级别:最小

更改内容

四个实验性功能在 Nuxt 4 中不再可配置:

  • experimental.treeshakeClientOnly 将默认为 true(自 v3.0 起的默认值)
  • experimental.configSchema 将默认为 true(自 v3.3 起的默认值)
  • experimental.polyfillVueUseHead 将默认为 false(自 v3.4 起的默认值)
  • experimental.respectNoSSRHeader 将默认为 false(自 v3.4 起的默认值)
  • vite.devBundler 不再可配置 - 默认使用 vite-node
更改原因

这些选项已在当前值下设置了一段时间,我们没有理由相信它们需要保持可配置性。

迁移步骤

Nuxt 2 与 Nuxt 3+

下表是对 Nuxt 三个版本的快速比较:

特性 / 版本Nuxt 2Nuxt BridgeNuxt 3+
Vue223
稳定性😊 稳定😊 稳定😊 稳定
性能🏎 快✈️ 更快🚀 最快
Nitro 引擎
ESM 支持🌙 部分👍 更好
TypeScript☑️ 选择加入🚧 部分
组合式 API🚧 部分
选项 API
组件自动导入
<script setup> 语法🚧 部分
自动导入
webpack445
Vite⚠️ 部分🚧 部分
Nuxi CLI❌ 旧✅ nuxi✅ nuxi
静态网站

从 Nuxt 2 迁移到 Nuxt 3+

迁移指南提供了 Nuxt 2 特性与 Nuxt 3+ 特性之间的逐步比较,并指导您调整当前的应用程序。

查看 从 Nuxt 2 迁移到 Nuxt 3 的指南

从 Nuxt 2 迁移到 Nuxt Bridge

如果您希望逐步将您的 Nuxt 2 应用程序迁移到 Nuxt 3,您可以使用 Nuxt Bridge。Nuxt Bridge 是一层兼容性层,允许您在 Nuxt 2 中使用 Nuxt 3+ 的特性,并通过选择加入机制启用它们。

从 Nuxt 2 迁移到 Nuxt Bridge