Lzh on GitHub

升级指南

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

升级 Nuxt

最新版本

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

npx nuxi upgrade

Nightly 发布通道

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

nightly 发布通道的 latest 标签目前跟踪 Nuxt v4 分支,这意味着现在尤其可能出现破坏性更改 - 请务必小心!您可以使用 "nuxt": "npm:nuxt-nightly@3x" 选择加入 3.x 分支的 nightly 版本。

测试 Nuxt 4

Nuxt 4 的发布日期 待定。这取决于 Nitro 主要版本发布后是否有足够的时间在社区中进行充分测试。您可以在 此 PR 中关注 Nitro 版本发布的进展。

在发布之前,可以从 Nuxt 3.12+ 版本开始测试 Nuxt 4 的许多破坏性更改。

选择加入 Nuxt 4

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

然后,您可以设置您的 compatibilityVersion 以匹配 Nuxt 4 的行为:

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,
  //   alwaysRunFetchOnKeyChange: true,
  //   defaults: {
  //     useAsyncData: {
  //       deep: true
  //     }
  //   }
  // },
  // features: {
  //   inlineStyles: true
  // },
  // unhead: {
  //   renderSSRHeadOptions: {
  //     omitLineBreaks: false
  //   }
  // }
})
目前,您需要在每个选择加入 Nuxt 4 行为的 layer 中定义兼容性版本。Nuxt 4 发布后将不再需要这样做。

当您将 compatibilityVersion 设置为 4 时,整个 Nuxt 配置的默认值将更改为选择加入 Nuxt v4 的行为,但在测试时,您可以按照上面注释掉的行,细粒度地重新启用 Nuxt v3 的行为。如果遇到问题,请提交 issue,以便我们可以在 Nuxt 或生态系统中解决这些问题。

破坏性或重大更改将在此处注明,并提供向后/向前兼容的迁移步骤。

本节在最终发布之前可能会发生更改,如果您使用 compatibilityVersion: 4 测试 Nuxt 4,请定期查看此处。

使用 Codemod 进行迁移

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

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

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

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

npx codemod@latest nuxt/4/migration-recipe

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

新的目录结构

🚦 影响级别: 重大

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

👉 查看完整的 RFC

更改内容

  • 新的 Nuxt 默认 srcDir 默认为 app/,并且大多数内容都从那里解析。
  • serverDir 现在默认为 <rootDir>/server 而不是 <srcDir>/server
  • layers/modules/public/ 默认相对于 <rootDir> 解析
  • 如果使用 Nuxt Content v2.13+,则 content/ 相对于 <rootDir> 解析
  • 添加了一个新的 dir.app,它是我们查找 router.options.tsspa-loading-template.html 的目录 - 这默认为 <srcDir>/
一个 v4 文件夹结构的示例。
.output/
.nuxt/
app/
  assets/
  components/
  composables/
  layouts/
  middleware/
  pages/
  plugins/
  utils/
  app.config.ts
  app.vue
  router.options.ts
content/
layers/
modules/
node_modules/
public/
server/
  api/
  middleware/
  plugins/
  routes/
  utils/
nuxt.config.ts

👉 更多详情,请参阅 实现此更改的 PR.

更改原因

  1. 性能 - 将所有代码放在存储库的根目录会导致 .git/node_modules/ 文件夹被 FS 监视器扫描/包含的问题,这会显着延迟非 Mac OS 上的启动。
  2. IDE 类型安全 - server/ 和应用程序的其余部分在两个完全不同的上下文中使用不同的全局导入运行,确保 server/ 不在与应用程序其余部分相同的文件夹中是确保在 IDE 中获得良好自动完成功能的第一大步。

迁移步骤

  1. 创建一个名为 app/ 的新目录。
  2. 将您的 assets/components/composables/layouts/middleware/pages/plugins/utils/ 文件夹以及 app.vueerror.vueapp.config.ts 移动到该目录下。如果您有 app/router-options.tsapp/spa-loading-template.html,这些路径保持不变。
  3. 确保您的 nuxt.config.tscontent/layers/modules/public/server/ 文件夹保留在 app/ 文件夹之外,位于项目的根目录中。
  4. 请记住更新任何第三方配置文件以使用新的目录结构,例如您的 tailwindcsseslint 配置(如果需要 - @nuxtjs/tailwindcss 应该会自动正确配置 tailwindcss)。
您可以通过运行 npx codemod@latest nuxt/4/file-structure 自动执行此迁移。

但是,不需要迁移。如果您希望保留当前的文件夹结构,Nuxt 应该会自动检测到它。(如果未检测到,请提出 issue。)唯一的例外是,如果您 已经 有一个自定义的 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 `app/router.options.ts` and `app/spa-loading-template.html`
  dir: {
    app: 'app'
  }
})

单例数据获取层

🚦 影响级别: 中等

更改内容

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

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

更改原因

进行这些更改是为了提高内存使用率并增强 useAsyncData 的不同调用之间加载状态的一致性。

迁移步骤

  1. 检查不一致的选项: 检查任何使用相同 key 但具有不同选项或 fetch 函数的组件。
    // 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 })
    

    将任何共享显式 key(并具有自定义选项)的 useAsyncData 调用提取到它们自己的 composable 中可能是有益的:
    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
  }
})

路由元数据的去重

🚦 影响级别: 极小

更改内容

可以使用 definePageMeta 设置一些路由元数据,例如 namepath 等。以前,这些数据既可以在路由对象上访问,也可以在路由元数据上访问(例如,route.nameroute.meta.name)。

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

更改原因

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

迁移步骤

迁移应该很简单:

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

规范化的组件名称

🚦 影响级别: 中等

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

更改内容

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

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

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

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

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

