Nuxt 3 已于 31 July 2026 结束生命周期
此版本将不再接收错误修复或安全补丁。 升级到 Nuxt 4 或获取 HeroDevs 提供的延长支持

升级指南

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

Maschinelle Übersetzung ·2 % geprüft

升级 Nuxt

最新版本

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

npx nuxt upgrade

每日构建版本渠道

要使用最新的 Nuxt 构建版本,并在功能发布前进行测试,请阅读每夜构建版本渠道指南。

每日构建版本渠道 latest 标签目前跟踪 Nuxt v4 分支,这意味着现在特别可能存在破坏性更改 —— 请务必小心!你可以通过 "nuxt": "npm:nuxt-nightly@3x" 选择使用 3.x 分支的每日构建版本。

测试 Nuxt 4

Nuxt 4 计划于 2025 年第二季度发布。它将包含所有当前通过 compatibilityVersion: 4 可用的功能。

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

选择使用 Nuxt 4

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

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

export default defineNuxtConfig({
  future: {
    compatibilityVersion: 4,
  },
  // 若要重新启用 _全部_ Nuxt v3 的行为,请设置以下选项:
  // srcDir: '.',
  // dir: {
  //   app: 'app'
  // },
  // experimental: {
  //   scanPageMeta: 'after-resolve',
  //   sharedPrerenderData: false,
  //   compileTemplate: true,
  //   resetAsyncDataToUndefined: true,
  //   templateUtils: true,
  //   relativeWatchPaths: true,
  //   normalizeComponentNames: false,
  //   spaLoadingTemplateLocation: 'within',
  //   parseErrorData: false,
  //   pendingWhenIdle: true,
  //   alwaysRunFetchOnKeyChange: true,
  //   defaults: {
  //     useAsyncData: {
  //       deep: true
  //     }
  //   }
  // },
  // features: {
  //   inlineStyles: true
  // },
  // unhead: {
  //   renderSSRHeadOptions: {
  //     omitLineBreaks: false
  //   }
  // }
})
目前,你需要在每个选择 Nuxt 4 行为的层内定义 compatibilityVersion。在 Nuxt 4 发布后,将不再需要这样做。

当你将 compatibilityVersion 设置为 4 时,Nuxt 配置中的默认值将更改以适应 Nuxt v4 行为,但你可以根据上面注释的行内容,在测试中逐步重新启用 Nuxt v3 的行为。如果遇到问题,请提交 issue,方便我们在 Nuxt 或生态系统中修复。

重大或破坏性变更将会在这里进行说明,并提供向前/向后兼容的迁移步骤。

此部分内容直到最终发布前可能会有变化,如果你正在使用 compatibilityVersion: 4 测试 Nuxt 4,请定期回来查看。

使用 Codemods 迁移

为了简化升级流程,我们与 Codemod 团队合作,提供了一些开源的代码转换工具(codemods)来自动执行许多迁移步骤。

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

有关完整的 Nuxt 4 codemods 列表、每个工具的详细信息、源码和多种运行方式,请访问 Codemod 注册表。

你可以使用以下 codemod 命令执行本指南中提到的所有 codemods:

# Angepinnte Version wegen https://github.com/codemod/codemod/issues/1710
npx codemod@0.18.7 nuxt/4/migration-recipe

该命令将会按顺序运行所有 codemods,你还可以选择不执行某些不想运行的;每个 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.ts 和 spa-loading-template.html 的目录——默认值为 <srcDir>/
  • 新增了 shared/ 目录,用于存放 Vue 应用和 Nitro 服务器之间共享的代码,并为 shared/utils/ 和 shared/types/ 提供自动导入
示例 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/
  types/
  utils/
server/
  api/
  middleware/
  plugins/
  routes/
  utils/
nuxt.config.ts
在此新结构中,~ 别名默认指向 app/ 目录(你的 srcDir)。这意味着 ~/components 解析为 components/,~/pages 解析为 pages/,依此类推。

👉 详情可见 实现此变更的 PR。

变更原因

  1. 性能 — 把代码全部放在项目根目录会导致 .git/ 和 node_modules/ 文件夹被文件系统监听器扫描/包含,这可能在非 macOS 系统上显著延长启动时间。
  2. IDE 类型安全 — server/ 目录与应用其余部分运行在两个完全不同的环境中,拥有不同的全局导入。确保 server/ 不在应用程序其余部分的相同目录中是实现 IDE 良好自动补全的第一大步。

