升级指南
升级 Nuxt
最新版本
要将 Nuxt 升级到最新版本,请使用 nuxt upgrade
命令。
npx nuxt upgrade
yarn nuxt upgrade
pnpm nuxt upgrade
bun x nuxt upgrade
夜版发布渠道
要使用 Nuxt 的最新构建版本并测试新功能(尚未正式发布),请阅读夜版发布渠道指南。
迁移到 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
升级完成后,大多数 Nuxt 4 的行为已成为默认。但如果在迁移过程中需要保持向后兼容,某些特性仍可以配置。
以下章节详细描述了升级到 Nuxt 4 时的主要变化和迁移步骤。
重大或破坏性更改均已记录,并附带迁移步骤及可用配置选项。
使用 Codemods 迁移
为了简化升级流程,我们与 Codemod 团队合作,提供了多个开源的自动化迁移脚本。
npx codemod feedback
向 Codemod 团队反馈 🙏关于 Nuxt 4 Codemods 的完整列表、详细信息、源码及多种运行方式,请访问 Codemod 注册表。
您可以使用以下 codemod
脚本运行本指南中提及的所有 codemod:
npx codemod@latest nuxt/4/migration-recipe
yarn dlx codemod@latest nuxt/4/migration-recipe
pnpm dlx codemod@latest nuxt/4/migration-recipe
bun x codemod@latest nuxt/4/migration-recipe
该命令会依次执行所有 codemods,并允许您取消选择不希望运行的项。每个 codemod 也在下面按其对应更改列出,可单独运行。
新的目录结构
🚦 影响级别:重大
Nuxt 现在默认采用新的目录结构,同时保持向后兼容(如果 Nuxt 检测到您使用的是旧结构,比如存在顶层的 app/pages/
目录,则不会应用此新结构)。
👉 查看完整 RFC
变化内容
- 新的 Nuxt 默认
srcDir
变为app/
,大多数资源都会从这里解析。 serverDir
默认值由<srcDir>/server
改为<rootDir>/server
layers/
、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
解析到 app/components/
,~/pages
解析到 app/pages/
,等等。👉 更多细节请参见实现该变更的 PR。
变更原因
- 性能 — 将所有代码置于仓库根目录会导致
.git/
和node_modules/
文件夹被文件系统监视器扫描,尤其在非 Mac OS 系统下极大延长启动时间。 - IDE 类型安全 —
server/
目录与应用的其他部分运行在完全不同的上下文并有不同的全局导入,确保server/
不在同一文件夹下是保证 IDE 自动补全效果的重要一步。
迁移步骤
- 新建一个名为
app/
的目录。 - 将
assets/
、components/
、composables/
、layouts/
、middleware/
、pages/
、plugins/
和utils/
文件夹,以及app.vue
、error.vue
、app.config.ts
移动到app/
目录下。如果您有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: '.',
// 指定 `router.options.ts` 和 `spa-loading-template.html` 的目录前缀
dir: {
app: 'app',
},
})
单例数据获取层
🚦 影响级别:中等
变化内容
Nuxt 的数据获取系统(useAsyncData
和 useFetch
)经过重大重组,以提升性能与一致性:
- 相同 key 共享 refs:所有使用相同 key 调用
useAsyncData
或useFetch
的地方都会共享相同的data
、error
和status
引用。这意味着带有显式 key 的调用,其deep
、transform
、pick
、getCachedData
或default
选项必须一致,避免冲突。 - 更灵活的
getCachedData
:getCachedData
函数现在每次数据获取时都会调用,包括由侦听器触发或手动调用refreshNuxtData
时。(之前这些情况下不会调用此函数。)为更灵活地控制何时使用缓存数据、何时重新请求,函数会接收包含请求原因的上下文对象。 - 支持响应式 key:现在支持使用计算 Ref、普通 Ref 或 getter 函数作为 key,可以基于 key 自动重新请求数据(并且独立存储数据)。
- 数据自动清理:最后一个使用
useAsyncData
数据的组件卸载时,Nuxt 会自动移除相关数据,避免内存占用不断增长。
变更原因
此改动提升内存利用效率,同时统一 useAsyncData
各调用处的加载状态表现。
迁移步骤
- 检查不一致的选项:查找所有使用同一 key 但带不同选项或 fetch 函数的组件。
// 现在会触发警告 const { data: users1 } = useAsyncData('users', () => $fetch('/api/users'), { deep: false }) const { data: users2 } = useAsyncData('users', () => $fetch('/api/users'), { deep: true })
建议将带有显式 key(且自定义选项)的调用提取为独立的组合函数: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 - 可能为 '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 层级系统时,之前模块加载顺序错误:项目根目录的模块先加载,扩展层的模块后加载,顺序与预期相反。
现在模块加载顺序已被正确修正:
- 先加载层级模块(按照扩展顺序,最深层优先)
- 后加载项目模块(优先级最高)
这影响了:
- 在
nuxt.config.ts
的modules
数组中定义的模块 modules/
目录中自动发现的模块
变更原因
此改动确保:
- 扩展层的优先级低于使用它们的项目
- 模块执行顺序符合层继承的直观预期
- 在多层架构下,模块配置和钩子能正常工作
迁移步骤
大多数项目无需更改,此改动只是修正了模块加载顺序与预期相符。
但如果您的项目依赖之前错误的顺序,可能需要:
- 检查模块依赖关系:查看是否有模块依赖特定加载顺序
- 调整模块配置:如果此前为应对顺序问题做了配置调整
- 充分测试:确保修正顺序后功能正常
新的正确示例顺序:
// 层级: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 #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 中识别该组件,需要使用这个名称。
但 Nuxt 的自动导入会使用 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'
如仍有问题,可通过启用 head.legacy
配置回退到 v1 行为:
export default defineNuxtConfig({
unhead: {
legacy: true,
},
})
SPA Loading 屏幕的新 DOM 位置
🚦 影响级别:最小
变化内容
客户端渲染页面(ssr: false
)时,加载屏幕(默认为 ~/app/spa-loading-template.html
;Nuxt 4 中改为 ~/spa-loading-template.html
)渲染在 Nuxt 应用根节点内:
<div id="__nuxt">
<!-- spa loading template -->
</div>
现在,加载屏幕默认渲染在 Nuxt 应用根节点旁边:
<div id="__nuxt"></div>
<!-- spa loading template -->
变更原因
这样能保证加载屏幕在 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>
更细粒度的内联样式
🚦 影响级别:中等
变化内容
Nuxt 现在仅对 Vue 组件的样式进行内联,不再内联全局 CSS。
之前,Nuxt 会内联包括全局样式在内的所有 CSS 并移除针对单独 CSS 文件的 <link>
,现在只对 Vue 组件相关 CSS 内联(此前这些样式会产生独立的 CSS Chunk)。这能更好地平衡减少网络请求数量(首屏加载时不会为每个页面或组件单独请求 .css
文件)、全局 CSS 缓存和减少初始请求体积。
迁移步骤
此特性可通过配置完全控制,您可以通过设置 inlineStyles: true
恢复之前的行为,即对全局 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
会自动为你处理)
// 在动态页面(例如 `[slug].vue`)中,这样使用是不安全的,因为路由的 slug 会影响请求的数据,
// 而 Nuxt 无法感知这一点,因为它未反映在 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
。
变更原因
之前 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
选项的布尔值支持 移除调用
🚦 影响级别:最小
变化内容
之前调用 refresh
时可以传递 dedupe: boolean
。它们是 cancel
(true
)和 defer
(false
)的别名。
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 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
。清空数据时应尊重这一设定。
useAsyncData
和 useFetch
中 pending
值的对齐
🚦 影响级别:中等
useAsyncData
、useFetch
、useLazyAsyncData
和 useLazyFetch
返回的 pending
现在是计算属性,仅在 status
也处于挂起状态时为 true
。
变化内容
当传入 immediate: false
时,pending
会在首次请求发起前保持为 false
,这与之前的行为不同(之前首次请求前 pending
总是为 true
)。
变更原因
此变更使 pending
的含义与 status
属性一致,当请求进行时两者均为挂起状态。
迁移步骤
如果依赖 pending
属性,请确保逻辑考虑到新行为,即 pending
仅在 status
也处于挂起时为 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,
},
})
useAsyncData
和 useFetch
中 key 变更行为
🚦 影响级别:中等
变化内容
在 useAsyncData
或 useFetch
中使用响应式 key 时,当 key 变化会自动重新请求数据。设置 immediate: false
时,useAsyncData
只有在数据已请求过一次后,key 变化才会重新请求。
之前,useFetch
的行为略有不同,会在 key 变化时始终请求数据。
现今,useFetch
与 useAsyncData
行为一致 —— 只有在数据已请求过一次后,key 变化才重新请求。
变更原因
保证 useAsyncData
与 useFetch
行为一致,避免意外请求。
若设置了 immediate: false
,需调用 refresh
或 execute
,否则数据永远不会被请求。
迁移步骤
此更改一般提升预期行为,但如果你期望在非立即加载的 useFetch
中通过改变 key 或选项自动请求,现在需要手动首次触发。
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
对象现在是 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。
如果你是模块作者并想兼容 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__
对象 移除
变化内容
在应用完成 hydration 后,我们移除了全局的 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。
迁移步骤
我们已针对使用 EJS 语法的模块提交了 PR,但如果你需要自行处理,有三种兼容方案:
- 将字符串插值逻辑直接写入
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 })
+ },
})
最后,如果你使用了模板工具函数(如 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
默认值由 false
改为 true
。
变更原因
这是之前 3.12 配置更新 的后续,改进了默认配置,主要遵循 TotalTypeScript 的建议。
迁移步骤
两种方案:
- 对应用运行类型检查并修复新报错(推荐)。
- 在
nuxt.config.ts
中覆写默认配置:export default defineNuxtConfig({ typescript: { tsConfig: { compilerOptions: { noUncheckedIndexedAccess: false, }, }, }, })
TypeScript 配置拆分
🚦 影响级别:最小
变化内容
Nuxt 现为不同环境生成独立的 TypeScript 配置文件,提升类型检测体验:
- 新的 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
的项目继续正常使用。 - 项目引用选项(Project References):新项目或需要更好类型检查的可以采用。
- 针对不同环境的上下文类型检查:每个环境都拥有合适的编译选项和包含/排除规则。
- 新增
typescript.nodeTsConfig
选项:可定制 Node.js 构建时的 TypeScript 配置。
变更原因
此变更带来多重好处:
- 更好类型安全:各环境拥有匹配的全局变量和 API 检查。
- 提升 IDE 体验:针对不同代码部分更好的智能提示和错误报告。
- 清晰分离:避免服务端代码误引用客户端 API,反之亦然。
- 性能提升:TypeScript 可更高效地检查限定范围代码。
例如,你的 nuxt.config.ts
中不再支持自动导入,之前 TS 不会报错。IDE 虽识别服务端目录的 tsconfig.json
,但类型检查未反映,此变化补足此差异。
迁移步骤
无需迁移,现有项目照旧支持。
若想利用更好类型检查,可采用新项目引用方式:
- 更新根目录
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: { // 定制 app/server 的 TypeScript 配置 tsConfig: { compilerOptions: { strict: true, }, }, // 定制构建时的 TypeScript 配置 nodeTsConfig: { compilerOptions: { strict: true, }, }, }, })
- 更新 CI/构建脚本,确保使用新项目引用方式。
新配置为可选,提供了更好的类型安全和智能提示,同时对现有项目保持完整兼容。
移除实验性功能配置
🚦 影响级别:最小
变化内容
Nuxt 4 不再允许配置以下四个实验性功能:
experimental.treeshakeClientOnly
将默认开启(v3.0 以来即默认)experimental.configSchema
将默认开启(v3.3 以来默认)experimental.polyfillVueUseHead
将默认关闭(v3.4 以来默认)experimental.respectNoSSRHeader
将默认关闭(v3.4 以来默认)vite.devBundler
不再可配置,将默认使用vite-node
变更原因
这些选项长期默认开启或关闭,暂无理由保留为可配置状态。
迁移步骤
generate
配置项 移除顶层
🚦 影响级别:最小
变化内容
Nuxt 4 不再支持顶层的 generate
配置,包括其所有属性:
generate.exclude
—— 排除 prerender 路由generate.routes
—— 指定 prerender 路由
变更原因
顶层 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']
+ }
+ }
})
Nuxt 2 与 Nuxt 3+ 比较
下表简要比较了 Nuxt 三个版本:
特性 / 版本 | Nuxt 2 | Nuxt Bridge | Nuxt 3+ |
---|---|---|---|
Vue | 2 | 2 | 3 |
稳定性 | 😊 稳定 | 😊 稳定 | 😊 稳定 |
性能 | 🏎 快 | ✈️ 更快 | 🚀 最快 |
Nitro 引擎 | ❌ | ✅ | ✅ |
ESM 支持 | 🌙 部分支持 | 👍 改进 | ✅ |
TypeScript | ☑️ 选用引入 | 🚧 部分支持 | ✅ |
组合式 API | ❌ | 🚧 部分支持 | ✅ |
选项式 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 2 中使用部分 Nuxt 3+ 特性,需主动开启。