升级指南

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

升级 Nuxt

¥Upgrading Nuxt

最新版本

¥Latest release

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

¥To upgrade Nuxt to the latest release, use the nuxi upgrade command.

npx nuxi upgrade

Nightly 发布渠道

¥Nightly Release Channel

要在最新的 Nuxt 构建和测试功能发布之前使用它们,请阅读 每日发布渠道 指南。

¥To use the latest Nuxt build and test features before their release, read about the nightly release channel guide.

夜间发布通道 latest 标签目前正在跟踪 Nuxt v4 分支,这意味着它现在特别有可能发生重大更改。 - 小心!¥The nightly release channel latest tag is currently tracking the Nuxt v4 branch, meaning that it is particularly likely to have breaking changes right now - be careful!你可以选择使用 "nuxt": "npm:nuxt-nightly@3x" 加入 3.x 分支的夜间版本。¥You can opt in to the 3.x branch nightly releases with "nuxt": "npm:nuxt-nightly@3x".

测试 Nuxt 4

¥Testing Nuxt 4

Nuxt 4 的发布日期即将公布。它依赖于 Nitro 主要版本发布后是否有足够的时间在社区中进行适当的测试。你可以关注 Nitro 在 此 PR 中的发布进度。

¥The release date of Nuxt 4 is to be announced. It is dependent on having enough time after Nitro's major release to be properly tested in the community. You can follow progress towards Nitro's release in this PR.

在正式发布之前,可以从 Nuxt 3.12 及更高版本测试 Nuxt 4 的许多重大更改。

¥Until the release, it is possible to test many of Nuxt 4's breaking changes from Nuxt version 3.12+.

选择加入 Nuxt 4

¥Opting in to Nuxt 4

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

¥First, upgrade Nuxt to the latest release.

然后,你可以将 compatibilityVersion 设置为与 Nuxt 4 行为匹配:

¥Then you can set your compatibilityVersion to match Nuxt 4 behavior:

nuxt.config.ts
export default defineNuxtConfig({
  future: {
    compatibilityVersion: 4,
  },
  // To re-enable _all_ Nuxt v3 behavior, set the following options:
  // 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,
  //   defaults: {
  //     useAsyncData: {
  //       deep: true
  //     }
  //   }
  // },
  // features: {
  //   inlineStyles: true
  // },
  // unhead: {
  //   renderSSRHeadOptions: {
  //     omitLineBreaks: false
  //   }
  // }
})
目前,你需要在每个选择启用 Nuxt 4 行为的层中定义兼容版本。Nuxt 4 发布后,将不再需要执行此操作。

当你将 compatibilityVersion 设置为 4 时,整个 Nuxt 配置中的默认值将更改为启用 Nuxt v4 行为,但你可以在测试时按照上面注释掉的行,精细地重新启用 Nuxt v3 行为。如有问题,请提交问题,以便我们在 Nuxt 或生态系统中解决。

¥When you set your compatibilityVersion to 4, defaults throughout your Nuxt configuration will change to opt in to Nuxt v4 behavior, but you can granularly re-enable Nuxt v3 behavior when testing, following the commented out lines above. Please file issues if so, so that we can address them in Nuxt or in the ecosystem.

重大或重大变更将在此处注明,并说明为实现向后/向前兼容而进行的迁移步骤。

¥Breaking or significant changes will be noted here along with migration steps for backward/forward compatibility.

本节内容在最终版本发布前可能会有所变更,因此如果你正在使用 compatibilityVersion: 4 测试 Nuxt 4,请定期查看此处。

使用 Codemods 进行迁移

¥Migrating Using Codemods

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

¥To facilitate the upgrade process, we have collaborated with the Codemod team to automate many migration steps with some open-source codemods.

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

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

¥For a complete list of Nuxt 4 codemods, detailed information on each, their source, and various ways to run them, visit the Codemod Registry.

你可以使用以下 codemod 配方运行本指南中提到的所有代码模块:

¥You can run all the codemods mentioned in this guide using the following codemod recipe:

