升级指南

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

升级Nuxt

最新版本

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

npx nuxt upgrade

夜间发布通道

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

迁移到Nuxt 4

Nuxt 4包含重大改进和变更。本指南将帮助你将现有的Nuxt 3应用迁移到Nuxt 4。

首先,升级到Nuxt 4:

npm install nuxt@^4.0.0

升级后,大多数Nuxt 4的行为已成为默认值。不过,如果你需要在迁移过程中保持向后兼容性,部分功能仍可配置。

以下部分详细介绍了升级到Nuxt 4所需的关键变更和迁移步骤。

下方记录了突破性或重大变更,以及迁移步骤和可用的配置选项。

使用代码转换工具(Codemods)迁移

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

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

有关Nuxt 4代码转换工具的完整列表、每个工具的详细信息、来源以及各种运行方式,请访问代码转换工具注册表

你可以使用以下codemod命令运行本指南中提到的所有代码转换工具:

npx codemod@latest nuxt/4/migration-recipe

此命令将按顺序执行所有代码转换工具,并允许你取消选择不希望运行的工具。每个代码转换工具也会在相应的变更下方列出,可单独执行。

新目录结构

🚦 影响级别:重大

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>/
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系统上可能显著延迟启动。
  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({
  // 将新的srcDir默认值从`app`恢复为根目录
  srcDir: '.',
  // 指定`router.options.ts`和`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
  }
})

图层中模块加载顺序的修正

🚦 影响级别:最小

变更内容

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

现在模块按正确顺序加载:

  1. 首先是图层模块(按扩展顺序 - 更深层的图层优先)
  2. 最后是项目模块(最高优先级)

这影响:

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

变更原因

此变更确保:

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

迁移步骤

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

不过,如果你的项目依赖于以前的错误顺序,可能需要:

  1. 检查模块依赖:检查是否有模块依赖特定的加载顺序
  2. 调整模块配置:如果模块是为解决错误顺序而配置的
  3. 彻底测试:确保所有功能在修正后的顺序下正常工作

新正确顺序的示例:

// 图层:my-layer/nuxt.config.ts
export default defineNuxtConfig({
  modules: ['layer-module-1', 'layer-module-2']
})

// 项目:nuxt.config.ts
export default defineNuxtConfig({
  extends: ['./my-layer'],
  modules: ['project-module-1', 'project-module-2']
})

// 加载顺序(已修正):
// 1. layer-module-1
// 2. layer-module-2  
// 3. project-module-1(可覆盖图层模块)
// 4. project-module-2(可覆盖图层模块)

如果遇到因需要注册钩子而导致的模块顺序依赖问题,考虑对需要调用钩子的模块使用modules:done钩子。它在所有其他模块加载后运行,因此使用是安全的。

👉 更多细节见PR #31507issue #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

🚦 影响级别:最小

变更内容

用于生成<head>标签的Unhead已更新到版本2。虽然大部分兼容,但低级别API有一些突破性变更。

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

迁移步骤

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

如果遇到问题,应验证:

  • 你没有使用任何已移除的属性。
useHead({
  meta: [{ 
    name: 'description', 
    // meta标签不需要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 4中已改为~/spa-loading-template.html),位于Nuxt应用根目录内:

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

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

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

变更原因

这允许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,包括全局样式,并移除指向单独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
  }
})

useAsyncDatauseFetchdataerror的默认值

🚦 影响级别:最小

变更内容

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,因为我们不打算保留此可配置项。

移除useAsyncDatauseFetchrefresh调用时dedupe选项的已弃用boolean

🚦 影响级别:最小

变更内容

以前可以向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。重置/清除数据时应尊重这一点。

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 }}</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模块。参见issue #25339

不过,如果你是模块作者且使用builder:watch钩子,并希望保持向后/向前兼容性,可以使用以下代码确保你的代码在Nuxt v3和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__对象

变更内容

应用完成 hydration 后,我们将移除全局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语法的模块,但如果你需要自己执行此操作,有三种向后/向前兼容的替代方案:

+ 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自动化此步骤

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 - 用于向后兼容的遗留配置
  1. 向后兼容性:扩展.nuxt/tsconfig.json的现有项目将继续正常工作。
  2. 可选的项目引用:新项目或希望获得更好类型检查的项目可以采用TypeScript的项目引用功能。
  3. 特定上下文的类型检查:每个上下文现在有适当的编译器选项以及针对其特定环境的包含/排除项。
  4. 新的typescript.nodeTsConfig选项:现在可以自定义Node.js构建时代码的TypeScript配置。

变更原因

此变更提供以下好处:

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

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

迁移步骤

无需迁移 - 现有项目将继续正常工作。

不过,要利用改进的类型检查,可以选择采用新的项目引用方法:

  1. **更新根目录tsconfig.json**以使用项目引用:
    {
      "files": [],
      "references": [
        { "path": "./.nuxt/tsconfig.app.json" },
        { "path": "./.nuxt/tsconfig.server.json" },
        { "path": "./.nuxt/tsconfig.shared.json" },
        { "path": "./.nuxt/tsconfig.node.json" }
      ]
    }
    
  2. **移除任何手动的服务器tsconfig.json**文件(如server/tsconfig.json),这些文件扩展了.nuxt/tsconfig.server.json
  3. 更新类型检查脚本以使用项目引用的构建标志:
    - "typecheck": "nuxt prepare && vue-tsc --noEmit"
    + "typecheck": "nuxt prepare && vue-tsc -b --noEmit"
    
  4. 根据需要配置Node.js TypeScript选项
    export default defineNuxtConfig({
      typescript: {
        // 自定义应用/服务器TypeScript配置
        tsConfig: {
          compilerOptions: {
            strict: true
          }
        },
        // 自定义构建时TypeScript配置  
        nodeTsConfig: {
          compilerOptions: {
            strict: true
          }
        }
      }
    })
    
  5. 更新任何运行TypeScript检查的CI/构建脚本,确保它们使用新的项目引用方法。

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

移除实验性功能

🚦 影响级别:最小

变更内容

四个实验性功能在Nuxt 4中不再可配置:

  • experimental.treeshakeClientOnly将为true(自v3.0起默认)
  • experimental.configSchema将为true(自v3.3起默认)
  • experimental.polyfillVueUseHead将为false(自v3.4起默认)
  • experimental.respectNoSSRHeader将为false(自v3.4起默认)
  • vite.devBundler不再可配置 - 默认使用vite-node

变更原因

这些选项已设置为当前值一段时间,我们认为没有必要保持它们的可配置性。

迁移步骤

移除顶级generate配置

🚦 影响级别:最小

变更内容

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

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

变更原因

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

迁移步骤

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

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🚧 部分
选项式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