升级指南
升级 Nuxt
最新版本
要将 Nuxt 升级到最新版本,请使用 nuxt upgrade 命令。
npx nuxt upgrade
yarn nuxt upgrade
pnpm nuxt upgrade
bun x nuxt upgrade
deno x 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
// }
// }
})
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:
# Using pinned version due to https://github.com/codemod/codemod/issues/1710
npx codemod@0.18.7 nuxt/4/migration-recipe
# Using pinned version due to https://github.com/codemod/codemod/issues/1710
yarn dlx codemod@0.18.7 nuxt/4/migration-recipe
# Using pinned version due to https://github.com/codemod/codemod/issues/1710
pnpm dlx codemod@0.18.7 nuxt/4/migration-recipe
# Using pinned version due to https://github.com/codemod/codemod/issues/1710
bun x codemod@0.18.7 nuxt/4/migration-recipe
# Using pinned version due to https://github.com/codemod/codemod/issues/1710
deno x codemod@0.18.7 nuxt/4/migration-recipe
该命令将会按顺序运行所有 codemods,你还可以选择不执行某些不想运行的;每个 codemod 也在下文列出,并可单独运行。
新的目录结构
🚦 影响等级:重大
Nuxt 默认采用新的目录结构,但保持向后兼容(如果 Nuxt 检测到你使用的是旧结构,比如顶级的 pages/ 目录,则不会应用这个新结构)。
变化内容
- 新的 Nuxt 默认
srcDir现在是app/,大部分内容都从这里解析。 serverDir默认从<rootDir>/server而非<srcDir>/serverlayers/、modules/和public/目录默认相对于<rootDir>解析- 如果使用 Nuxt Content v2.13+,
content/相对于<rootDir>解析 - 新增
dir.app,这是寻找router.options.ts和spa-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 解析为 components/,~/pages 解析为 pages/,依此类推。👉 详情可见 实现此变更的 PR。
变更原因
- 性能 — 把代码全部放在项目根目录会导致
.git/和node_modules/文件夹被文件系统监听器扫描/包含,这可能在非 macOS 系统上显著延长启动时间。 - IDE 类型安全 —
server/目录与应用其余部分运行在两个完全不同的环境中,拥有不同的全局导入。确保server/不在应用程序其余部分的相同目录中是实现 IDE 良好自动补全的第一大步。
迁移步骤
- 创建一个名为
app/的新目录。 - 将你的
assets/、components/、composables/、layouts/、middleware/、pages/、plugins/和utils/文件夹移到其中,以及app.vue、error.vue、app.config.ts。如果你已有app/router-options.ts或app/spa-loading-template.html,路径保持不变。 - 确保
nuxt.config.ts、content/、layers/、modules/、public/和server/文件夹留在项目根目录(app/外)。 - 记得更新任何第三方配置文件以适应新的目录结构,比如你的
tailwindcss或eslint配置(如果需要的话,@nuxtjs/tailwindcss会自动配置正确的tailwindcss)。
npx codemod@latest nuxt/4/file-structure 来自动化此迁移。然而,迁移 非必需。如果想继续使用当前的文件夹结构,Nuxt 会自动检测。如果没有检测到,请提交 issue。唯一的例外是如果你已经自定义了 srcDir。此时,你需要知道 modules/、public/ 和 server/ 文件夹会从你的 rootDir 解析,而不是从自定义的 srcDir。如有需要,你可通过配置 dir.modules、dir.public 和 serverDir 来覆盖。
你也可以通过以下配置强制使用 v3 文件夹结构:
export default defineNuxtConfig({
// 将新的 srcDir 默认值从 `app` 改回根目录
srcDir: '.',
// 指定 `app/router.options.ts` 和 `app/spa-loading-template.html` 的目录前缀
dir: {
app: 'app',
},
})
单例数据获取层
🚦 影响等级:中等
变化内容
Nuxt 的数据获取系统 (useAsyncData 和 useFetch) 已经被大幅重构,以提升性能和一致性:
- 相同 key 共享 refs:所有调用
useAsyncData或useFetch并使用相同 key 的调用都会共享同一份data、error和statusrefs。这意味着所有带显式 key 的调用的选项deep、transform、pick、getCachedData或default不能冲突。 - 对
getCachedData更多控制:现在无论数据是否来自 watcher 还是通过调用refreshNuxtData,每次请求数据时都会调用getCachedData。之前这些场景下会强制重新获取数据而不调用该函数。为了更灵活地控制何时使用缓存数据,函数接收的上下文参数中包含了请求来源。 - 支持响应式 key:现在可使用计算的 refs、普通 refs 或 getter 函数作为 key,从而支持自动数据刷新(并分开存储数据)。
- 数据清理:当最后一个使用了某条
useAsyncData获取数据的组件卸载时,Nuxt 会移除这条数据,避免内存持续增长。
变更原因
这些修改是为了改善内存使用效率和提升多个调用之间加载状态的一致性。
迁移步骤
- 检查不一致的选项:审查任何对相同 key 使用不一致选项或获取函数的组件。
// 这将触发警告 const { data: users1 } = useAsyncData('users', () => $fetch('/api/users'), { deep: false }) const { data: users2 } = useAsyncData('users', () => $fetch('/api/users'), { deep: true })
建议将对同一明确 key 的调用(同时含自定义选项)提取到独立的组合函数中:composables/useUserData.tsexport function useUserData (userId: string) { return useAsyncData( `user-${userId}`, () => fetchUser(userId), { deep: true, transform: user => ({ ...user, lastAccessed: new Date() }), }, ) } - 更新
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 时,模块加载顺序被修正。之前项目根目录的模块会优先加载,导致顺序与预期相反。
现在模块加载顺序正确:
- Layer 模块先加载(按继承顺序,较深层先)
- 项目模块后加载(最高优先级)
影响包括:
- 在
nuxt.config.ts的modules数组中声明的模块 modules/目录的自动发现模块
变更原因
此变化确保:
- 扩展层优先级低于使用它的项目
- 模块执行顺序符合直观的层级继承模式
- 多层结构中模块配置和钩子正常工作
迁移步骤
大多数项目无需更改,该修正使加载顺序与预期一致。
但如果你的项目依赖之前的错误顺序,可能需要:
- 检查模块依赖:确认模块加载顺序是否有严格依赖
- 调整模块配置:如果有为适应旧顺序的配置,进行调整
- 全面测试:确保功能正常且顺序正确
正确顺序示例:
// Layer: my-layer/nuxt.config.ts
export default defineNuxtConfig({
modules: ['layer-module-1', 'layer-module-2'],
})
// Project: nuxt.config.ts
export default defineNuxtConfig({
extends: ['./my-layer'],
modules: ['project-module-1', 'project-module-2'],
})
// 加载顺序(已修正):
// 1. layer-module-1
// 2. layer-module-2
// 3. project-module-1(可以覆盖 layer 模块)
// 4. project-module-2(可以覆盖 layer 模块)
如果因需注册钩子而遇到模块加载顺序问题,可考虑使用 modules:done 钩子,该钩子在所有模块加载完成后调用,安全使用。
👉 详情见 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 loading template -->
</div>
变更为:
<div id="__nuxt"></div>
<!-- spa loading template -->
加载模板不再包裹在 #_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 应唯一标识请求资源:
// 这是不安全的,因为 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 对象触发响应;修改内部属性不再触发响应。
变更原因
极大提升深层嵌套对象性能,避免监听每个属性。多数场景数据应保持不变。
迁移步骤
一般无需变更。若依赖深层响应性:
- 针对单个组合函数启用深层响应:
- const { data } = useFetch('/api/test') + const { data } = useFetch('/api/test', { deep: true }) - 全局默认开启(不推荐):nuxt.config.ts
export default defineNuxtConfig({ experimental: { defaults: { useAsyncData: { deep: true, }, }, }, })
npx codemod@latest nuxt/4/shallow-function-reactivitybuilder: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 语法的模块,也提供以下方案:
- 把字符串插值逻辑转到
getContents()。 - 使用类似 https://github.com/nuxt-modules/color-mode/pull/240 的替换函数。
- 使用
es-toolkit/compat(lodash template 替代)作为项目依赖:
+ 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 的建议。
迁移步骤
有两种方法:
- 对你的应用进行类型检查并修复所有新出现的错误(推荐)。
- 在你的
nuxt.config.ts中覆盖新的默认值:export default defineNuxtConfig({ typescript: { tsConfig: { compilerOptions: { noUncheckedIndexedAccess: false, }, }, }, })
TypeScript 配置拆分
🚦 影响级别:最小
变更内容
Nuxt 现在为不同的上下文生成单独的 TypeScript 配置,以提供更好的类型检查体验:
- 新的 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- 旧版配置,保持向后兼容
- 向后兼容性:现有扩展
.nuxt/tsconfig.json的项目将继续像以前一样工作。 - 可选择加入的项目引用:新项目或希望获得更好类型检查的项目可以采用 TypeScript 的项目引用功能。
- 特定上下文类型检查:每个上下文现在都具有适合其特定环境的编译器选项和包含/排除设置。
- 新的
typescript.nodeTsConfig选项:你现在可以自定义 Node.js 构建时代码的 TypeScript 配置。
变更原因
此更改带来了以下几个好处:
- 更好的类型安全:每个上下文(应用、服务器、构建时)都能通过特定的全局变量和 API 进行适当的类型检查。
- 提升 IDE 体验:为代码库的不同部分提供更好的智能提示和错误报告。
- 更清晰的分离:服务器端代码不会错误地建议客户端 API,反之亦然。
- 性能提升:TypeScript 能够通过正确范围的配置更高效地检查代码。
例如,自动导入在你的 nuxt.config.ts 中不可用(但之前 TypeScript 并不会对此进行标记)。而且虽然 IDE 能识别你 server/ 目录下 tsconfig.json 所提示的独立上下文,但这并未反映在类型检查中(需要单独的步骤)。
迁移步骤
无需迁移——现有项目将继续照常运行。
不过,为了利用更完善的类型检查,你可以选择采用新的项目引用方式:
- 更新你的根目录
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" } ] } - 移除所有手动创建的服务器端
tsconfig.json文件(例如server/tsconfig.json),这些文件如果扩展了.nuxt/tsconfig.server.json,请删除。 - 更新你的类型检查脚本,使用构建标志以支持项目引用:
- "typecheck": "nuxt prepare && vue-tsc --noEmit" + "typecheck": "nuxt prepare && vue-tsc -b --noEmit" - 将所有类型增强移到其适当的上下文中:
- 如果你正在增强应用上下文的类型,请将文件移动到
app/目录。 - 如果你正在增强服务器上下文的类型,请将文件移动到
server/目录。 - 如果你正在增强在应用和服务器之间共享的类型,请将文件移动到
shared/目录。
从app/、server/或shared/目录之外增强类型将无法与新的项目引用设置一起使用。 - 如果你正在增强应用上下文的类型,请将文件移动到
- 根据需要配置 Node.js TypeScript 选项:
export default defineNuxtConfig({ typescript: { // customize tsconfig.app.json tsConfig: { // ... }, // customize tsconfig.shared.json sharedTsConfig: { // ... }, // customize tsconfig.node.json nodeTsConfig: { // ... }, }, nitro: { typescript: { // customize tsconfig.server.json tsConfig: { // ... }, }, }, }) - 更新所有运行 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 一段时间了,并且它是 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']
+ }
+ }
})
Nuxt 2 与 Nuxt 3+
在下表中,对 Nuxt 的三个版本进行了快速比较:
| Feature / Version | Nuxt 2 | Nuxt Bridge | Nuxt 3+ |
|---|---|---|---|
| Vue | 2 | 2 | 3 |
| 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 | ❌ | ✅ | ✅ |
| webpack | 4 | 4 | 5 |
| Vite | ⚠️ Partial | 🚧 Partial | ✅ |
| Nuxt CLI | ❌ Old | ✅ nuxt | ✅ nuxt |
| Static sites | ✅ | ✅ | ✅ |
从 Nuxt 2 迁移到 Nuxt 3+
迁移指南提供了 Nuxt 2 特性与 Nuxt 3+ 特性的逐步对比,以及如何适配现有应用的指导。
从 Nuxt 2 迁移到 Nuxt Bridge
如果你希望渐进迁移 Nuxt 2 应用到 Nuxt 3,可以使用 Nuxt Bridge。Nuxt Bridge 是一个兼容层,允许你在 Nuxt 2 中按需启用 Nuxt 3+ 的特性。