npx codemod@latest nuxt/4/migration-recipe

此命令将按顺序执行所有 codemod,并可以选择取消选择你不想运行的任何 codemod。每个代码修改及其相应的变更也列在下面,并且可以独立执行。

¥This command will execute all codemods in sequence, with the option to deselect any that you do not wish to run. Each codemod is also listed below alongside its respective change and can be executed independently.

新目录结构

¥New Directory Structure

🚦 影响等级:重要

¥🚦 Impact Level: Significant

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

¥Nuxt now defaults to a new directory structure, with backwards compatibility (so if Nuxt detects you are using the old structure, such as with a top-level pages/ directory, this new structure will not apply).

👉 参见完整的 RFC

¥👉 See full RFC

有哪些变化

¥What Changed

  • 新的 Nuxt 默认 srcDir 默认为 app/,大多数问题都从那里得到解决。
  • serverDir 现在默认为 <rootDir>/server 模块,而不是 <srcDir>/server 模块。
  • layers/modules/public/ 默认相对于 <rootDir> 解析。
  • 如果使用 Nuxt 内容 v2.13+content/ 是相对于 <rootDir> 解析的。
  • 添加了新的 dir.app,也就是我们查找 router.options.tsspa-loading-template.html 的目录 - 默认为 <srcDir>/
An example v4 folder structure.
.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

👉 更多关于编写 Vue 代码而不出现 Hylation Mismatch 问题的示例,请参阅 实现此变更的 PR

¥👉 For more details, see the PR implementing this change.

变更原因

¥Reasons for Change

  1. 性能 - 将所有代码放在代码库的根目录中会导致 .git/node_modules/ 文件夹被文件系统监控器扫描/包含时出现问题,这会显著延迟非 Mac OS 上的启动。
  2. IDE 类型安全 - server/ 和你应用的其余部分运行在两个完全不同的上下文中,并且可以使用不同的全局导入。确保 server/ 不与应用的其余部分位于同一文件夹中,是确保 IDE 获得良好自动补齐功能的第一步。

迁移步骤

¥Migration Steps

  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 来覆盖它。

¥However, migration is not required. If you wish to keep your current folder structure, Nuxt should auto-detect it. (If it does not, please raise an issue.) The one exception is that if you already have a custom srcDir. In this case, you should be aware that your modules/, public/ and server/ folders will be resolved from your rootDir rather than from your custom srcDir. You can override this by configuring dir.modules, dir.public and serverDir if you need to.

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

¥You can also force a v3 folder structure with the following configuration:

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 `app/router.options.ts` and `app/spa-loading-template.html`
  dir: {
    app: 'app'
  }
})

单例数据获取层

¥Singleton Data Fetching Layer

🚦 影响等级:中等

¥🚦 Impact Level: Moderate

有哪些变化

¥What Changed

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

¥Nuxt's data fetching system (useAsyncData and useFetch) has been significantly reorganized for better performance and consistency:

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

变更原因

¥Reasons for Change

这些更改是为了改善内存使用率,并提高 useAsyncData 调用之间加载状态的一致性。

¥These changes have been made to improve memory usage and increase consistency with loading states across calls of useAsyncData.

迁移步骤

¥Migration Steps

  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 调用提取到其自己的可组合项中可能会有所帮助:
    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]
    +  }
    })
    

或者,目前,你可以使用以下命令禁用此行为:

¥Alternatively, for now, you can disable this behaviour with:

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

路由元数据去重

¥Deduplication of Route Metadata

🚦 影响等级:最小

¥🚦 Impact Level: Minimal

有哪些变化

¥What Changed

可以使用 definePageMeta 设置一些路由元数据,例如 namepath 等等。之前,这些参数在路由和路由元数据中均可用(例如,route.nameroute.meta.name)。

¥It's possible to set some route metadata using definePageMeta, such as the name, path, and so on. Previously these were available both on the route and on route metadata (for example, route.name and route.meta.name).

现在,它们只能通过路由对象访问。

¥Now, they are only accessible on the route object.

