升级指南

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

升级 Nuxt

最新版本

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

npx nuxt upgrade

夜间发布渠道

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

测试 Nuxt 5

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

选择使用 Nuxt 5

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

然后您可以设置您的 future.compatibilityVersion 以匹配 Nuxt 5 行为

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

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

  • Vite 环境 API:自动启用新的 Vite 环境 API,以改进构建配置
  • 其他 Nuxt 5 改进和更改(可用时)
本节在最终发布前可能会发生变化,因此如果您正在使用 future.compatibilityVersion: 5 测试 Nuxt 5,请定期查看此处。

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

迁移到 Vite 环境 API

🚦 影响级别:中

更改内容

Nuxt 5 迁移到 Vite 6 的新环境 API,它规范了环境的概念,并为每个环境提供了更好的配置控制。

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

您可以通过设置 future.compatibilityVersion: 5(参见 测试 Nuxt 5)或通过显式启用 experimental.viteEnvironmentApi: true 来提前测试此功能。

主要更改

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

更改原因

Vite 环境 API 提供

  • 更好的开发和生产构建一致性
  • 对环境特定配置更精细的控制
  • 改进的性能和插件架构
  • 支持客户端和服务器以外的自定义环境

迁移步骤

1. 迁移使用 Vite 插件

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

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

// After
addVitePlugin(() => ({
  name: 'my-plugin',
  config (config) {
    // you can set global vite configuration here
  },
  configResolved (config) {
    // you can access the fully resolved vite configuration here
  },
  configEnvironment (name, config) {
    // you can set environment-specific vite configuration here
    if (name === 'client') {
      config.optimizeDeps ||= {}
      config.optimizeDeps.include ||= []
      config.optimizeDeps.include.push('my-package')
    }
  },
  applyToEnvironment (environment) {
    return environment.name === 'client'
  },
}))
2. 迁移 Vite 插件以使用环境

您可以使用插件中的新 applyToEnvironment 钩子,而不是使用带有 server: falseclient: falseaddVitePlugin

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

// After
addVitePlugin(() => ({
  name: 'my-plugin',
  config (config) {
    // you can set global vite configuration here
  },
  configResolved (config) {
    // you can access the fully resolved vite configuration here
  },
  configEnvironment (name, config) {
    // you can set environment-specific vite configuration here
    if (name === 'client') {
      config.optimizeDeps ||= {}
      config.optimizeDeps.include ||= []
      config.optimizeDeps.include.push('my-package')
    }
  },
  applyToEnvironment (environment) {
    return environment.name === 'client'
  },
}))
了解更多关于 Vite 的环境 API

迁移到 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 注册表.

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

# Using pinned version due to https://github.com/codemod-com/codemod/issues/1710
npx [email protected] nuxt/4/migration-recipe

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

新目录结构

🚦 影响级别:显著

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

👉查看完整 RFC

更改内容

  • 新的 Nuxt 默认 srcDir 默认为 app/,大多数内容都从那里解析。
  • serverDir 现在默认为 <rootDir>/server,而不是 <srcDir>/server
  • layers/modules/public/ 默认相对于 <rootDir> 解析
  • 如果使用Nuxt Content v2.13+content/ RelativeTo <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/ 文件夹被文件系统观察者扫描/包含,这可能会在非 Mac OS 上显著延迟启动。
  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.tsmodules 数组中定义的模块
  • 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

在这种情况下,从 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', 
    // 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 来自动化此步骤

移除在调用 useAsyncDatauseFetchrefreshdedupe 选项的已废弃 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 属性对齐,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 体验:为代码库的不同部分提供更好的 IntelliSense 和错误报告。
  3. 更清晰的分离:服务器代码不会错误地建议客户端 API,反之亦然。
  4. 性能:TypeScript 可以使用正确范围的配置更有效地检查代码。

例如,自动导入在您的 nuxt.config.ts 中不可用(但以前 TypeScript 没有标记这一点)。虽然 IDE 识别出您的 server/ 目录中由 tsconfig.json 提示的独立上下文,但这并未反映在类型检查中(需要单独的步骤)。

迁移步骤

无需迁移 - 现有项目将继续像以前一样工作。

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

  1. 更新您的根 tsconfig.json 以使用项目引用
    如果您的 tsconfig.json 当前有 "extends": "./.nuxt/tsconfig.json" 行,请在添加引用之前删除它。项目引用和 extends 是互斥的。
    {
      // Remove "extends": "./.nuxt/tsconfig.json" if present
      "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/构建脚本,以确保它们使用新的项目引用方法。

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

移除实验性功能

🚦 影响级别:最小

更改内容

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 vs. Nuxt 3+

下表是 Nuxt 三个版本之间的快速比较

功能 / 版本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