升级指南

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

升级 Nuxt

最新版本

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

npx nuxt upgrade

夜间发布渠道

要在 Nuxt 功能发布前使用最新构建版本并测试其功能,请阅读夜间发布通道指南。

迁移到 Nuxt 4

Nuxt 4 包含了显著的改进和变更。本指南将帮助您将现有的 Nuxt 3 应用程序迁移到 Nuxt 4。

首先,升级到 Nuxt 4

npm install nuxt@^4.0.0

升级后,大多数 Nuxt 4 的行为现在都是默认的。但是,如果您的迁移需要保持向后兼容性,某些功能仍然可以配置。

以下部分详细介绍了升级到 Nuxt 4 时需要进行的关键变更和迁移。

突破性或重大变更已在下面记录,并附有迁移步骤和可用的配置选项。

使用 Codemods 进行迁移

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

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

有关 Nuxt 4 codemods 的完整列表、详细信息、其来源以及运行它们的各种方式,请访问Codemod 注册表.

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

npx codemod@latest nuxt/4/migration-recipe

此命令将按顺序执行所有 codemods,您可以选择取消选择任何不想运行的 codemod。每个 codemod 及其各自的变更也列在下面,并且可以独立执行。

新目录结构

🚦 影响级别:重大

Nuxt 现在默认使用新的目录结构,并向后兼容(因此如果 Nuxt 检测到您正在使用旧结构,例如顶层 app/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/
shared/
server/
  api/
  middleware/
  plugins/
  routes/
  utils/
nuxt.config.ts
有了这个新结构,~ 别名现在默认指向 app/ 目录(您的 srcDir)。这意味着 ~/components 解析为 app/components/~/pages 解析为 app/pages/ 等。

👉 欲了解更多详情,请参见实现此更改的 PR.

变更原因

  1. 性能 - 将所有代码放在仓库根目录会导致 .git/node_modules/ 文件夹被 FS 观察器扫描/包含,这会显著延迟非 Mac OSes 上的启动。
  2. IDE 类型安全 - server/ 和您应用程序的其余部分在两个完全不同的上下文中运行,具有不同的全局导入可用,确保 server/ 不在您应用程序的同一文件夹中是确保您在 IDE 中获得良好自动补全的重要第一步。

迁移步骤

  1. 创建一个名为 app/ 的新目录。
  2. 将您的 assets/components/composables/app/layouts/app/middleware/app/pages/app/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({
  // 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 的数据获取系统(useAsyncDatauseFetch)已进行重大重组,以提高性能和一致性

  1. 相同键的共享引用:所有使用相同键调用 useAsyncDatauseFetch 的函数现在共享相同的 dataerrorstatus 引用。这意味着所有带有显式键的调用都不能有冲突的 deeptransformpickgetCachedDatadefault 选项。
  2. getCachedData 的更多控制getCachedData 函数现在每次获取数据时都会被调用,即使这是由观察器或调用 refreshNuxtData 引起的。(以前,总是获取新数据,并且在这种情况下不调用此函数。)为了更好地控制何时使用缓存数据以及何时重新获取,该函数现在接收一个包含请求原因的上下文对象。
  3. 响应式键支持:您现在可以使用计算引用、普通引用或 getter 函数作为键,这使得数据能够自动重新获取(并单独存储数据)。
  4. 数据清理:当使用 useAsyncData 获取数据的最后一个组件被卸载时,Nuxt 将删除该数据,以避免内存使用量不断增长。

变更原因

这些更改旨在改善内存使用并提高 useAsyncData 调用之间的加载状态一致性。

迁移步骤

  1. 检查不一致的选项:检查所有使用相同键但选项或获取函数不同的组件。
    // 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 })
    

    将任何共享显式键(并具有自定义选项)的 useAsyncData 调用提取到它们自己的组合函数中可能是有益的
    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 实现:
    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]
    +  }
    })
    

或者,现在您可以禁用此行为

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

层中模块加载顺序已更正

🚦 影响级别:最小

变更内容

使用Nuxt 层时,模块的加载顺序已更正。以前,项目根目录中的模块在扩展层中的模块之前加载,这与预期行为相反。

现在模块按正确顺序加载

  1. 层模块优先(按扩展顺序 - 较深层优先)
  2. 项目模块最后(最高优先级)

这会影响

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

变更原因

此更改确保

  • 扩展层的优先级低于消费项目
  • 模块执行顺序与直观的层继承模式匹配
  • 模块配置和钩子在多层设置中按预期工作

迁移步骤

大多数项目不需要更改,因为这会更正加载顺序以匹配预期行为。

但是,如果您的项目依赖于以前不正确的顺序,您可能需要

  1. 审查模块依赖关系:检查是否有任何模块依赖于特定的加载顺序
  2. 调整模块配置:如果模块是为解决不正确的顺序而配置的
  3. 彻底测试:确保所有功能在更正的顺序下按预期工作