变更原因

¥Reasons for Change

这是默认启用 experimental.scanPageMeta 的结果,是一种性能优化。

¥This is a result of enabling experimental.scanPageMeta by default, and is a performance optimization.

迁移步骤

¥Migration Steps

迁移过程应该很简单:

¥The migration should be straightforward:

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

规范化组件名称

¥Normalized Component Names

🚦 影响等级:中等

¥🚦 Impact Level: Moderate

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

¥Vue will now generate component names that match the Nuxt pattern for component naming.

有哪些变化

¥What Changed

默认情况下,如果你未手动设置,Vue 将分配一个与组件文件名匹配的组件名称。

¥By default, if you haven't set it manually, Vue will assign a component name that matches the filename of the component.

Directory structure
├─ components/
├─── SomeFolder/
├───── MyComponent.vue

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

¥In this case, the component name would be MyComponent, as far as Vue is concerned. If you wanted to use <KeepAlive> with it, or identify it in the Vue DevTools, you would need to use this name.

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

¥But in order to auto-import it, you would need to use SomeFolderMyComponent.

进行此更改后,这两个值将匹配,并且 Vue 将生成一个与 Nuxt 组件命名模式匹配的组件名称。

¥With this change, these two values will match, and Vue will generate a component name that matches the Nuxt pattern for component naming.

迁移步骤

¥Migration Steps

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

¥Ensure that you use the updated name in any tests which use findComponent from @vue/test-utils and in any <KeepAlive> which depends on the name of your component.

或者,目前,你可以使用以下命令禁用此行为:

¥Alternatively, for now, you can disable this behaviour with:

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

Unhead v2

🚦 影响等级:最小

¥🚦 Impact Level: Minimal

有哪些变化

¥What Changed

用于生成 <head> 标签的 Unhead 已更新至版本 2。虽然大多数情况下兼容,但它包含一些针对低级 API 的重大更改。

¥Unhead, used to generate <head> tags, has been updated to version 2. While mostly compatible it includes several breaking changes for lower-level APIs.

  • 已移除的属性:vmid, hid, children, body.
  • 不再支持 Promise 输入。
  • 标签现在默认使用 Capo.js 排序。

迁移步骤

¥Migration Steps

上述更改对你的应用的影响应该很小。

¥The above changes should have minimal impact on your app.

如果你遇到问题,请验证以下几点:

¥If you have issues you should verify:

  • 你没有使用任何已删除的属性。
