升级指南

学习如何升级到最新的 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 版本 3.12+ 开始的许多 Nuxt 4 的破坏性更改。

观看 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:

npm
npx codemod@latest nuxt/4/migration-recipe
yarn
yarn dlx codemod@latest nuxt/4/migration-recipe
pnpm
pnpm dlx codemod@latest nuxt/4/migration-recipe
bun
bun x 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 OS 的启动。
  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/ 文件夹保持在项目根目录外。
  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'
  }
})

路由元数据去重

🚦 影响程度: 最小

变更内容

现在可以使用 definePageMeta 来设置一些路由元数据,例如 namepath 等。以前这些元数据可以在路由和路由元数据上同时可用(例如,route.nameroute.meta.name)。

现在,这些元数据只能在路由对象上访问。

更改原因

这是因为默认情况下启用了 experimental.scanPageMeta,并且这是一个性能优化。

迁移步骤

迁移应该是简单的:

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

组件名称标准化

🚦 影响程度: 中等

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

变更内容

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

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

在这种情况下,Vue 所认为的组件名称是 MyComponent。如果您想使用 <KeepAlive> 或在 Vue DevTools 中识别它,您需要使用此名称。

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

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

迁移步骤

确保您在使用 @vue/test-utilsfindComponent 以任何方式与组件配合使用时使用更新后的名称,并在任何 <KeepAlive> 中依赖于组件的名称。

或者,暂时可以使用以下代码禁用此行为:

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

Unhead v2

🚦 影响程度: 最小

变更内容

Unhead,用于生成 <head> 标签,已更新到版本 2。虽然大部分兼容,但对某些低级 API 包含多个破坏性更改。

  • 移除了属性: vmidhidchildrenbody
  • 不再支持 Promise 输入。
  • 现默认使用 Capo.js 排序标签。
迁移步骤

上述更改对您的应用的影响最小。

如果您有问题,您应该验证:

  • 您没有使用任何被移除的属性。
useHead({
  meta: [{ 
    name: 'description', 
    // 元标签不需要 vmid 或关键字    
-   vmid: 'description' 
-   hid: 'description'
  }]
})
import { TemplateParamsPlugin, AliasSortingPlugin } from '@unhead/vue/plugins'

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

尽管不需要,但推荐您将任何来自 @unhead/vue 的导入更新为 #importsnuxt/app

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

如果您仍然遇到问题,可以通过启用 head.legacy 配置来恢复 v1 行为。

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

SPA 加载屏幕的新 DOM 位置

🚦 影响程度: 最小

变更内容

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

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

现在,我们默认将模板渲染在 Nuxt 应用根目录旁边:

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

这使得 SPA 加载模板在 Vue 应用或挂起解析之前可以保留在 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
  }
})

useAsyncDatauseFetch 中的默认 dataerror

🚦 影响程度: 最小

变更内容

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'
      }
    }
  }
})

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

当调用 useAsyncDatauseFetchrefresh 时移除弃用的 boolean 值以进行去重选项

🚦 影响程度: 最小

变更内容

以前,可以将 dedupe: boolean 传递给 refresh。这些是 canceltrue)和 deferfalse)的别名。

app.vue
const { refresh } = await useAsyncData(async () => ({ message: 'Hello, Nuxt!' }))

async function refreshData () {
  await refresh({ dedupe: true })
}
更改原因

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

当将 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 语法的模块,但如果您需要自己做此操作,可以选择任何三种向后/向前兼容的替代方案:

+ 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 vs 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