升级指南
升级 Nuxt
最新版本
要将 Nuxt 升级到 最新版本,请使用 nuxt upgrade 命令。
npx nuxt upgrade
yarn nuxt upgrade
pnpm nuxt upgrade
bun x nuxt upgrade
deno x nuxt upgrade
夜间发布渠道
要在最新 Nuxt 构建和测试功能正式发布前进行体验,请阅读 每日构建发布渠道 指南。
测试 Nuxt 5
Nuxt 5 目前正在开发中。在正式发布之前,您可以从 Nuxt 4.2+ 版本开始测试 Nuxt 5 的许多破坏性变更。
启用 Nuxt 5
首先,将 Nuxt 升级到 最新版本。
然后,您可以设置 future.compatibilityVersion 以匹配 Nuxt 5 的行为
export default defineNuxtConfig({
future: {
compatibilityVersion: 5,
},
})
当您将 future.compatibilityVersion 设置为 5 时,整个 Nuxt 配置中的默认值将更改为启用 Nuxt v5 的行为,包括:
- Vite 环境 API:自动启用新的 Vite 环境 API 以改进构建配置
- 归一化的页面名称:页面组件名称将与 其路由名称相匹配,以确保
<KeepAlive>行为的一致性 clearNuxtState重置为默认值:clearNuxtState将会 把状态重置为其初始值,而不是将其设为undefined- 非异步的
callHook:callHook可能返回void,而不是总是返回Promise - 注释节点占位符:仅客户端组件使用 注释节点而不是
<div>作为 SSR 占位符,从而修复了作用域样式水合(hydration)问题 - 更严格的副作用导入:生成的
tsconfig.json启用了noUncheckedSideEffectImports,以匹配 TypeScript 7 的默认设置 - 禁用 Vue Options API:Options API 将从客户端打包产物中被编译移除,以减小体积
- 随着 Nuxt 5 改进和变更的推出,将持续更新其他内容
future.compatibilityVersion: 5 测试 Nuxt 5,请定期回来查看。下文将列出破坏性变更或重大更改,并提供用于向后/向前兼容的迁移步骤。
迁移到 Vite 环境 API
🚦 影响程度:中
变更内容
Nuxt 5 迁移到了 Vite 6 的新 环境 API(Environment API),该 API 正式确立了环境的概念,并提供了对每个环境配置的更好控制。
过去,Nuxt 使用独立的客户端和服务端 Vite 配置。现在,Nuxt 使用共享的 Vite 配置,其中包含针对特定环境的插件,这些插件使用 applyToEnvironment() 方法来针对特定环境。
future.compatibilityVersion: 5(参见 测试 Nuxt 5)或通过显式启用 experimental.viteEnvironmentApi: true 来提前测试此功能。关键变更
- 弃用特定于环境的
extendViteConfig():extendViteConfig()中的server和client选项已被弃用,在使用时将显示警告。 - 更改插件注册:通过
addVitePlugin()注册且仅针对单一环境(通过传入server: false或client: false)的 Vite 插件,其config或configResolved钩子将不会被调用。 - 共享配置:
vite:extendConfig和vite:configResolved钩子现在作用于共享配置,而不是独立的客户端/服务端配置。
更改原因
Vite 环境 API 提供了以下优势:
- 开发构建与生产构建之间更好的一致性
- 对特定环境配置更细粒度的控制
- 改进的性能和插件架构
- 支持除客户端和服务端之外的自定义环境
迁移步骤
1. 迁移到使用 Vite 插件
我们建议您使用 Vite 插件,而不是 extendViteConfig、vite:configResolved 和 vite:extendConfig。
// Before
extendViteConfig((config) => {
config.optimizeDeps.include.push('my-package')
}, { server: false })
nuxt.hook('vite:extendConfig' /* or vite:configResolved */, (config, { isClient }) => {
if (isClient) {
config.optimizeDeps.include.push('my-package')
}
})
// After
addVitePlugin(() => ({
name: 'my-plugin',
config (config) {
// you can set global vite configuration here
},
configResolved (config) {
// you can access the fully resolved vite configuration here
},
configEnvironment (name, config) {
// you can set environment-specific vite configuration here
if (name === 'client') {
config.optimizeDeps ||= {}
config.optimizeDeps.include ||= []
config.optimizeDeps.include.push('my-package')
}
},
applyToEnvironment (environment) {
return environment.name === 'client'
},
}))
2. 迁移 Vite 插件以使用环境 API
您可以在插件中使用新的 applyToEnvironment 钩子,而不是配合 server: false 或 client: false 使用 addVitePlugin。
// Before
addVitePlugin(() => ({
name: 'my-plugin',
config (config) {
config.optimizeDeps.include.push('my-package')
},
}), { client: false })
// After
addVitePlugin(() => ({
name: 'my-plugin',
config (config) {
// you can set global vite configuration here
},
configResolved (config) {
// you can access the fully resolved vite configuration here
},
configEnvironment (name, config) {
// you can set environment-specific vite configuration here
if (name === 'client') {
config.optimizeDeps ||= {}
config.optimizeDeps.include ||= []
config.optimizeDeps.include.push('my-package')
}
},
applyToEnvironment (environment) {
return environment.name === 'client'
},
}))
非异步的 callHook
🚦 影响程度:极小
变更内容
随着升级到 hookable v6,callHook 现在可以返回 void,而不是总是返回 Promise<void>。这是一项重大的性能改进,当没有注册钩子或所有钩子都是同步的时,它避免了不必要的 Promise 分配。
默认情况下(在 compatibilityVersion: 4 下),Nuxt 会用 Promise.resolve() 包装 callHook,以便现有的 .then() 和 .catch() 链式调用能继续工作。而在 compatibilityVersion: 5 下,此包装器将被移除。
更改原因
Hookable v6 的 callHook 快 20-40 倍,因为它在不需要时避免创建 Promise。这对于拥有大量钩子调用点的应用程序非常有利。
迁移步骤
如果您或您的模块在 callHook 中使用了 .then() 或 .catch() 链式调用,请切换为使用 await
- nuxtApp.callHook('my:hook', data).then(() => { ... })
+ await nuxtApp.callHook('my:hook', data)
- nuxtApp.hooks.callHook('my:hook', data).catch(err => { ... })
+ try { await nuxtApp.hooks.callHook('my:hook', data) } catch (err) { ... }
future.compatibilityVersion: 5(参见 测试 Nuxt 5)或通过显式启用 experimental.asyncCallHook: false 来提前测试此功能。或者,您可以通过以下方式确保 callHook 始终返回 Promise:
export default defineNuxtConfig({
experimental: {
asyncCallHook: true,
},
})
仅客户端注释占位符
🚦 影响程度:极小
变更内容
在 compatibilityVersion: 5 下,仅客户端组件(.client.vue 文件和 createClientOnly() 包装器)现在在服务端渲染时会渲染 HTML 注释(<!--placeholder-->),而不是空的 <div> 元素。
更改原因
当占位符 <div> 与实际组件根节点具有相同的标签名时,Vue 的运行时会在水合(hydration)期间跳过重新应用 setScopeId。这会导致组件挂载后作用域样式丢失。使用注释节点可以完全避免标签名冲突。
迁移步骤
如果您依赖占位符 <div> 来继承属性(如 class、style 等)以用于布局目的(例如预留空间以防止布局偏移),请改用带有 #fallback 插槽的 <ClientOnly> 包裹组件
- <MyComponent class="placeholder" style="min-height: 200px" />
+ <ClientOnly>
+ <MyComponent />
+ <template #fallback>
+ <div class="placeholder" style="min-height: 200px"></div>
+ </template>
+ </ClientOnly>
future.compatibilityVersion: 5(参见 测试 Nuxt 5)或通过显式启用 experimental.clientNodePlaceholder: true 来提前测试此功能。或者,您可以通过以下方式恢复到先前的 <div> 占位符行为:
export default defineNuxtConfig({
experimental: {
clientNodePlaceholder: false,
},
})
更严格的副作用导入
🚦 影响程度:极小
变更内容
在 compatibilityVersion: 5 下,Nuxt 生成的 tsconfig.json 启用了 noUncheckedSideEffectImports。这是 TypeScript 7 的默认设置,因此提前采用它可以使您的项目在此次升级前保持一致。
开启此选项后,TypeScript 无法解析为模块的纯副作用导入(import './setup')现在将产生类型错误,而此前它会被忽略。这仅影响类型检查(nuxt typecheck 和您的编辑器),不影响运行时行为。
更改原因
未解析的副作用导入过去被静默忽略,因此拼写错误或已删除的文件可能会通过类型检查。标记这些错误可以捕捉到这些失误,并且符合 TypeScript 7 的默认行为。
迁移步骤
如果类型检查现在对非代码资源的副作用导入(例如 import '~/assets/styles.css')报错,请添加一个环境模块声明(ambient module declaration),以便 TypeScript 知道该导入是有效的:
declare module '*.css' {}
nuxt.config 中禁用该选项来恢复到以前的行为export default defineNuxtConfig({
typescript: {
tsConfig: {
compilerOptions: {
noUncheckedSideEffectImports: false,
},
},
},
})
默认禁用 Vue Options API
🚦 影响程度:极小
变更内容
在 compatibilityVersion: 5 下,Nuxt 将 Vue 的 __VUE_OPTIONS_API__ 特性标志设为 false,这会将 Vue 的 Options API 运行时从客户端打包产物中编译移除。
更改原因
尽管大多数 Nuxt 应用都是用组合式 API(Composition API)和 <script setup> 编写的,但 Options API 运行时仍会被打包进每个客户端产物中。移除它可以缩小客户端包体积(对于最小化应用,大约可减少 6 kB 压缩后 / 2 kB gzip 压缩后的体积)。
迁移步骤
如果您的任何组件(或依赖项的组件)使用了 Options API(export default { data() {}, methods: {}, ... }),请在 nuxt.config 中重新启用它
export default defineNuxtConfig({
vue: {
optionsApi: true,
},
})
defineNuxtComponent 不受影响:它的 asyncData 和 head 选项是通过 setup() 而不是 Vue Options API 处理的,因此无论此标志如何,它都能正常工作。迁移到 Nuxt 4
Nuxt 4 包含了重大的改进和变更。本指南将帮助您将现有的 Nuxt 3 应用程序迁移到 Nuxt 4。
首先,升级到 Nuxt 4
npm install nuxt@^4.0.0
yarn add nuxt@^4.0.0
pnpm add nuxt@^4.0.0
bun add nuxt@^4.0.0
deno add npm:nuxt@^4.0.0
升级后,大多数 Nuxt 4 行为现在已成为默认设置。但是,如果您在迁移期间需要保持向后兼容性,某些功能仍然可以进行配置。
以下各节详细介绍了升级到 Nuxt 4 所需的关键变更和迁移步骤。
下文记录了破坏性或重大的变更,以及迁移步骤和可用的配置选项。
使用 Codemods 进行迁移
为了简化升级过程,我们与 Codemod 团队合作,通过一些开源 codemod 自动化了许多迁移步骤。
npx codemod feedback 向 Codemod 团队反馈 🙏有关 Nuxt 4 codemod 的完整列表、每个 codemod 的详细信息、其来源以及运行它们的各种方法,请访问 Codemod 注册表。
您可以使用以下 codemod 配方运行本指南中提到的所有 codemod
# 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
此命令将按顺序执行所有 codemod,并提供取消选择任何您不想运行的选项。每个 codemod 也与其各自的更改一起列在下面,并且可以独立执行。
新目录结构
🚦 影响程度:重大
Nuxt 现在默认采用新的目录结构,并具有向后兼容性(因此如果 Nuxt 检测到您正在使用旧结构,例如具有顶层 app/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>/ - 新增了一个
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 会解析为 app/components/,~/pages 解析为 app/pages/ 等。👉 更多详情,请参见 实现此更改的 PR。
更改原因
- 性能 - 将所有代码放在仓库的根目录下会导致文件系统监视器(FS watchers)扫描/包含
.git/和node_modules/文件夹,这在非 Mac 操作系统上会显著延迟启动时间。 - IDE 类型安全 -
server/与应用的其余部分运行在两个完全不同的上下文中,并且可用的全局导入也不同。确保server/不在应用其余部分所在的同一文件夹内部,是确保你在 IDE 中获得良好自动补全的重要第一步。
迁移步骤
- 创建一个名为
app/的新目录。 - 将您的
assets/、components/、composables/、app/layouts/、app/middleware/、app/pages/、app/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/、shared/和server/文件夹保留在app/文件夹之外的项目根目录中。 - 记得更新任何第三方配置文件以适应新的目录结构,例如您的
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 文件夹结构
export default defineNuxtConfig({
// This reverts the new srcDir default from `app` back to your root directory
srcDir: '.',
// This specifies the directory prefix for `router.options.ts` and `spa-loading-template.html`
dir: {
app: 'app',
},
})
单例数据获取层
🚦 影响程度:中等
变更内容
Nuxt 的数据获取系统(useAsyncData 和 useFetch)经过了重大重组,以获得更好的性能和一致性
- 相同键共享引用:所有使用相同键调用
useAsyncData或useFetch的地方现在共享相同的data、error和status引用。这意味着,所有带有显式键的调用绝不能有冲突的deep、transform、pick、getCachedData或default选项,这一点非常重要。 - 对
getCachedData的更多控制:现在,每次获取数据时都会调用getCachedData函数,即使这是由 watcher 或调用refreshNuxtData引起的(此前,在这些情况下总是会获取新数据且不会调用此函数)。为了更好地控制何时使用缓存数据以及何时重新获取,该函数现在会接收一个包含请求原因的上下文对象。 - 响应式键支持:您现在可以使用计算属性引用(computed refs)、普通引用(plain refs)或 getter 函数作为键,这支持了自动重新获取数据(并单独存储数据)。
- 数据清理:当使用通过
useAsyncData获取的数据的最后一个组件被卸载时,Nuxt 将删除该数据,以避免内存使用量不断增长。
更改原因
这些更改旨在改善内存使用情况,并增强跨 useAsyncData 调用的加载状态的一致性。
迁移步骤
- 检查不一致的选项:检查所有使用相同键但带有不同选项或获取函数的组件。
// 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调用提取到它们自己的组合式函数(composable)中可能会更有利app/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 - 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] + } })
或者,目前您可以通过以下方式禁用此行为:
export default defineNuxtConfig({
experimental: {
granularCachedData: false,
purgeCachedData: false,
},
})
纠正了图层(Layers)中的模块加载顺序
🚦 影响程度:极小
变更内容
使用 Nuxt 图层(Layers)时加载模块的顺序已被纠正。此前,项目根目录中的模块会在扩展图层中的模块之前加载,这与预期的行为相反。
现在模块将按正确的顺序加载
- 图层模块优先(按扩展顺序——更深层的图层优先)
- 项目模块最后(优先级最高)
这会影响以下两方面:
- 在
nuxt.config.ts的modules数组中定义的模块 - 从
modules/目录自动发现的模块
更改原因
此更改确保了:
- 扩展图层的优先级低于消费项目(consuming project)
- 模块执行顺序符合直观的图层继承模式
- 模块配置和钩子在多图层设置中按预期工作
迁移步骤
大多数项目不需要进行更改,因为这纠正了加载顺序以匹配预期行为。
但是,如果您的项目依赖于以前的不正确顺序,您可能需要:
- 审查模块依赖项:检查是否有任何模块依赖于特定的加载顺序
- 调整模块配置:如果模块是为了绕过不正确的顺序而进行配置的
- 充分测试:确保所有功能在纠正顺序后按预期工作
新的正确顺序示例
// 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'],
})
// Loading order (corrected):
// 1. layer-module-1
// 2. layer-module-2
// 3. project-module-1 (can override layer modules)
// 4. project-module-2 (can override layer modules)
如果您由于需要注册钩子而遇到模块顺序依赖问题,请考虑对需要调用钩子的模块使用 modules:done 钩子。这会在所有其他模块加载完毕后运行,因此可以安全使用。
👉 更多详情,请参见 PR #31507 和 issue #25719。
路由元数据的去重
🚦 影响程度:极小
变更内容
可以使用 definePageMeta 设置一些路由元数据,例如 name、path 等。此前这些数据既可在路由上获取,也可在路由元数据上获取(例如 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.0 版本。虽然它大部分向后兼容,但包含对底层 API 的若干破坏性更改。
- 移除了以下属性:
vmid、hid、children、body。 - 不再支持 Promise 输入。
- 现在默认使用 Capo.js 对标签进行排序。
迁移步骤
上述更改对您的应用影响应该极小。
如果您遇到问题,您应该进行以下验证:
- 您没有使用任何被移除的属性。
useHead({
meta: [{
name: 'description',
// meta tags don't need a vmid, or a key
- vmid: 'description'
- hid: 'description'
}]
})
- 如果您正在使用 模板参数(Template Params) 或 别名标签排序(Alias Tag Sorting),您现在需要显式启用这些功能。
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'
如果您仍然遇到问题,可以通过启用 head.legacy 配置来恢复到 v1 的行为。
export default defineNuxtConfig({
unhead: {
legacy: true,
},
})
SPA 加载屏幕的新 DOM 位置
🚦 影响程度:极小
变更内容
在渲染仅客户端页面(设置了 ssr: false)时,我们会在 Nuxt 应用根目录内有选择地渲染加载屏幕(来自 ~/app/spa-loading-template.html——请注意,在 Nuxt 4 中这也已更改为 ~/spa-loading-template.html)
<div id="__nuxt">
<!-- spa loading template -->
</div>
现在,我们默认将模板渲染在 Nuxt 应用根目录的旁边
<div id="__nuxt"></div>
<!-- spa loading template -->
更改原因
这允许 spa 加载模板在 DOM 中保留,直到 Vue 应用的 suspense 解析完毕,从而防止白屏闪烁。
迁移步骤
如果您过去使用 CSS 或 document.queryElement 来定位 spa 加载模板,则需要更新您的选择器。为此,您可以使用新的 app.spaLoaderTag 和 app.spaLoaderAttrs 配置选项。
或者,您可以通过以下方式恢复到以前的行为
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,从而恢复到以前的行为。
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'
}
}
})
或者,您可以通过以下方式恢复到以前的行为
export default defineNuxtConfig({
experimental: {
scanPageMeta: true,
},
})
共享预渲染数据
🚦 影响程度:中
变更内容
我们启用了一项先前处于实验阶段的功能,用于在不同页面之间共享来自 useAsyncData 和 useFetch 调用的数据。参见 原始 PR。
更改原因
此功能会自动在预渲染的页面之间共享负载数据。当预渲染使用 useAsyncData 或 useFetch 并在不同页面中获取相同数据的站点时,这可以带来显著的性能提升。
例如,如果您的站点每个页面都需要调用 useFetch(例如,获取菜单的导航数据或来自 CMS 的站点设置),则该数据在预渲染使用它的第一个页面时只会获取一次,然后会被缓存以用于预渲染其他页面。
迁移步骤
确保数据的任何唯一键始终能够解析为相同的数据。例如,如果您使用 useAsyncData 获取与特定页面相关的数据,则应提供一个唯一匹配该数据的键(useFetch 应该会自动为您完成此操作)。
// 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}`)
})
或者,您可以通过以下方式禁用此功能
export default defineNuxtConfig({
experimental: {
sharedPrerenderData: false,
},
})
useAsyncData 和 useFetch 中的默认 data 和 error 值
🚦 影响程度:极小
变更内容
从 useAsyncData 返回的 data 和 error 对象现在将默认为 undefined。
更改原因
过去 data 初始化为 null,但在 clearNuxtData 中会被重置为 undefined。error 初始化为 null。此更改旨在带来更好的一致性。
迁移步骤
如果您过去检查 data.value 或 error.value 是否为 null,您可以将这些检查更新为检查 undefined。
npx codemod@latest nuxt/4/default-data-error-value 来自动化此步骤移除在 useAsyncData 和 useFetch 中调用 refresh 时针对 dedupe 选项的已弃用 boolean 值
🚦 影响程度:极小
变更内容
过去可以向 refresh 传递 dedupe: boolean。这些是 cancel(true)和 defer(false)的别名。
// @errors: 2322
const { refresh } = await useAsyncData(() => Promise.resolve({ 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 来自动化此步骤在 useAsyncData 和 useFetch 中清除 data 时遵循默认值
🚦 影响程度:极小
变更内容
如果您为 useAsyncData 提供了自定义的 default 值,现在在调用 clear 或 clearNuxtData 时将使用该值,它将被重置为默认值,而不仅仅是取消设置。
更改原因
通常用户会设置一个适当的空值(例如空数组),以避免在迭代它时检查 null/undefined。在重置/清除数据时,应该尊重这一点。
在清除 useState 时遵循默认值
🚦 影响程度:极小
变更内容
在 compatibilityVersion: 5 下,clearNuxtState 会将状态重置为其初始值(由 useState 的 init 函数提供),而不是将其设为 undefined。这使 clearNuxtState 的行为与已经重置为默认值的 clearNuxtData 保持一致。
更改原因
当 clearNuxtState 将状态设为 undefined 时,依赖于该状态的组合式函数可能会崩溃,因为它们期望状态始终具有有效的结构(例如,访问 undefined 上的属性)。重置为 init 值可确保状态始终具有可用的默认值。
迁移步骤
如果您依赖于 clearNuxtState 将状态设为 undefined 的行为,您可以显式传递 { reset: false }
- clearNuxtState('myKey')
+ clearNuxtState('myKey', { reset: false })
或者,您可以通过以下方式恢复到以前的行为:
export default defineNuxtConfig({
experimental: {
defaults: {
useState: {
resetOnClear: false,
},
},
},
})
您也可以在不设置 compatibilityVersion: 5 的情况下提前启用此行为
export default defineNuxtConfig({
experimental: {
defaults: {
useState: {
resetOnClear: true,
},
},
},
})
useAsyncData 和 useFetch 中 pending 值的对齐
🚦 影响程度:中
从 useAsyncData、useFetch、useLazyAsyncData 和 useLazyFetch 返回的 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: {{ 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,
},
})
Key Change Behavior in useAsyncData 和 useFetch 中的键变更行为
🚦 影响程度:中
变更内容
当在 useAsyncData 或 useFetch 中使用响应式键时,当键更改时,Nuxt 会自动重新获取数据。当设置了 immediate: false 时,useAsyncData 只有在数据已经获取过一次的情况下,才会在键更改时获取数据。
以前,useFetch 的行为略有不同。无论如何,只要键更改,它就会获取数据。
现在,useFetch 和 useAsyncData 的行为保持一致——只有在数据已经获取过一次的情况下,才会在键更改时获取数据。
更改原因
这确保了 useAsyncData 和 useFetch 之间的行为一致,并防止了意外的数据获取。如果你设置了 immediate: false,那么你必须调用 refresh 或 execute,否则数据将永远不会在 useFetch 或 useAsyncData 中被获取。
迁移步骤
这一更改通常会改善预期行为,但如果你原本期望通过更改非即时执行的 useFetch 的键或选项来触发获取,现在你需要首次手动触发它。
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,
},
})
useAsyncData 和 useFetch 中的浅层数据响应式
🚦 影响程度:极小
从 useAsyncData、useFetch、useLazyAsyncData 和 useLazyFetch 返回的 data 对象现在是一个 shallowRef,而不是 ref。
变更内容
当获取新数据时,任何依赖于 data 的内容仍将具有响应式,因为整个对象被替换了。但是,如果你的代码更改了该数据结构内部的属性,这将不会触发应用中的任何响应式。
更改原因
这为深层嵌套的对象和数组带来了显着的性能提升,因为 Vue 不再需要监视每个属性/数组的修改。在大多数情况下,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-reactivity 来自动执行此步骤builder:watch 中的绝对监视路径
🚦 影响程度:极小
变更内容
Nuxt 的 builder:watch 钩子现在发出的路径是绝对路径,而不是相对于项目 srcDir 的相对路径。
更改原因
这使我们能够支持监视 srcDir 之外的路径,并为层和其他更复杂的模式提供更好的支持。
迁移步骤
我们已经主动迁移了我们所知道的使用此钩子的公开 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)
目录索引扫描
🚦 影响程度:中
变更内容
你 app/middleware/ 文件夹中的子文件夹现在也会被扫描以查找 index 文件,并且这些文件现在也会在你的项目中注册为中间件。
更改原因
Nuxt 会自动扫描多个文件夹,包括 app/middleware/ 和 app/plugins/。
你 app/plugins/ 文件夹中的子文件夹会被扫描以查找 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 曾接连出现安全问题。这些问题实际上并不适用于 Nuxt 项目,因为它是用于构建时而非运行时,并且由受信代码使用。然而,它们仍然会出现在安全审计中。而且,lodash 是一个很重的依赖项,且大多数项目并未真正使用它。
最后,直接在 Nuxt 内部提供代码序列化函数并不是理想的做法。相反,我们维护像 unjs/knitwork 这样的项目,它们可以作为你项目的依赖项,并且可以在其中直接报告/解决安全问题,而无需升级 Nuxt 本身。
迁移步骤
我们已经提交了 PR 以更新使用 EJS 语法的模块,但如果你需要自己动手,你有三种向后/向前兼容的替代方案
- 将你的字符串插值逻辑直接移入
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 })
+ },
})
最后,如果你正在使用模板实用工具(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 的项目引用功能。
- 特定上下文的类型检查:现在,每个上下文都为其特定环境拥有适当的编译器选项以及 include/exclude 设置。
- 新的
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 是互斥的。{ // Remove "extends": "./.nuxt/tsconfig.json" if present "files": [], "references": [ { "path": "./.nuxt/tsconfig.app.json" }, { "path": "./.nuxt/tsconfig.server.json" }, { "path": "./.nuxt/tsconfig.shared.json" }, { "path": "./.nuxt/tsconfig.node.json" } ] } - 删除任何继承自
.nuxt/tsconfig.server.json的手动服务器tsconfig.json文件(例如server/tsconfig.json)。 - 更新你的类型检查脚本以使用项目引用的构建标志
- "typecheck": "nuxt prepare && vue-tsc --noEmit" + "typecheck": "nuxt prepare && vue-tsc -b --noEmit" - 将所有类型扩充移动到其适当的上下文中:
- 如果你正在为应用上下文扩充类型,请将文件移动到
app/目录。 - 如果你正在为服务器上下文扩充类型,请将文件移动到
server/目录。 - 如果你正在扩充在应用和服务器之间共享的类型,请将文件移动到
shared/目录。
从app/、server/或shared/目录之外扩充类型将无法在新项目引用设置下工作。 - 如果你正在为应用上下文扩充类型,请将文件移动到
- 如果需要,配置 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']
+ }
+ }
})
规范化的页面组件名称
🚦 影响程度:极小
变更内容
当 future.compatibilityVersion 设置为 5(或启用了 experimental.normalizePageNames)时,页面组件名称与其路由名称匹配,而不是使用文件名。例如,pages/foo/index.vue 的组件名称将是 foo 而不是 index。
更改原因
以前,Vue 根据文件名分配组件名称。这意味着像 pages/foo/index.vue 和 pages/bar/index.vue 这样的多个页面的组件名称都将是 index。这使得带有 include/exclude 过滤器的 <KeepAlive> 变得不可靠,并且需要手动向每个页面添加 defineOptions({ name: '...' })。
迁移步骤
如果你依赖当前的组件名称(例如在 <KeepAlive> 的 include/exclude 列表中),请将其更新为使用路由名称而不是文件名。
<template>
<NuxtPage :keepalive="{
- include: ['index']
+ include: ['foo']
}" />
</template>
要禁用此行为
export default defineNuxtConfig({
experimental: {
normalizePageNames: false,
},
})
Nuxt 2 对比 Nuxt 3+
下表是对 3 个版本 Nuxt 的简要对比
| 特性 / 版本 | Nuxt 2 | Nuxt Bridge | Nuxt 3+ |
|---|---|---|---|
| Vue | 2 | 2 | 3 |
| 稳定性 | 😊 稳定 | 😊 稳定 | 😊 稳定 |
| 性能 | 🏎 快 | ✈️ 更快 | 🚀 最快 |
| Nitro 引擎 | ❌ | ✅ | ✅ |
| ESM 支持 | 🌙 部分支持 | 👍 更好 | ✅ |
| TypeScript | ☑️ 可选 | 🚧 部分支持 | ✅ |
| 组合式 API | ❌ | 🚧 部分支持 | ✅ |
| Options API | ✅ | ✅ | ✅ |
| 组件自动导入 | ✅ | ✅ | ✅ |
<script setup> 语法 | ❌ | 🚧 部分支持 | ✅ |
| 自动导入 | ❌ | ✅ | ✅ |
| webpack | 4 | 4 | 5 |
| Vite | ⚠️ 部分支持 | 🚧 部分支持 | ✅ |
| Nuxt CLI | ❌ 旧版 | ✅ nuxt | ✅ nuxt |
| 静态网站 | ✅ | ✅ | ✅ |
从 Nuxt 2 到 Nuxt 3+
迁移指南提供了 Nuxt 2 特性与 Nuxt 3+ 特性的逐步对比,以及调整当前应用程序的指导。
Nuxt 2 到 Nuxt Bridge
如果你倾向于将 Nuxt 2 应用程序逐步迁移到 Nuxt 3,你可以使用 Nuxt Bridge。Nuxt Bridge 是一个兼容层,允许你通过可选机制在 Nuxt 2 中使用 Nuxt 3+ 的特性。