useHead({
  meta: [{ 
    name: 'description', 
    // meta tags don't need a vmid, or a 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

¥While not required it's recommend to update any imports from @unhead/vue to #imports or nuxt/app.

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

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

¥If you still have issues you may revert to the v1 behavior by enabling the head.legacy config.

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

SPA 加载屏幕的新 DOM 位置

¥New DOM Location for SPA Loading Screen

🚦 影响等级:最小

¥🚦 Impact Level: Minimal

有哪些变化

¥What Changed

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

¥When rendering a client-only page (with ssr: false), we optionally render a loading screen (from app/spa-loading-template.html), within the Nuxt app root:

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

现在,我们默认将模板与 Nuxt 应用根目录一起渲染:

¥Now, we default to rendering the template alongside the Nuxt app root:

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

变更原因

¥Reasons for Change

这使得 spa 加载模板能够保留在 DOM 中,直到 Vue 应用悬念解决,从而避免出现白屏闪烁。

¥This allows the spa loading template to remain in the DOM until the Vue app suspense resolves, preventing a flash of white.

迁移步骤

¥Migration Steps

如果你使用 CSS 或 document.queryElement 定位 spa 加载模板,则需要更新选择器。为此,你可以将文件放在括号中的文件夹中。

¥If you were targeting the spa loading template with CSS or document.queryElement you will need to update your selectors. For this purpose you can use the new app.spaLoaderTag and app.spaLoaderAttrs configuration options.

或者,你可以使用以下命令恢复到之前的行为:

¥Alternatively, you can revert to the previous behaviour with:

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

已解析 error.data

¥Parsed error.data

🚦 影响等级:最小

¥🚦 Impact Level: Minimal

data 属性可能会引发错误,但该错误并未被解析。现在,它已被解析并在 error 对象中可用。虽然这只是一个修复,但如果你依赖之前的行为并手动解析,从技术上来说,这是一个重大更改。

¥It was possible to throw an error with a data property, but this was not parsed. Now, it is parsed and made available in the error object. Although a fix, this is technically a breaking change if you were relying on the previous behavior and parsing it manually.

迁移步骤

¥Migration Steps

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

¥Update your custom error.vue to remove any additional parsing of 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>

或者,你可以禁用此更改:

¥Alternatively, you can disable this change:

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

更精细的内联样式

¥More Granular Inline Styles

🚦 影响等级:中等

¥🚦 Impact Level: Moderate

Nuxt 现在只会为 Vue 组件添加内联样式,而不是全局 CSS。

¥Nuxt will now only inline styles for Vue components, not global CSS.

有哪些变化

¥What Changed

之前,Nuxt 会内联所有 CSS(包括全局样式),并将 <link> 元素移除到单独的 CSS 文件中。现在,Nuxt 将仅针对 Vue 组件执行此操作(之前 Vue 组件会生成单独的 CSS 块)。我们认为这在减少单独的网络请求(与以前一样,初始加载时不会针对每个页面或每个组件单独请求单独的 .css 文件)和允许缓存单个全局 CSS 文件以及减少初始请求的文档下载大小之间取得了更好的平衡。

¥Previously, Nuxt would inline all CSS, including global styles, and remove <link> elements to separate CSS files. Now, Nuxt will only do this for Vue components (which previously produced separate chunks of CSS). We think this is a better balance of reducing separate network requests (just as before, there will not be separate requests for individual .css files per-page or per-component on the initial load), as well as allowing caching of a single global CSS file and reducing the document download size of the initial request.

迁移步骤

¥Migration Steps

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

¥This feature is fully configurable and you can revert to the previous behavior by setting inlineStyles: true to inline global CSS as well as per-component CSS.

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

解析后扫描页面 Meta

¥Scan Page Meta After Resolution

🚦 影响等级:最小

¥🚦 Impact Level: Minimal

有哪些变化

¥What Changed

现在,我们会在调用 pages:extend 钩子之后(而不是之前)扫描页面元数据(在 definePageMeta 中定义)。

¥We now scan page metadata (defined in definePageMeta) after calling the pages:extend hook rather than before.

变更原因

¥Reasons for Change

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

¥This was to allow scanning metadata for pages that users wanted to add in pages:extend. We still offer an opportunity to change or override page metadata in a new pages:resolved hook.

迁移步骤

¥Migration Steps

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

¥If you want to override page metadata, do that in pages:resolved rather than in 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'
      }
    }
  })

或者,你可以使用以下命令恢复到之前的行为:

¥Alternatively, you can revert to the previous behaviour with:

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

共享预渲染数据

¥Shared Prerender Data

🚦 影响等级:中等

¥🚦 Impact Level: Medium

有哪些变化

¥What Changed

我们启用了一项之前处于实验阶段的功能,可以在不同页面之间共享来自 useAsyncDatauseFetch 调用的数据。请参阅 原始 PR

¥We enabled a previously experimental feature to share data from useAsyncData and useFetch calls, across different pages. See original PR.

变更原因

¥Reasons for Change

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

¥This feature automatically shares payload data between pages that are prerendered. This can result in a significant performance improvement when prerendering sites that use useAsyncData or useFetch and fetch the same data in different pages.

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

¥For example, if your site requires a useFetch call for every page (for example, to get navigation data for a menu, or site settings from a CMS), this data would only be fetched once when prerendering the first page that uses it, and then cached for use when prerendering other pages.

迁移步骤

¥Migration Steps

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

¥Make sure that any unique key of your data is always resolvable to the same data. For example, if you are using useAsyncData to fetch data related to a particular page, you should provide a key that uniquely matches that data. (useFetch should do this automatically for you.)

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