迁移步骤

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

或者,目前,您可以使用以下方式禁用此行为:

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

Unhead v2

🚦 影响级别: 极小

更改内容

用于生成 <head> 标签的 Unhead 已更新至版本 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 { 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)时,我们会选择性地在 Nuxt 应用程序根目录下渲染一个加载屏幕(来自 app/spa-loading-template.html):

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

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

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

更改原因

这允许 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,包括全局样式,并删除 <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 data。当预渲染使用 useAsyncDatauseFetch 并在不同页面中获取相同数据的站点时,这可以显着提高性能。

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

迁移步骤

确保数据的任何唯一 key 始终可以解析为相同的数据。例如,如果您使用 useAsyncData 获取与特定页面相关的数据,则应提供一个唯一匹配该数据的 key。(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 自动执行此步骤。

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

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

如果您这样做,请报告一个 issue,因为我们不打算将其保留为可配置项。

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

🚦 影响级别: 极小

更改内容

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

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

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

更改原因

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

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

refresh({ dedupe: false }) 的意思是 “不要为了这个新的请求而 取消 现有的请求”。但是在 useAsyncData 的选项中传递 dedupe: true 的意思是 “如果存在正在进行的请求,则不要发出任何新的请求。”(请参阅 PR)。

迁移步骤

迁移应该很简单:

  const { refresh } = await useAsyncData(async () => ({ message: 'Hello, Nuxt 3!' }))
  
  async function refreshData () {
-   await refresh({ dedupe: true })
+   await refresh({ dedupe: 'cancel' })

-   await refresh({ dedupe: false })
+   await refresh({ dedupe: 'defer' })
  }
您可以通过运行 npx codemod@latest nuxt/4/deprecated-dedupe-value 自动执行此步骤。

在清除 useAsyncDatauseFetch 中的 data 时遵循默认值

🚦 影响级别: 极小

更改内容

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

更改原因

通常,用户会设置一个适当的空值,例如一个空数组,以避免在迭代数据时需要检查 null/undefined。在重置/清除数据时,应该尊重这个默认值。

迁移步骤

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

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

如果您这样做,请报告一个 issue,因为我们不打算将其保留为可配置项。

useAsyncDatauseFetchpending 值的对齐

🚦 影响级别: 中等

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

更改内容

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

更改原因

这使 pending 的含义与 status 属性对齐,后者在请求进行中时也为 pending

迁移步骤

如果您依赖 pending 属性,请确保您的逻辑考虑了新的行为,即 pending 只有在 status 也为 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 中的 Key 更改行为

🚦 影响级别: 中等

更改内容

当在 useAsyncDatauseFetch 中使用响应式 key 时,Nuxt 会在 key 更改时自动重新获取数据。当设置了 immediate: false 时,useAsyncData 只有在数据已经获取过一次后,才会因为 key 的更改而重新获取数据。

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

现在,useFetchuseAsyncData 的行为保持一致 - 只有在数据已经获取过一次后,才会因为 key 的更改而获取数据。

更改原因

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

迁移步骤

此更改通常会改进预期行为,但如果您之前期望更改非立即执行的 useFetch 的 key 或选项,那么现在您需要在第一次手动触发它。

  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. 您可以基于每个 composable 细粒度地选择加入深度响应性:
    - 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 之外的路径,并为 layers 和其他更复杂的模式提供更好的支持。

迁移步骤

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

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

+ import { relative, resolve } from 'node:fs'
  // ...
  nuxt.hook('builder:watch', async (event, path) => {
+   path = relative(nuxt.options.srcDir, resolve(nuxt.options.srcDir, path))
    // ...
  })
您可以通过运行 npx codemod@latest nuxt/4/absolute-watch-path 自动执行此步骤。

移除 window.__NUXT__ 对象

更改内容

在应用程序完成水合后,我们将删除全局 window.__NUXT__ 对象。

更改原因

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

迁移步骤

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

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

目录索引扫描

🚦 影响级别: 中等

更改内容

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

更改原因

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

会扫描 plugins/ 文件夹中的子文件夹以查找 index 文件,我们希望使扫描目录之间的这种行为保持一致。

迁移步骤

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

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

模板编译更改

🚦 影响级别: 极小

更改内容

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

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

更改原因

在 Nuxt v3 中,我们转向使用带有 getContents() 函数的 “虚拟” 语法,这种语法更加灵活和高效。

此外,lodash/template 曾多次出现安全问题。这些问题实际上并不适用于 Nuxt 项目,因为它是在构建时而不是运行时使用,并且由受信任的代码使用。但是,它们仍然会出现在安全审计中。此外,lodash 是一个庞大的依赖项,并且大多数项目都不使用它。

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

迁移步骤

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

  • 将您的字符串插值逻辑直接移动到 getContents() 中。
  • 使用自定义函数来处理替换,例如在 https://github.com/nuxt-modules/color-mode/pull/240 中所示。
  • 使用 es-toolkit/compat(lodash template 的直接替代品)作为 您的 项目的依赖项,而不是 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 自动执行此步骤。

移除实验性功能

🚦 影响级别: 极小

更改内容

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:

Feature / VersionNuxt 2Nuxt BridgeNuxt 3+
Vue223
Stability😊 Stable😊 Stable😊 Stable
Performance🏎 Fast✈️ Faster🚀 Fastest
Nitro Engine
ESM support🌙 Partial👍 Better
TypeScript☑️ Opt-in🚧 Partial
Composition API🚧 Partial
Options API
Components Auto Import
<script setup> syntax🚧 Partial
Auto Imports
webpack445
Vite⚠️ Partial🚧 Partial
Nuxi CLI❌ Old✅ nuxi✅ nuxi
Static sites

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