要将 Nuxt 升级到最新版本,请使用 nuxt upgrade 命令。
npx nuxt upgrade
yarn nuxt upgrade
pnpm nuxt upgrade
bun x nuxt upgrade
要使用 Nuxt 的最新构建版本并测试新功能(尚未正式发布),请阅读夜版发布渠道指南。
Nuxt 5 目前正在开发中。在发布之前,可以测试许多 Nuxt 5 从 Nuxt 版本 4.2+ 的重大更改。
首先,将 Nuxt 升级到 最新版本。
然后,您可以将 future.compatibilityVersion 设置为匹配 Nuxt 5 的行为:
export default defineNuxtConfig({
future: {
compatibilityVersion: 5,
},
})
当您将 future.compatibilityVersion 设置为 5 时,您在 Nuxt 配置中的默认值将更改为选择 Nuxt v5 的行为,包括:
future.compatibilityVersion: 5 测试 Nuxt 5,请定期回来查看。重大或显著的变化将在下面注明,并附上向后/向前兼容的迁移步骤。
🚦 影响级别:中等
Nuxt 5 迁移到 Vite 6 的新 环境 API,该 API 正式化了环境的概念,并提供了对每个环境配置的更好控制。
之前,Nuxt 使用单独的客户端和服务器 Vite 配置。现在,Nuxt 使用共享的 Vite 配置,并配备特定于环境的插件,这些插件使用 applyToEnvironment() 方法来针对特定环境。
future.compatibilityVersion: 5(请参阅 测试 Nuxt 5)或通过显式启用 experimental.viteEnvironmentApi: true 来提前测试此功能。关键变化:
extendViteConfig(): extendViteConfig() 中的 server 和 client 选项已被弃用,使用时将显示警告。addVitePlugin() 注册的 Vite 插件,如果仅针对一个环境(通过传递 server: false 或 client: false)将不会调用其 config 或 configResolved 钩子。vite:extendConfig 和 vite:configResolved 钩子现在与共享配置一起工作,而不是单独的客户端/服务器配置。Vite 环境 API 提供了:
我们建议您使用 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'
},
}))
您可以使用插件中的新 applyToEnvironment 钩子,而不是使用 addVitePlugin 结合 server: false 或 client: false。
// 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'
},
}))
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 时的主要变化和迁移步骤。
重大或破坏性更改均已记录,并附带迁移步骤及可用配置选项。
为了简化升级流程,我们与 Codemod 团队合作,提供了多个开源的自动化迁移脚本。
npx codemod feedback 向 Codemod 团队反馈 🙏关于 Nuxt 4 Codemods 的完整列表、详细信息、源码及多种运行方式,请访问 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
该命令会依次执行所有 codemods,并允许您取消选择不希望运行的项。每个 codemod 也在下面按其对应更改列出,可单独运行。
🚦 影响级别:重大
Nuxt 现在默认采用新的目录结构,同时保持向后兼容(如果 Nuxt 检测到您使用的是旧结构,比如存在顶层的 app/pages/ 目录,则不会应用此新结构)。
👉 查看完整 RFC
srcDir 变为 app/,大多数资源都会从这里解析。serverDir 默认值由 <srcDir>/server 改为 <rootDir>/serverlayers/、modules/ 和 public/ 目录默认相对于 <rootDir> 解析content/ 目录默认相对于 <rootDir> 解析dir.app 配置项,指定查找 router.options.ts 和 spa-loading-template.html 的目录,默认是 <srcDir>/.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 系统下极大延长启动时间。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)经过重大重组,以提升性能与一致性:
useAsyncData 或 useFetch 的地方都会共享相同的 data、error 和 status 引用。这意味着带有显式 key 的调用,其 deep、transform、pick、getCachedData 或 default 选项必须一致,避免冲突。getCachedData:getCachedData 函数现在每次数据获取时都会调用,包括由侦听器触发或手动调用 refreshNuxtData 时。(之前这些情况下不会调用此函数。)为更灵活地控制何时使用缓存数据、何时重新请求,函数会接收包含请求原因的上下文对象。useAsyncData 数据的组件卸载时,Nuxt 会自动移除相关数据,避免内存占用不断增长。此改动提升内存利用效率,同时统一 useAsyncData 各调用处的加载状态表现。
// 现在会触发警告
const { data: users1 } = useAsyncData('users', () => $fetch('/api/users'), { deep: false })
const { data: users2 } = useAsyncData('users', () => $fetch('/api/users'), { deep: true })
export 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,
},
})
🚦 影响级别:最小
使用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,
},
})
🚦 影响级别:最小
用于生成 <head> 标签的 Unhead 升级到版本 2。尽管大多数兼容,但底层 API 有若干破坏性变更:
vmid、hid、children、body上述变更对应用影响较小。
如果遇到问题,请检查:
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,
},
})
🚦 影响级别:最小
客户端渲染页面(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 })
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()。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 自动完成此步骤🚦 影响级别:最小
compilerOptions.noUncheckedIndexedAccess 默认值由 false 改为 true。
这是之前 3.12 配置更新 的后续,改进了默认配置,主要遵循 TotalTypeScript 的建议。
两种方案:
nuxt.config.ts 中覆写默认配置:export default defineNuxtConfig({
typescript: {
tsConfig: {
compilerOptions: {
noUncheckedIndexedAccess: false,
},
},
},
})
🚦 影响级别:最小
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.nodeTsConfig 选项:可定制 Node.js 构建时的 TypeScript 配置。此变更带来多重好处:
例如,你的 nuxt.config.ts 中不再支持自动导入,之前 TS 不会报错。IDE 虽识别服务端目录的 tsconfig.json ,但类型检查未反映,此变化补足此差异。
无需迁移,现有项目照旧支持。
若想利用更好类型检查,可采用新项目引用方式:
tsconfig.json,添加项目引用:tsconfig.json 当前有一行 "extends": "./.nuxt/tsconfig.json",在添加引用之前请删除它。项目引用和扩展是互斥的。{
// 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" }
]
}
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/ 目录外,无法兼容新项目引用机制。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: {
// ...
},
},
},
})
新配置为可选,提供了更好的类型安全和智能提示,同时对现有项目保持完整兼容。
🚦 影响级别:最小
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 三个版本:
| 特性 / 版本 | 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 Bridge。它是一个兼容层,允许你在 Nuxt 2 中使用部分 Nuxt 3+ 特性,需主动开启。