或者,你可以使用以下命令禁用此功能:

¥Alternatively, you can disable this feature with:

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

useAsyncDatauseFetch 中的默认 dataerror

¥Default data and error values in useAsyncData and useFetch

🚦 影响等级:最小

¥🚦 Impact Level: Minimal

有哪些变化

¥What Changed

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

¥data and error objects returned from useAsyncData will now default to undefined.

变更原因

¥Reasons for Change

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

¥Previously data was initialized to null but reset in clearNuxtData to undefined. error was initialized to null. This change is to bring greater consistency.

迁移步骤

¥Migration Steps

如果你正在检查 data.valueerror.value 是否为 null,则可以更新这些检查以改为检查 undefined

¥If you were checking if data.value or error.value were null, you can update these checks to check for undefined instead.

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

如果你遇到任何问题,可以使用以下方法恢复到之前的行为:

¥If you encounter any issues you can revert back to the previous behavior with:

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

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

¥Please report an issue if you are doing this, as we do not plan to keep this as configurable.

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

¥Removal of deprecated boolean values for dedupe option when calling refresh in useAsyncData and useFetch

🚦 影响等级:最小

¥🚦 Impact Level: Minimal

有哪些变化

¥What Changed

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

¥Previously it was possible to pass dedupe: boolean to refresh. These were aliases of cancel (true) and defer (false).

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

async function refreshData () {
  await refresh({ dedupe: true })
}

变更原因

¥Reasons for Change

为了更加清晰,这些别名已被删除。

¥These aliases were removed, for greater clarity.

问题出现在将 dedupe 作为 useAsyncData 的选项添加时,我们删除了布尔值,因为它们最终是相反的。

¥The issue came up when adding dedupe as an option to useAsyncData, and we removed the boolean values as they ended up being opposites.

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

¥refresh({ dedupe: false }) meant 'do not cancel existing requests in favour of this new one'. But passing dedupe: true within the options of useAsyncData means 'do not make any new requests if there is an existing pending request.' (See PR.)

迁移步骤

¥Migration Steps

迁移过程应该很简单:

¥The migration should be straightforward:

  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 时遵循默认值

¥Respect defaults when clearing data in useAsyncData and useFetch

🚦 影响等级:最小

¥🚦 Impact Level: Minimal

有哪些变化

¥What Changed

如果你为 useAsyncData 提供了自定义的 default 值,则现在将在调用 clearclearNuxtData 时使用该值,并且该值将重置为其默认值,而不是简单地取消设置。

¥If you provide a custom default value for useAsyncData, this will now be used when calling clear or clearNuxtData and it will be reset to its default value rather than simply unset.

变更原因

¥Reasons for Change

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

¥Often users set an appropriately empty value, such as an empty array, to avoid the need to check for null/undefined when iterating over it. This should be respected when resetting/clearing the data.

迁移步骤

¥Migration Steps

如果你遇到任何问题,可以使用以下方法恢复到之前的行为:

¥If you encounter any issues you can revert back to the previous behavior, for now, with:

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

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

¥Please report an issue if you are doing so, as we do not plan to keep this as configurable.

useAsyncDatauseFetchpending 值的对齐

¥Alignment of pending value in useAsyncData and useFetch

🚦 影响等级:中等

¥🚦 Impact Level: Medium

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

¥The pending object returned from useAsyncData, useFetch, useLazyAsyncData and useLazyFetch is now a computed property that is true only when status is also pending.

有哪些变化

¥What Changed

现在,当传递 immediate: false 时,pending 将为 false,直到发出第一个请求。这与以前的行为有所不同,以前的行为是,在发出第一个请求之前,pending 始终为 true

¥Now, when immediate: false is passed, pending will be false until the first request is made. This is a change from the previous behavior, where pending was always true until the first request was made.

变更原因

¥Reasons for Change

这将 pending 的含义与 status 属性保持一致,当请求正在进行时,status 属性的含义也为 pending