新正确顺序的示例

// 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 钩子。此钩子在所有其他模块加载后运行,这意味着它是安全使用的。

👉 参阅PR #31507等等问题 #25719了解更多详情。

路由元数据去重

🚦 影响级别:最小

变更内容

可以使用 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

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

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

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

迁移步骤

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

或者,现在您可以禁用此行为

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

Unhead v2

🚦 影响级别:最小

变更内容

Unhead,用于生成 <head> 标签,已更新到版本 2。虽然大部分兼容,但它包含几个对低级 API 的重大更改。

  • 已删除的 props:vmidhidchildrenbody
  • 不再支持 Promise 输入。
  • 标签现在默认使用 Capo.js 排序。

迁移步骤

以上更改对您的应用程序影响极小。

如果您遇到问题,您应该验证

  • 您没有使用任何已删除的 props。
useHead({
  meta: [{ 
    name: 'description', 
    // meta tags don't need a vmid, or a key    
-   vmid: 'description' 
-   hid: 'description'
  }]
})
import { AliasSortingPlugin, TemplateParamsPlugin } 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)时,我们选择性地在 Nuxt 应用程序根目录下渲染一个加载屏幕(来自 ~/app/spa-loading-template.html - 请注意,在 Nuxt 4 中这也已更改为 ~/spa-loading-template.html

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

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

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

变更原因

这允许 spa 加载模板保留在 DOM 中,直到 Vue 应用程序的 suspense 解决,从而防止闪白。

迁移步骤

如果您正在使用 CSS 或 document.queryElement 定位 spa 加载模板,则需要更新您的选择器。为此,您可以使用新的 app.spaLoaderTagapp.spaLoaderAttrs 配置选项。

或者,您可以通过以下方式恢复以前的行为

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

已解析的 error.data

🚦 影响级别:最小

以前可以抛出带有 data 属性的错误,但此属性未被解析。现在,它被解析并可在 error 对象中访问。尽管这是一个修复,但如果您依赖于以前的行为并手动解析它,这在技术上是一个重大更改。

迁移步骤

更新您的自定义 error.vue 以删除对 error.data 的任何额外解析

  <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,包括全局样式,并删除 <link> 元素以分离 CSS 文件。现在,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.

变更原因

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

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

迁移步骤

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

app/pages/test/[slug].vue
// 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 () => {
  return await $fetch(`/api/my-page/${route.params.slug}`)
})
// Instead, you should use a key that uniquely identifies the data fetched.
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 自动化此步骤

useAsyncDatauseFetch 中调用 refresh 时,移除了 dedupe 选项的已弃用 boolean

🚦 影响级别:最小

变更内容

以前可以将 dedupe: boolean 传递给 refresh。这些是 cancel (true) 和 defer (false) 的别名。

app/app.vue
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.)

迁移步骤

迁移应该很简单

  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。在重置/清除数据时应尊重此设置。

useAsyncDatauseFetchpending 值对齐

🚦 影响级别:中等

useAsyncDatauseFetchuseLazyAsyncDatauseLazyFetch 返回的 pending 对象现在是一个计算属性,仅当 status 也处于 pending 状态时才为 true

变更内容

现在,当传递 immediate: false 时,pending 将在首次请求之前为 false。这与之前的行为不同,之前 pending 始终为 true,直到首次请求。

变更原因

这使 pending 的含义与 status 属性保持一致,后者在请求进行中时也处于 pending 状态。

迁移步骤

如果您依赖 pending 属性,请确保您的逻辑考虑了新行为,即 pending 仅当状态也处于 pending 状态时才为 true

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

或者,您可以暂时恢复到以前的行为

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

useAsyncDatauseFetch 中的键更改行为

🚦 影响级别:中等

变更内容

useAsyncDatauseFetch 中使用响应式键时,Nuxt 会在键更改时自动重新获取数据。当设置 immediate: false 时,useAsyncData 仅在数据已获取一次后才在键更改时获取数据。

以前,useFetch 的行为略有不同。它总是在键更改时获取数据。

现在,useFetchuseAsyncData 的行为保持一致——仅在数据已获取一次后才在键更改时获取数据。

变更原因

这确保了 useAsyncDatauseFetch 之间行为的一致性,并防止意外获取。如果您已设置 immediate: false,则必须调用 refreshexecute,否则 useFetchuseAsyncData 中的数据将永远不会被获取。

迁移步骤

此更改通常会改善预期行为,但如果您期望更改非即时 useFetch 的键或选项,您现在将需要首次手动触发它。

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

要退出此行为

