升级指南

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

升级 Nuxt

最新版本

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

npx nuxi upgrade

夜间发布频道

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

夜间发布频道的 latest 标签目前跟踪的是 Nuxt v4 分支,这意味着它目前特别容易出现重大变更——请小心!您可以通过 "nuxt": "npm:nuxt-nightly@3x" 选择加入 3.x 分支的夜间发布。

测试 Nuxt 4

Nuxt 4 的发布日期待定。它取决于 Nitro 重大发布后有足够的时间在社区中进行适当测试。您可以跟踪 Nitro 发布进展,查看此 PR

在发布之前,可以从 Nuxt 3.12+ 版本开始测试 Nuxt 4 的许多重大变更。

选择加入 Nuxt 4

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

然后,您可以将 compatibilityVersion 设置为匹配 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',
  //   parseErrorData: false,
  //   pendingWhenIdle: true,
  //   alwaysRunFetchOnKeyChange: true,
  //   defaults: {
  //     useAsyncData: {
  //       deep: true
  //     }
  //   }
  // },
  // features: {
  //   inlineStyles: true
  // },
  // unhead: {
  //   renderSSRHeadOptions: {
  //     omitLineBreaks: false
  //   }
  // }
})
目前,您需要在选择 Nuxt 4 行为的每个层中定义兼容性版本。Nuxt 4 发布后,这将不再需要。

当您将 compatibilityVersion 设置为 4 时,Nuxt 配置中的默认值将更改以选择 Nuxt v4 行为,但您可以在测试时通过上述注释掉的行,逐一重新启用 Nuxt v3 行为。如果遇到问题,请提交问题,以便我们在 Nuxt 或生态系统中解决。

重大或显著的变更将在这里记录,并附上向后/向前兼容的迁移步骤。

此部分在最终发布前可能会发生变化,因此如果您使用 compatibilityVersion: 4 测试 Nuxt 4,请定期检查此内容。

使用 Codemods 迁移

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

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

有关 Nuxt 4 codemods 的完整列表、每个 codemod 的详细信息、其源代码以及各种运行方式,请访问 Codemod 注册表

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

npx codemod@latest nuxt/4/migration-recipe

此命令将按顺序执行所有 codemods,您可以选择取消任何不想运行的 codemod。每个 codemod 也在下文与其相应变更一起列出,并可以独立执行。

新目录结构

🚦 影响级别:显著

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

👉 查看完整 RFC

变更内容

  • 新的 Nuxt 默认 srcDirapp/,大多数内容都从此目录解析。
  • serverDir 现在默认为 <rootDir>/server,而不是 <srcDir>/server
  • layers/modules/public/ 默认相对于 <rootDir> 解析。
  • 如果使用 Nuxt Content v2.13+content/ 相对于 <rootDir> 解析。
  • 新增了 dir.app,用于查找 router.options.tsspa-loading-template.html,默认值为 <srcDir>/
Nuxt 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'
  }
})

单例数据获取层

🚦 影响级别:中等

变更内容

Nuxt 的数据获取系统(useAsyncDatauseFetch)已进行了重大重组,以提高性能和一致性:

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

变更原因

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

迁移步骤

  1. 检查不一致的选项:检查使用相同键但具有不同选项或获取函数的任何组件。
    // 现在这将触发警告
    const { data: users1 } = useAsyncData('users', () => $fetch('/api/users'), { deep: false })
    const { data: users2 } = useAsyncData('users', () => $fetch('/api/users'), { deep: true })
    

    将共享显式键(并具有自定义选项)的 useAsyncData 调用提取到自己的可组合中可能会有所帮助:
    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 - 可以是 'initial' | 'refresh:hook' | 'refresh:manual' | 'watch'
    +    
    +    // 示例:在手动刷新时不使用缓存
    +    if (ctx.cause === 'refresh:manual') return undefined
    +    
    +    return cachedData[key]
    +  }
    })
    

或者,您可以暂时禁用此行为:

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

路由元数据的去重

🚦 影响级别:最小

变更内容

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

🚦 影响级别:最小

变更内容

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

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

迁移步骤

上述变更对您的应用程序影响应该很小。

如果您遇到问题,请验证:

  • 您没有使用任何已删除的属性。
useHead({
  meta: [{ 
    name: 'description', 
    // 元标签不需要 vmid 或 key    
-   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 加载模板 -->
</div>

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

<div id="__nuxt"></div>
<!-- spa 加载模板 -->

变更原因

这允许 SPA 加载模板在 Vue 应用 suspense 解析之前保留在 DOM 中,防止白色闪烁。

迁移步骤

如果您使用 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.config.ts
export default defineNuxtConfig({
  experimental: {
    parseErrorData: false
  }
})

更细粒度的内联样式

🚦 影响级别:中等

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

如果您这样做,请报告问题,因为我们不打算保持此配置。

移除 useAsyncDatauseFetch 中调用 refresh 时已弃用的 dedupe 选项的布尔值

🚦 影响级别:最小

变更内容

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

app.vue
const { refresh } = await useAsyncData(async () => ({ 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。在重置/清除数据时应尊重这一点。

迁移步骤

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

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

如果您这样做,请报告问题,因为我们不打算保持此配置。

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 }}</p>
    </div>
    <div v-else>
      <p>加载中...</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 })

要退出此行为:

// 或在您的 Nuxt 配置中全局设置
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)

目录索引扫描

🚦 影响级别:中等

变更内容

您的 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 本身。

迁移步骤

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

+ 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 })
+   },
  })

最后,如果您使用模板实用工具(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