¥This aligns the meaning of pending with the status property, which is also pending when the request is in progress.

迁移步骤

¥Migration Steps

如果你依赖 pending 属性,请确保你的逻辑已考虑到新的行为:只有当状态也为 pending 时,pending 才会为 true

¥If you rely on the pending property, ensure that your logic accounts for the new behavior where pending will only be true when the status is also pending.

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

或者,你可以使用以下命令暂时恢复到之前的行为:

¥Alternatively, you can temporarily revert to the previous behavior with:

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

useAsyncData 中的浅层数据反应性 useFetch

¥Shallow Data Reactivity in useAsyncData and useFetch

🚦 影响等级:最小

¥🚦 Impact Level: Minimal

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

¥The data object returned from useAsyncData, useFetch, useLazyAsyncData and useLazyFetch is now a shallowRef rather than a ref.

有哪些变化

¥What Changed

获取新数据时,任何依赖于 data 的内容仍将响应式,因为整个对象都会被替换。但是,如果你的代码更改了该数据结构中的属性,这不会在你的应用中触发任何响应。

¥When new data is fetched, anything depending on data will still be reactive because the entire object is replaced. But if your code changes a property within that data structure, this will not trigger any reactivity in your app.

变更原因

¥Reasons for Change

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

¥This brings a significant performance improvement for deeply nested objects and arrays because Vue does not need to watch every single property/array for modification. In most cases, data should also be immutable.

迁移步骤

¥Migration Steps

在大多数情况下,无需迁移步骤,但如果你依赖数据对象的响应性,则有两种选择:

¥In most cases, no migration steps are required, but if you rely on the reactivity of the data object then you have two options:

  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 中的绝对监视路径

¥Absolute Watch Paths in builder:watch

🚦 影响等级:最小

¥🚦 Impact Level: Minimal

有哪些变化

¥What Changed

Nuxt builder:watch hook 现在会发出一个绝对路径,而不是相对于项目 srcDir 的相对路径。

¥The Nuxt builder:watch hook now emits a path which is absolute rather than relative to your project srcDir.

变更原因

¥Reasons for Change

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

¥This allows us to support watching paths which are outside your srcDir, and offers better support for layers and other more complex patterns.

迁移步骤

¥Migration Steps

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

¥We have already proactively migrated the public Nuxt modules which we are aware use this hook. See issue #25339.

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

¥However, if you are a module author using the builder:watch hook and wishing to remain backwards/forwards compatible, you can use the following code to ensure that your code works the same in both Nuxt v3 and 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__ 对象

¥Removal of window.__NUXT__ object

有哪些变化

¥What Changed

我们将在应用完成 hydration 后移除全局 window.__NUXT__ 对象。

¥We are removing the global window.__NUXT__ object after the app finishes hydration.

变更原因

¥Reasons for Change

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