// Or globally in your Nuxt config
export default defineNuxtConfig({
  experimental: {
    alwaysRunFetchOnKeyChange: 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)

目录索引扫描

🚦 影响级别:中等

变更内容

您的 app/middleware/ 文件夹中的子文件夹也已扫描 index 文件,这些文件现在也作为中间件注册到您的项目中。

变更原因

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

您的 app/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 自动化此步骤

默认 TypeScript 配置更改

🚦 影响级别:最小

变更内容

compilerOptions.noUncheckedIndexedAccess 现在是 true 而不是 false

变更原因

此更改是先前3.12 配置更新的后续,其中我们改进了默认值,主要遵循TotalTypeScript 的建议.

迁移步骤

有两种方法

  1. 对您的应用程序运行类型检查并修复任何新错误(推荐)。
  2. 在您的 nuxt.config.ts 中覆盖新默认值
    export default defineNuxtConfig({
      typescript: {
        tsConfig: {
          compilerOptions: {
            noUncheckedIndexedAccess: false,
          },
        },
      },
    })
    

TypeScript 配置拆分

🚦 影响级别:最小

变更内容

Nuxt 现在为不同的上下文生成单独的 TypeScript 配置,以提供更好的类型检查体验

  1. 新的 TypeScript 配置文件:Nuxt 现在生成额外的 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. 选择性项目引用:新项目或需要更好类型检查的项目可以采用 TypeScript 的项目引用功能。
  4. 上下文特定的类型检查:每个上下文现在都具有适合其特定环境的编译器选项和包含/排除项。
  5. 新的 typescript.nodeTsConfig 选项:您现在可以自定义 Node.js 构建时代码的 TypeScript 配置。

变更原因

此更改提供了以下几点优势

  1. 更好的类型安全:每个上下文(应用程序、服务器、构建时)都通过上下文特定的全局变量和 API 获得适当的类型检查。
  2. 改进的 IDE 体验:为代码库的不同部分提供更好的智能感知和错误报告。
  3. 更清晰的分离:服务器代码不会错误地建议客户端 API,反之亦然。
  4. 性能:TypeScript 可以使用适当范围的配置更有效地检查代码。

例如,在您的 nuxt.config.ts 中无法使用自动导入(但以前 TypeScript 不会标记此问题)。虽然 IDE 识别出您的 server/ 目录中的 tsconfig.json 所暗示的独立上下文,但这并未反映在类型检查中(需要单独的步骤)。

迁移步骤

无需迁移 - 现有项目将继续按以前的方式工作。

但是,为了利用改进的类型检查,您可以选择加入新的项目引用方法

  1. 更新您的根 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. 更新您的类型检查脚本以使用项目引用的构建标志
    - "typecheck": "nuxt prepare && vue-tsc --noEmit"
    + "typecheck": "nuxt prepare && vue-tsc -b --noEmit"
    
  4. 将所有类型增强移动到其适当的上下文:
    • 如果您正在增强应用程序上下文的类型,请将文件移动到 app/ 目录。
    • 如果您正在增强服务器上下文的类型,请将文件移动到 server/ 目录。
    • 如果您正在增强在应用程序和服务器之间共享的类型,请将文件移动到 shared/ 目录。
    app/server/shared/ 目录外部增强类型将不适用于新的项目引用设置。
  5. 根据需要配置 Node.js TypeScript 选项
    export default defineNuxtConfig({
      typescript: {
        // Customize app/server TypeScript config
        tsConfig: {
          compilerOptions: {
            strict: true,
          },
        },
        // Customize build-time TypeScript config
        nodeTsConfig: {
          compilerOptions: {
            strict: true,
          },
        },
      },
    })
    
  6. 更新任何运行 TypeScript 检查的 CI/构建脚本,以确保它们使用新的项目引用方法。

新配置为选择加入的项目提供了更好的类型安全性和智能感知,同时保持了现有设置的完全向后兼容性。

移除实验性功能

🚦 影响级别:最小

变更内容

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

变更原因

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

迁移步骤

  • polyfillVueUseHead 可由用户通过以下方式实现此插件
  • respectNoSSRHeader 可由用户通过以下方式实现服务器中间件

移除顶层 generate 配置

🚦 影响级别:最小

变更内容

Nuxt 4 中不再提供顶层 generate 配置选项。这包括其所有属性

  • generate.exclude - 用于从预渲染中排除路由
  • generate.routes - 用于指定要预渲染的路由

变更原因

顶层 generate 配置是 Nuxt 2 的遗留。我们已经支持 nitro.prerender 一段时间了,它是 Nuxt 3+ 中配置预渲染的首选方式。

迁移步骤

generate 配置替换为相应的 nitro.prerender 选项

export default defineNuxtConfig({
- generate: {
-   exclude: ['/admin', '/private'],
-   routes: ['/sitemap.xml', '/robots.txt']
- }
+ nitro: {
+   prerender: {
+     ignore: ['/admin', '/private'],
+     routes: ['/sitemap.xml', '/robots.txt']
+   }
+ }
})
阅读有关 Nitro 预渲染配置选项的更多信息。

Nuxt 2 与 Nuxt 3+

下表快速比较了 Nuxt 的 3 个版本

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

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