升级指南
Maschinelle Übersetzung ·3 % geprüft
升级 Nuxt
最新版本
要将 Nuxt 升级到最新版本,请使用 nuxt upgrade 命令。
npx nuxt upgradeyarn nuxt upgradepnpm nuxt upgradebun x nuxt upgradedeno x nuxt upgrade夜版发布渠道
要使用最新的 Nuxt 构建版本,并在功能发布前进行测试,请阅读夜版发布渠道指南。
测试 Nuxt 5
Nuxt 5 目前正在开发中。从 Nuxt 4.2+ 开始,你可以通过将 future.compatibilityVersion 设置为 5 来测试它的许多破坏性变更:
export default defineNuxtConfig({
future: {
compatibilityVersion: 5,
},
})随后,Nuxt 配置中的各项默认值会发生变化,以选择启用 Nuxt 5 行为。在 Nuxt 4 中,这包括:
- Vite Environment API:启用
experimental.viteEnvironmentApi,改变 Vite 插件的注册和配置方式 - 区分大小写的路由:页面路由会严格匹配 URL 的大小写,与 Nitro 保持一致,因此
/About不再匹配pages/about.vue - 规范化的页面名称:页面组件名称与路由名称保持一致,确保
<KeepAlive>行为一致 - 可序列化的页面 meta:
experimental.extractSerializablePageMeta会在构建时提取可静态分析的definePageMeta值 - 客户端负载提取:
experimental.payloadExtraction会变为'client',将负载内联到预渲染页面的 HTML 中 clearNuxtState重置为默认值:clearNuxtState会将状态重置为useState的初始值,而不是将其设为undefined- 非异步的
callHook:callHook可能返回void,而不再总是返回Promise navigateTo提前返回:experimental.navigateToEarlyReturn会阻止中间件在重定向后继续执行- 仅限服务器端的 head 组合式函数:
useServerHead、useServerHeadSafe和useServerSeoMeta不再自动导入,unhead.legacy会被忽略,Unhead 的旧版插件不再注册,并且始终启用 Capo 排序 - 默认关闭 PostCSS:除非你在
postcss.plugins中启用autoprefixer和cssnano,否则不会再配置它们 - 带键的组合式函数需要
source:没有字符串source的optimization.keyedComposables条目,不再通过其自动导入项进行匹配 - 注释节点占位符:仅客户端组件使用注释节点而不是
<div>作为 SSR 占位符 - 类型化页面:启用
experimental.typedPages,以支持经过类型检查的路由 - 错误响应格式:启用
experimental.inlineErrorRendering,因此页面错误会返回错误页面,服务器路由错误会返回 JSON,且不受请求标头影响 - 类型化
$fetch:experimental.routeTypedFetch会根据生成的路由类型为$fetch和useFetch提供类型 - 关闭 Nitro 自动导入:服务器代码会显式导入辅助函数,例如从
nuxt/server导入,而不是依赖自动导入兼容层。设置experimental.nitroAutoImports: true可继续使用自动导入 - 禁用 Vue Options API:Options API 会从客户端包中编译移除,以减小其体积
- 更严格的副作用导入检查:生成的
tsconfig.json会启用noUncheckedSideEffectImports - 忽略 TypeScript
baseUrl:生成的 TypeScript 配置不再使用compilerOptions.baseUrl解析 Nuxt 别名
每项变更的迁移步骤,以及此次升级带来的其他所有内容,都记录在 Nuxt 5 升级指南中。
迁移到 nuxt/server
从 Nuxt 4.6 开始,服务器代码可以从 nuxt/server 而不是 h3 导入处理程序和辅助函数,因此同一个文件可以在 Nuxt 4(nitropack v2 和 h3 v1)以及 Nuxt 5(Nitro v3 和 h3 v2)上运行。
在 Nuxt 4.6 中,请从 nuxt/server 显式导入 defineEventHandler 和辅助函数。同名的自动导入项属于 h3 v1,而 h3 的 defineEventHandler 传递给处理程序的事件,与 nuxt/server 辅助函数读取的事件不同。
+ import { defineEventHandler, getQuery } from 'nuxt/server'
+
export default defineEventHandler((event) => {
const { name } = getQuery<{ name?: string }>(event)
return { message: `Hello, ${name ?? 'world'}!` }
})在 Nuxt 5 中,这些同名项会从 nuxt/server 自动导入,因此在 Nuxt 4.6 中编写的显式导入可以继续原样使用。
与 h3 v1 的差异
| h3 v1 | nuxt/server |
|---|---|
sendRedirect(event, location) 会发送响应。 | sendRedirect(event, location) 会返回响应体:return sendRedirect(event, '/login')。 |
getRouterParam(event, name) 和 getRouterParams(event) 会返回解码后的值。 | 它们会返回 URL 中显示的值。传入 { decode: true } 可对其进行解码。 |
handleCors(event, options) 在处理预检请求时会返回 true。 | handleCors(event, options) 会返回应由处理程序返回的预检 Response,或 false:const preflight = handleCors(event, options); if (preflight) return preflight。 |
handleCors 在运行时接受单个来源字符串。 | origin 可以是 '*'、'null'、来源或模式数组,也可以是函数;单个来源应包装在数组中。maxAge 是以秒为单位的字符串(例如 '86400'),或 false。请从 nuxt/server 导入 CorsOptions 类型。 |
readValidatedBody 和 getValidatedQuery 接受一个校验函数。 | 它们接受一个 Standard Schema 或一个函数。 |
createError({ statusCode, statusMessage }) | createError({ status, statusText }) |
isError(error) | isNuxtError(error) |
h3 的 useSession、getSession、updateSession 和 clearSession 会使用你传入的 password 密封一个 h3 Cookie。 | 它们会密封一个 nuxt-session Cookie,默认使用从 appSecret 派生的密钥。两者不能互换:其中一种签发的会话无法由另一种解封。 |
useRuntimeConfig(event) | useRuntimeConfig()。没有按请求读取配置的形式。 |
全局 $fetch | nuxt/server 不提供此功能。使用 serverFetch(event, path) 在进程内调用应用路由,它会返回 Response。对于其他 URL,原生 fetch 在非 2xx 状态时不会抛错,也不会解析 JSON:请检查 response.ok 并调用 response.json()。 |
event.path | getRequestURL(event).pathname 或 event.url.pathname。 |
event.node | nuxt/server 不提供此属性。请使用 event.req 和 event.res。 |
getResponseHeader、setResponseHeader、appendResponseHeader、removeResponseHeader | event.res.headers.get()、.set()、.append()、.delete() |
getMethod(event)、isMethod(event, method)、assertMethod(event, method) | 比较 event.req.method。 |
readMultipartFormData(event) | await event.req.formData()。 |
readRawBody(event)、getRequestWebStream(event) | 使用 await event.req.arrayBuffer()(或 .text())读取原始请求体,使用 event.req.body 读取流。 |
sanitizeStatusCode | 不提供。 |
nuxt/server 之外的辅助函数
对于需要使用 nuxt/server 未提供的 h3 或 Nitro 辅助函数(例如 readMultipartFormData、proxyRequest、useStorage 或 defineCachedEventHandler)的处理程序,请继续从 h3(或 nitropack/runtime)为该处理程序导入 defineEventHandler 和这些辅助函数。这样编写的代码与服务器运行时绑定,需要在 Nuxt 5 中重新检查。
import { defineEventHandler, readMultipartFormData } from 'h3'
export default defineEventHandler(async (event) => {
const parts = await readMultipartFormData(event)
return { received: parts?.length ?? 0 }
})NUXT_E8012
当 nuxt/server 的辅助函数(例如 readValidatedBody、getValidatedQuery、handleCors 或 useSession)收到 h3 事件时,请求会失败并返回一条以 [NUXT_E8012] 开头的 500 错误消息。这意味着该处理程序是用 h3 的 defineEventHandler 定义的(通常是自动导入的那个)。请在与辅助函数相同的文件中从 nuxt/server 导入 defineEventHandler。
应用密钥
nuxt/server 的会话辅助函数和 deriveSecret 会读取 runtimeConfig.appSecret,你可以通过 NUXT_APP_SECRET 设置该值。在 Nuxt 4 中,nitropack v2 会解析环境变量覆盖值,因此仅由数字组成的密钥会被解析为数字;请参阅应用密钥,了解如何避免此问题。
迁移到 Nuxt 4
Nuxt 4 包含了重要的改进和变更。本指南将帮助你将现有的 Nuxt 3 应用迁移到 Nuxt 4。
首先,升级到 Nuxt 4:
npm install nuxt@^4.0.0yarn add nuxt@^4.0.0pnpm add nuxt@^4.0.0bun add nuxt@^4.0.0deno add npm:nuxt@^4.0.0升级后,大多数 Nuxt 4 行为现在都是默认行为。不过,如果你在迁移期间需要保持向后兼容,仍然可以配置某些功能。
以下部分详细介绍升级到 Nuxt 4 时的主要变更和所需的迁移步骤。
下文记录了具有破坏性或较为重要的变更,以及相应的迁移步骤和可用的配置选项。
使用 Codemods 迁移
为了简化升级流程,我们与 Codemod 团队合作,使用一些开源 codemod 自动完成了许多迁移步骤。
npx codemod feedback 向 Codemod 团队报告 🙏如需查看 Nuxt 4 codemod 的完整列表、每个 codemod 的详细信息及其源代码,以及多种运行方式,请访问 Codemod Registry。
你可以使用以下 codemod recipe 运行本指南中提到的所有 codemod:
# Angepinnte Version wegen https://github.com/codemod/codemod/issues/1710
npx codemod@0.18.7 nuxt/4/migration-recipe# Angepinnte Version wegen https://github.com/codemod/codemod/issues/1710
yarn dlx codemod@0.18.7 nuxt/4/migration-recipe# Angepinnte Version wegen https://github.com/codemod/codemod/issues/1710
pnpm dlx codemod@0.18.7 nuxt/4/migration-recipe# Angepinnte Version wegen https://github.com/codemod/codemod/issues/1710
bun x codemod@0.18.7 nuxt/4/migration-recipe# Angepinnte Version wegen https://github.com/codemod/codemod/issues/1710
deno x codemod@0.18.7 nuxt/4/migration-recipe此命令会依次执行所有 codemod,你可以选择取消不想运行的 codemod。下文也会列出每个 codemod 及其对应的变更,你可以单独运行它们。
新的目录结构
🚦 影响级别:重要
Nuxt 现在默认使用新的目录结构,同时保持向后兼容(因此,如果 Nuxt 检测到你使用的是旧结构,例如顶层的 app/pages/ 目录,就不会应用此新结构)。
👉 查看完整 RFC
变更内容
- 新的 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。
变更原因
- 性能 —— 将所有代码放在仓库根目录,会导致
.git/和node_modules/文件夹被文件系统监视程序扫描或包含,从而引发问题;在非 Mac OS 系统上,这可能会明显延迟启动。 - 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({
// Dies setzt den neuen Standardwert von srcDir von `app` wieder auf dein Stammverzeichnis zurück
srcDir: '.',
// Dies legt das Verzeichnispräfix für `router.options.ts` und `spa-loading-template.html` fest
dir: {
app: 'app',
},
})单例数据获取层
🚦 影响级别:中等
变更内容
Nuxt 的数据获取系统(useAsyncData 和 useFetch)进行了大幅重组,以提升性能和一致性:
- 相同 key 共享 refs:现在,所有使用相同 key 调用
useAsyncData或useFetch的地方都会共享相同的data、error和statusrefs。这意味着,所有带有显式 key 的调用都不能使用互相冲突的deep、transform、pick、getCachedData或default选项。 - 更灵活地控制
getCachedData:现在每次获取数据时都会调用getCachedData函数,即使这是由 watcher 或调用refreshNuxtData触发的。(以前在这些情况下总是会获取新数据,而不会调用此函数。)为了更好地控制何时使用缓存数据、何时重新获取数据,该函数现在会收到一个包含请求触发原因的上下文对象。 - 支持响应式 key:现在可以使用计算 refs、普通 refs 或 getter 函数作为 key,从而支持自动重新获取数据(并单独存储数据)。
- 数据清理:当最后一个使用
useAsyncData获取数据的组件卸载时,Nuxt 会移除这些数据,以避免内存持续增长。
变更原因
这些变更旨在改善内存使用情况,并提高多个 useAsyncData 调用之间加载状态的一致性。
迁移步骤
检查不一致的选项:检查是否有组件使用相同的 key,却指定了不同的选项或获取函数。
// Dies löst jetzt eine Warnung aus const { data: users1 } = useAsyncData('users', () => $fetch('/api/users'), { deep: false }) const { data: users2 } = useAsyncData('users', () => $fetch('/api/users'), { deep: true })你可以考虑将共享显式 key(且带有自定义选项)的
useAsyncData调用提取到它们自己的组合式函数中: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,
},
})修正图层中的模块加载顺序
🚦 影响级别:最小
变更内容
使用 Nuxt 图层时,模块的加载顺序已得到修正。此前,项目根目录中的模块会在扩展图层中的模块之前加载,这与预期行为相反。
现在,模块会按正确顺序加载:
- 首先加载图层模块(按扩展顺序——较深的图层优先)
- 最后加载项目模块(优先级最高)
这会影响:
nuxt.config.ts中modules数组定义的模块- 从
modules/目录自动发现的模块
变更原因
此变更确保:
- 扩展图层的优先级低于使用它的项目
- 模块执行顺序符合直观的图层继承模式
- 在多图层配置中,模块配置和钩子能按预期工作
迁移步骤
大多数项目无需更改,因为此次变更修正了加载顺序,使其符合预期行为。
但是,如果你的项目依赖之前错误的顺序,可能需要:
- 检查模块依赖:检查是否有模块依赖特定的加载顺序
- 调整模块配置:如果模块配置是为了适配错误的顺序而设置的
- 充分测试:确保所有功能在修正后的顺序下都能按预期工作
新的正确顺序示例:
// 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'],
})
// Ladereihenfolge (korrigiert):
// 1. layer-module-1
// 2. layer-module-2
// 3. project-module-1 (kann Layer-Module überschreiben)
// 4. project-module-2 (kann Layer-Module überschreiben)如果你因为需要注册钩子而遇到模块顺序依赖问题,请考虑在需要调用钩子的模块中使用 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 加载屏幕的新 DOM 位置
🚦 影响级别:最小
变更内容
客户端渲染页面(ssr: false)时,加载屏幕(默认值为 ~/app/spa-loading-template.html;在 Nuxt 4 中改为 ~/spa-loading-template.html)渲染在 Nuxt 应用根节点内:
<div id="__nuxt">
<!-- SPA-Ladevorlage -->
</div>现在,加载屏幕默认渲染在 Nuxt 应用根节点旁边:
<div id="__nuxt"></div>
<!-- SPA-Ladevorlage -->变更原因
这样能确保加载屏幕在 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)的别名。
// @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 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 也处于挂起状态时才为 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()。 - 使用自定义函数处理替换,如 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: { /* 一些选项 */ }
- 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 中不再支持自动导入,之前 TypeScript 不会报错。IDE 虽能识别服务端目录的 tsconfig.json,但类型检查并未反映这一点。此变更补足了这一差异。
迁移步骤
无需迁移,现有项目保持不变即可继续工作。
若想利用更好的类型检查,可采用新的项目引用方式:
更新根目录
tsconfig.json,添加项目引用:如果你的tsconfig.json当前有一行"extends": "./.nuxt/tsconfig.json",请在添加引用之前删除它。项目引用与扩展是互斥的。{ // 如果存在则移除 "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/目录之外,将无法兼容新的项目引用机制。- 应用端的增强放入
如需定制 TypeScript 配置,在配置中添加:
export default defineNuxtConfig({ typescript: { // 自定义 tsconfig.app.json tsConfig: { // ... }, // 自定义 tsconfig.shared.json sharedTsConfig: { // ... }, // 自定义 tsconfig.node.json nodeTsConfig: { // ... }, }, nitro: { typescript: { // 自定义 tsconfig.server.json tsConfig: { // ... }, }, }, })- 更新 CI/构建脚本,确保使用新的项目引用方式。
新配置为可选项:它能提供更好的类型安全与智能提示,同时对现有项目保持完整兼容。
移除实验性功能配置
🚦 影响级别:最小
变化内容
Nuxt 4 不再允许配置以下五个实验性功能:
experimental.treeshakeClientOnly:将默认开启(自 v3.0 起即默认)experimental.configSchema:将默认开启(自 v3.3 起即默认)experimental.polyfillVueUseHead:将默认关闭(自 v3.4 起即默认)experimental.respectNoSSRHeader:将默认关闭(自 v3.4 起即默认)experimental.viteEnvironmentApi:现在始终启用,且不可禁用
变更原因
这些选项长期默认开启或关闭,目前没有理由保留为可配置状态。
迁移步骤
移除顶层 generate 配置项
🚦 影响级别:最小
变化内容
Nuxt 4 不再支持顶层的 generate 配置,包括其所有属性:
generate.exclude—— 排除预渲染路由generate.routes—— 指定预渲染路由
变更原因
顶层 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']
+ }
+ }
})标准化页面组件名称
🚦 影响级别:最小
变化内容
当设置 future.compatibilityVersion 为 5(或启用 experimental.normalizePageNames)时,页面组件名称将与其路由名称匹配,而不是使用文件名。例如,pages/foo/index.vue 组件名将变为 foo,而不是 index。
变更原因
之前,Vue 会根据文件名为组件命名,导致多个页面(如 pages/foo/index.vue 和 pages/bar/index.vue)都拥有组件名 index。这会导致 <KeepAlive> 结合 include/exclude 过滤器时效果不可靠,并且需要在每个页面手动添加 defineOptions({ name: '...' })。
迁移步骤
如果你依赖当前的组件名(例如在 <KeepAlive> 的 include 或 exclude 列表中),请更新为使用路由名称,而非文件名。
<template>
<NuxtPage :keepalive="{
- include: ['index']
+ include: ['foo']
}" />
</template>若要禁用此行为:
export default defineNuxtConfig({
experimental: {
normalizePageNames: false,
},
})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+ 特性,需主动启用。
Was this helpful?