¥This opens the way to multi-app patterns (#21635) and enables us to focus on a single way to access Nuxt app data - useNuxtApp().

迁移步骤

¥Migration Steps

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

¥The data is still available, but can be accessed with useNuxtApp().payload:

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

目录索引扫描

¥Directory index scanning

🚦 影响等级:中等

¥🚦 Impact Level: Medium

有哪些变化

¥What Changed

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

¥Child folders in your middleware/ folder are also scanned for index files and these are now also registered as middleware in your project.

变更原因

¥Reasons for Change

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

¥Nuxt scans a number of folders automatically, including middleware/ and plugins/.

plugins/ 文件夹中的子文件夹也会被扫描以查找 index 文件,我们希望使此行为在扫描的目录中保持一致。

¥Child folders in your plugins/ folder are scanned for index files and we wanted to make this behavior consistent between scanned directories.

迁移步骤

¥Migration Steps

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

¥Probably no migration is necessary but if you wish to revert to previous behavior you can add a hook to filter out these middleware:

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

模板编译变更

¥Template Compilation Changes

🚦 影响等级:最小

¥🚦 Impact Level: Minimal

有哪些变化

¥What Changed

之前,Nuxt 使用 lodash/template 编译位于文件系统上、使用 .ejs 文件格式/语法的模板。

¥Previously, Nuxt used lodash/template to compile templates located on the file system using the .ejs file format/syntax.

此外,我们还提供了一些模板实用程序(serializeimportNameimportSources),可用于在这些模板中进行代码生成,这些实用程序现已被移除。

¥In addition, we provided some template utilities (serialize, importName, importSources) which could be used for code-generation within these templates, which are now being removed.

变更原因

¥Reasons for Change

在 Nuxt v3 中,我们迁移到了 'virtual' 语法和 getContents() 函数,后者更加灵活,性能也更高。

¥In Nuxt v3 we moved to a 'virtual' syntax with a getContents() function which is much more flexible and performant.

此外,lodash/template 存在一系列安全问题。这些默认值实际上并不适用于 Nuxt 项目,因为它是在构建时(而不是运行时)由受信任的代码使用的。然而,它们仍然会出现在安全审核中。此外,lodash 是一个很大的依赖,大多数项目都未使用。

¥In addition, lodash/template has had a succession of security issues. These do not really apply to Nuxt projects because it is being used at build-time, not runtime, and by trusted code. However, they still appear in security audits. Moreover, lodash is a hefty dependency and is unused by most projects.

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

¥Finally, providing code serialization functions directly within Nuxt is not ideal. Instead, we maintain projects like unjs/knitwork which can be dependencies of your project, and where security issues can be reported/resolved directly without requiring an upgrade of Nuxt itself.

迁移步骤

¥Migration Steps

我们已经提交了 PR 以使用 EJS 语法更新模块,但如果你需要自行操作,你有三种向后/向前兼容的替代方案:

¥We have raised PRs to update modules using EJS syntax, but if you need to do this yourself, you have three backwards/forwards-compatible alternatives:

  • 将字符串插值逻辑直接移动到 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 中的实用程序:

¥Finally, if you are using the template utilities (serialize, importName, importSources), you can replace them as follows with utilities from 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 来自动执行此步骤

移除实验性功能

¥Removal of Experimental Features

🚦 影响等级:最小

¥🚦 Impact Level: Minimal

有哪些变化

¥What Changed

、 或 等框架函数仍可正常工作,无需手动导入。

¥Four experimental features are no longer configurable in 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

变更原因

¥Reasons for Change

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

¥These options have been set to their current values for some time and we do not have a reason to believe that they need to remain configurable.

迁移步骤

¥Migration Steps

Nuxt 2 与 Nuxt 3+

¥Nuxt 2 vs. Nuxt 3+

下表简要比较了三个版本的 Nuxt:

¥In the table below, there is a quick comparison between 3 versions of Nuxt:

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

Nuxt 2 到 Nuxt 3+

¥Nuxt 2 to Nuxt 3+

迁移指南逐步比较了 Nuxt 2 和 Nuxt 3+ 的功能,并指导你如何调整当前的应用。

¥The migration guide provides a step-by-step comparison of Nuxt 2 features to Nuxt 3+ features and guidance to adapt your current application.

:

Read more in Docs > Migration > Overview.

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

¥Check out the guide to migrating from Nuxt 2 to Nuxt 3.

::

Nuxt Nuxt 2 到 Nuxt 桥接

¥Nuxt 2 to Nuxt Bridge

如果你希望逐步将 Nuxt 2 应用迁移到 Nuxt 3,则可以使用 Nuxt Bridge。Nuxt Bridge 是一个兼容层,允许你通过选择加入机制在 Nuxt 2 中使用 Nuxt 3+ 功能。

¥If you prefer to progressively migrate your Nuxt 2 application to Nuxt 3, you can use Nuxt Bridge. Nuxt Bridge is a compatibility layer that allows you to use Nuxt 3+ features in Nuxt 2 with an opt-in mechanism.

:

Read more in Docs > Bridge > Overview.

从 Nuxt 2 迁移到 Nuxt Bridge

¥Migrate from Nuxt 2 to Nuxt Bridge

::