迁移步骤

  1. 创建一个名为 app/ 的新目录。
  2. 将 assets/、components/、composables/、layouts/、middleware/、pages/、plugins/ 和 utils/ 文件夹,以及 app.vue、error.vue、app.config.ts 移入其中。如果你有 app/router-options.ts 或 app/spa-loading-template.html,这些路径保持不变。
  3. 确保 nuxt.config.ts、content/、layers/、modules/、public/、shared/ 和 server/ 文件夹保留在 app/ 文件夹之外,即项目根目录中。
  4. 记得更新任何第三方配置文件,以适配新的目录结构,例如你的 tailwindcss 或 eslint 配置(如果需要——@nuxtjs/tailwindcss 应能自动正确配置 tailwindcss)。
你可以通过运行 npx codemod@latest nuxt/4/file-structure 来自动化此迁移。

不过,迁移并非_必需_。如果你希望保留当前的文件夹结构,Nuxt 应能自动检测到它。(如果没有,请提交 issue。)唯一的例外是你_已经_设置了自定义 srcDir。在这种情况下,你应该注意,modules/、public/、shared/ 和 server/ 文件夹将从 rootDir 而不是自定义 srcDir 解析。如果有需要,可以通过配置 dir.modules、dir.public 和 serverDir 来覆盖此行为。

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

nuxt.config.ts
export default defineNuxtConfig({
  // 将新的 srcDir 默认值从 `app` 改回根目录
  srcDir: '.',
  // 指定 `app/router.options.ts` 和 `app/spa-loading-template.html` 的目录前缀
  dir: {
    app: 'app',
  },
})

单例数据获取层

🚦 影响等级:中等

变化内容

Nuxt 的数据获取系统 (useAsyncData 和 useFetch) 已经被大幅重构,以提升性能和一致性:

  1. 相同 key 共享 refs:所有调用 useAsyncData 或 useFetch 并使用相同 key 的调用都会共享同一份 data、error 和 status refs。这意味着所有带显式 key 的调用的选项 deep、transform、pick、getCachedData 或 default 不能冲突。
  2. 对 getCachedData 更多控制:现在无论数据是否来自 watcher 还是通过调用 refreshNuxtData,每次请求数据时都会调用 getCachedData。之前这些场景下会强制重新获取数据而不调用该函数。为了更灵活地控制何时使用缓存数据,函数接收的上下文参数中包含了请求来源。
  3. 支持响应式 key:现在可使用计算的 refs、普通 refs 或 getter 函数作为 key,从而支持自动数据刷新(并分开存储数据)。
  4. 数据清理:当最后一个使用了某条 useAsyncData 获取数据的组件卸载时,Nuxt 会移除这条数据,避免内存持续增长。

变更原因

这些修改是为了改善内存使用效率和提升多个调用之间加载状态的一致性。

迁移步骤

  1. 检查不一致的选项:审查任何对相同 key 使用不一致选项或获取函数的组件。

    // 这将触发警告
    const { data: users1 } = useAsyncData('users', () => $fetch('/api/users'), { deep: false })
    const { data: users2 } = useAsyncData('users', () => $fetch('/api/users'), { deep: true })

    建议将对同一明确 key 的调用(同时含自定义选项)提取到独立的组合函数中:

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

你也可以通过以下配置关闭该行为:

export default defineNuxtConfig({
  experimental: {
    granularCachedData: false,
    purgeCachedData: false,
  },
})

修正 Layers 中模块加载顺序

🚦 影响等级:最低

变化内容

使用 Nuxt layers 时,模块的加载顺序已修正。之前,项目根目录中的模块会先于扩展层中的模块加载,这与预期顺序相反。

现在模块加载顺序正确:

  1. 先加载 Layer 模块(按继承顺序,较深层先)
  2. 后加载项目模块(最高优先级)

影响包括:

  • 在 nuxt.config.ts 的 modules 数组中声明的模块
  • modules/ 目录的自动发现模块

变更原因

此变化确保:

  • 扩展层优先级低于使用它的项目
  • 模块执行顺序符合直观的层级继承模式
  • 多层结构中模块配置和钩子正常工作

迁移步骤

大多数项目无需更改,该修正使加载顺序与预期一致。

但如果你的项目依赖之前的错误顺序,可能需要:

  1. 检查模块依赖:确认模块加载顺序是否有严格依赖
  2. 调整模块配置:如果有为适应旧顺序的配置,进行调整
  3. 全面测试:确保功能正常且顺序正确

正确顺序示例:

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

// Projekt: 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(可以覆盖 layer 模块)
// 4. project-module-2(可以覆盖 layer 模块)

如果由于需要注册钩子而遇到模块顺序依赖问题,可以考虑在需要调用钩子的模块中使用 modules:done hook。该钩子会在所有其他模块加载完毕后运行,因此可以安全使用。

👉 详情见 PR #31507 及 issue #25719。

路由元数据去重

🚦 影响等级:最低

变化内容

通过 definePageMeta 设置的某些路由元数据(如 name、path 等),之前既存在于 route 对象上,也存在于路由元数据中(例如 route.name 与 route.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-utils 的 findComponent)和 <KeepAlive> 标签使用更新后的名称。

你也可以暂时通过以下配置关闭该行为:

export default defineNuxtConfig({
  experimental: {
    normalizeComponentNames: false,
  },
})

Unhead v2

🚦 影响等级:最低

变化内容

生成 <head> 标签的 Unhead 更新至版本 2。大多数兼容,但底层 API 有若干破坏性变更:

  • 移除了属性:vmid、hid、children、body。
  • 不再支持 Promise 类型输入。
  • 标签排序默认使用 Capo.js。

迁移步骤

上述变更对应用影响较小。

确保未使用已移除属性:

useHead({
  meta: [{ 
    name: 'description', 
    // meta 标签不再需要 vmid 或 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 的导入更新为 #imports 或 nuxt/app。

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

若仍有问题,可通过开启旧版模式恢复 v1 行为:

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

SPA 加载屏幕的新 DOM 位置

🚦 影响等级:最低

变化内容

客户端渲染页面(ssr: false)时,Nuxt 加载屏幕(取自 app/spa-loading-template.html)默认渲染位置由:

<div id="__nuxt">
  <!-- SPA-Ladevorlage -->
</div>

变更为:

<div id="__nuxt"></div>
<!-- SPA-Ladevorlage -->

加载模板不再包裹在 #_nuxt 内侧。

变更原因

此举确保加载模板保留在 DOM 中直到 Vue Suspense 解析完成,防止白屏闪烁。

迁移步骤

若有使用 CSS 或 document.querySelector 针对加载模板,需更新选择器。可用新配置 app.spaLoaderTag 和 app.spaLoaderAttrs 协助。

若需恢复旧行为:

export default defineNuxtConfig({
  experimental: {
    spaLoadingTemplateLocation: 'within',
  },
})

解析 error.data

🚦 影响等级:最低

之前抛出的错误对象中的 data 未被解析。现在该字段被解析后可直接使用。虽然是修正,但对依赖旧行为自行解析数据的用户属于破坏性变更。

迁移步骤

更新自定义的 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>

你也可关闭该实验特性:

export default defineNuxtConfig({
  experimental: {
    parseErrorData: false,
  },
})

更精细的内联样式

🚦 影响等级:中等

变化内容

Nuxt 仅对 Vue 组件内联样式,而非所有全局 CSS。

之前 Nuxt 会将所有 CSS(含全局样式)内联并移除 <link> 标签;现在只内联 Vue 组件的 CSS(这些组件之前会拆分成独立 CSS chunk)。这避免首屏请求独立 .css 文件,支持缓存单一全局 CSS,减少初始文档大小。

迁移步骤

你可以通过启用旧行为:

export default defineNuxtConfig({
  features: {
    inlineStyles: true,
  },
})

延后扫描页面元数据

🚦 影响等级:最低

变化内容

扫描页面元数据(definePageMeta)的时机改为在 pages:extend 钩子之后,而非之前。

变更原因

能扫描 pages:extend 中新增的页面元数据。新增钩子 pages:resolved 用于修改或覆盖元数据。

迁移步骤

如需覆盖页面元数据,应在 pages:resolved 钩子中进行:

  export default defineNuxtConfig({
    hooks: {
-     'pages:extend'(pages) {
+     'pages:resolved'(pages) {
        const myPage = pages.find(page => page.path === '/')
        myPage.meta ||= {}
        myPage.meta.layout = 'overridden-layout'
      }
    }
  })

你也可通过配置恢复旧行为:

export default defineNuxtConfig({
  experimental: {
    scanPageMeta: true,
  },
})

共享预渲染数据

🚦 影响等级:中等

变化内容

启用了之前的实验特性,支持在不同页面间共享 useAsyncData 和 useFetch 调用的数据。详情见 相关 PR。

变更原因

该功能允许预渲染时在页面间共享数据载荷,显著提升多页面请求同一数据(如导航栏数据)的性能。

迁移步骤

确保所有数据 key 唯一映射对应数据。例如动态页面中,useAsyncData 的 key 应唯一标识请求资源:

pages/test/[slug].vue
// 这是不安全的,因为 slug 影响数据但 key 无变化
const route = useRoute()
const { data } = await useAsyncData(async () => {
  return await $fetch(`/api/my-page/${route.params.slug}`)
})
// 应确保 key 唯一标识数据
const { data } = await useAsyncData(route.params.slug, async () => {
  return await $fetch(`/api/my-page/${route.params.slug}`)
})

你可通过配置关闭该功能:

export default defineNuxtConfig({
  experimental: {
    sharedPrerenderData: false,
  },
})

useAsyncData 和 useFetch 中默认的 data 和 error 值

🚦 影响等级:最低

变化内容

useAsyncData 返回的 data 和 error 默认值现在为 undefined(此前分别为 null 和 null/undefined)。

变更原因

保证初始化一致性。

迁移步骤

如果代码中检查 null,请改为检查 undefined,例如:

if (data.value === undefined) { ... }
可用命令自动迁移 npx codemod@latest nuxt/4/default-data-error-value。

若遇问题可关闭此变更:

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

如果您正在这样做,请报告问题,因为我们不打算将其保持为可配置项。

移除在 useAsyncData 和 useFetch 中调用 refresh 时,dedupe 选项已弃用的 boolean 值

🚦 影响级别:最低

变更内容

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

// @errors: 2322
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!' }))

  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。

useAsyncData 和 useFetch 清理数据时遵守默认值

🚦 影响等级:最低

变化内容

调用 clear 或 clearNuxtData 时,若设置了 default 值,则用默认值重置数据,而非直接清空。

变更原因

尊重用户自定义默认空值,避免频繁检查 null 或 undefined。

迁移步骤

遇问题可暂用配置关闭该行为:

export default defineNuxtConfig({
  experimental: {
    resetAsyncDataToUndefined: true,
  },
})

未来该配置可能移除。

useAsyncData 和 useFetch 中 pending 值对齐

🚦 影响等级:中等

useAsyncData、useFetch、useLazyAsyncData 和 useLazyFetch 返回的 pending 现在为计算属性,且仅在 status 为 pending 时返回 true。

变化内容

当设置 immediate: false 时,pending 在首次请求发起前保持 false(之前始终为 true)。

变更原因

保证 pending 和 status 一致,均表示请求中状态。

迁移步骤

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

可用配置临时恢复旧行为:

export default defineNuxtConfig({
  experimental: {
    pendingWhenIdle: true,
  },
})

useAsyncData 和 useFetch 中 key 变更行为

🚦 影响等级:中等

变化内容

响应式 key 变化时,useAsyncData 仅在已有数据时重新请求数据,符合 immediate: false 语义。useFetch 行为现在与之保持一致,均不会无条件请求。

变更原因

统一行为防止意外请求。关闭自动请求时,需手动调用 refresh 或 execute。

迁移步骤

如依赖旧行为自动请求,可手动注册监听:

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

useAsyncData 和 useFetch 中的数据浅层响应性

🚦 影响等级:最低

useAsyncData、useFetch、useLazyAsyncData 及 useLazyFetch 返回的 data 由 ref 变为 shallowRef。

变化内容

新数据到达时替换 data 对象触发响应;修改内部属性不再触发响应。

变更原因

极大提升深层嵌套对象性能,避免监听每个属性。多数场景数据应保持不变。

迁移步骤

一般无需变更。若依赖深层响应性:

  1. 针对单个组合函数启用深层响应:

    - const { data } = useFetch('/api/test')
    + const { data } = useFetch('/api/test', { deep: true })
  2. 全局默认开启(不推荐):

    export default defineNuxtConfig({
      experimental: {
        defaults: {
          useAsyncData: {
            deep: true,
          },
        },
      },
    })
自动迁移命令:npx codemod@latest nuxt/4/shallow-function-reactivity

builder:watch 中的绝对路径监听

🚦 影响等级:最低

变化内容

builder:watch 钩子回调的路径现为绝对路径(非相对于 srcDir 的相对路径)。

变更原因

支持监听 srcDir 以外路径,更好支持多层和复杂场景。

迁移步骤

我们已自动迁移公共 Nuxt 模块,详情见 issue #25339。

作为模块作者,兼容写法示例:

+ 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),并统一通过 useNuxtApp() 访问 Nuxt 数据。

迁移步骤

数据仍可访问,改为:

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

目录索引扫描改进

🚦 影响等级:中等

变化内容

middleware/ 目录下子文件夹中的 index 文件同样会被扫描并注册为中间件。

变更原因

与 plugins/ 目录一致,支持子文件夹中的 index 文件。

迁移步骤

通常无须迁移。如想恢复旧行为,可在钩子中过滤掉 index 中间件:

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

模板编译变更

🚦 影响等级:最低

变化内容

以前 Nuxt 用 lodash/template 编译文件系统模板(.ejs 语法),并提供了一些模板工具函数(如 serialize、importName、importSources),现在移除这些。

变更原因

Nuxt v3 引入了支持 getContents() 的虚拟语法,更灵活高效。lodash/template 存在安全问题,且依赖较大。内置使用代码序列化不理想,推荐使用 unjs/knitwork。

迁移步骤

我们已提交 PR 修正使用 EJS 语法的模块,也提供以下方案:

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

如使用 serialize、importName、importSources 函数,可改用 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 体验:为代码库的不同部分提供更好的智能提示和错误报告。
  3. 更清晰的分离:服务器端代码不会错误地建议客户端 API,反之亦然。
  4. 性能提升:TypeScript 能够通过正确范围的配置更高效地检查代码。

例如,自动导入在你的 nuxt.config.ts 中不可用(但之前 TypeScript 并不会对此进行标记)。而且虽然 IDE 能识别你 server/ 目录下 tsconfig.json 所提示的独立上下文,但这并未反映在类型检查中(需要单独的步骤)。

迁移步骤

无需迁移——现有项目将继续照常运行。

不过,为了利用更完善的类型检查,你可以选择采用新的项目引用方式:

  1. 更新你的根目录 tsconfig.json,以使用项目引用:

    如果你的 tsconfig.json 目前带有 "extends": "./.nuxt/tsconfig.json",在添加引用前请移除该行。项目引用和 extends 是互斥的。
    {
      // 移除 "extends": "./.nuxt/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. 将所有类型增强移到其适当的上下文中:

    • 如果你正在增强应用上下文的类型,请将文件移动到 app/ 目录。
    • 如果你正在增强服务器上下文的类型,请将文件移动到 server/ 目录。
    • 如果你正在增强在应用和服务器之间共享的类型,请将文件移动到 shared/ 目录。
    从 app/、server/ 或 shared/ 目录之外增强类型将无法与新的项目引用设置一起使用。
  5. 根据需要配置 Node.js TypeScript 选项:

    export default defineNuxtConfig({
      typescript: {
        // tsconfig.app.json anpassen
        tsConfig: {
          // ...
        },
        // tsconfig.shared.json anpassen
        sharedTsConfig: {
          // ...
        },
        // tsconfig.node.json anpassen
        nodeTsConfig: {
          // ...
        },
      },
      nitro: {
        typescript: {
          // tsconfig.server.json anpassen
          tsConfig: {
            // ...
          },
        },
      },
    })
  6. 更新所有运行 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

变更原因

这些选项已经被设置为当前的值有一段时间了,我们没有理由认为它们需要保持可配置。

迁移步骤

  • 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 和 Nuxt 3+

在下表中,对 Nuxt 的三个版本进行了快速比较:

特性 / 版本Nuxt 2Nuxt BridgeNuxt 3+
Vue223
稳定性😊 稳定😊 稳定😊 稳定
性能🏎 快速✈️ 更快🚀 最快
Nitro-Engine❌✅✅
ESM 支持🌙 部分👍 更好✅
TypeScript☑️ 可选🚧 部分✅
Composition 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