Nuxt:useAsyncData、Nitro 与混合渲染
最后核验:2026-06-30|适用版本:Nuxt 2–4(示例锁定 4.4.8)|前置知识:Vue、HTTP、Node.js
Nuxt 的约定式能力边界
Nuxt 在 Vue 之上提供文件路由、服务端渲染、数据载荷、自动导入、Nitro 服务端和部署 preset。需要 Vue 全栈渲染与统一服务端入口时,这些约定减少手工组合;纯客户端局部组件不需要为了自动导入承担服务端和部署模型。
Nuxt 4 使用 app/ 目录隔离应用代码,并用独立 TypeScript 项目区分 app、server、shared 和配置上下文。自动导入减少样板,但读代码时仍要知道 API 来自 Vue、Nuxt 还是 Nitro。
文件路由与服务端首屏
app/pages 形成路由,app/layouts 提供布局,server/api 进入 Nitro。初次请求在服务端执行页面 setup,生成 HTML 和数据载荷;客户端用相同载荷水合,导航时再通过 Vue Router 和 Nuxt 数据协议更新。
服务端和客户端首屏必须得到确定一致的输入。浏览器 API 放进 onMounted 或客户端插件;时间、随机数和设备状态不能直接改变首屏模板。
目录约定如何组成应用
Nuxt 4 默认把浏览器应用放进 app/:pages/ 生成 Vue Router 路由,layouts/ 包住页面,components/ 可自动导入组件,composables/ 和 utils/ 提供自动导入逻辑,plugins/ 扩展 Nuxt/Vue 实例,middleware/ 执行路由导航检查。server/ 属于 Nitro 服务端,shared/ 才是可同时被两侧安全引用的无环境依赖代码。
app/
├── app.vue
├── layouts/default.vue
├── pages/
│ ├── index.vue
│ └── entries/[id].vue
├── middleware/auth.ts
└── plugins/analytics.client.ts
server/
├── api/entries/[id].get.ts
└── middleware/request-id.ts
shared/
└── types/entry.ts<NuxtPage> 承载当前页面,<NuxtLayout> 选择布局。页面可用 definePageMeta 声明 layout、middleware、key 和自定义元数据;动态参数从 useRoute().params 读取并做运行时校验。页面 key 决定何时复用组件实例,错误地绑定整个 fullPath 会让 query 每变一次就销毁所有局部状态。
路由 middleware 在初次 SSR 和客户端导航都可能运行,适合重定向与导航体验;它不是数据授权层。Nitro server middleware 则处理每个匹配的服务端请求,不能混用 useRoute 等客户端上下文。插件用 .client、.server 后缀限制执行环境;无后缀插件在 SSR 与浏览器各执行一次,注册全局监听器或发埋点时必须避免双重副作用。
首个 HTML 请求的执行顺序大致是 Nitro 接收请求、匹配路由、建立 Nuxt app、运行插件/middleware、执行页面 setup 与数据 composable、渲染 Vue HTML、序列化 payload。浏览器创建新的 Nuxt app,读取 payload 后水合,再进入正常 Vue Router 导航。模块全局变量不会自动成为“请求级状态”,在长驻 Node 进程中会跨用户共享;请求状态应放 useState、event context 或每次创建的实例中。
useAsyncData 的键、载荷与去重
useAsyncData(key, handler) 管理异步数据、错误、状态、载荷传输和重新执行。键决定同一请求和水合过程中的共享身份;同键的 handler 和关键选项必须兼容。handler 应有确定返回值,服务端副作用使用 callOnce 或明确的服务器接口。
<script setup lang="ts">
const { data, error, status, refresh } = await useAsyncData(
'technology-catalog',
() => $fetch('/api/catalog'),
);
</script>无键调用可以由编译器生成位置键,但公共组合函数应显式命名,便于诊断缓存和重复请求。refresh 重新执行当前数据源,不等于清除所有 Nitro 或 CDN 缓存。
useAsyncData 返回的 data、error、status 是 Vue ref。handler 在 SSR 成功返回的值会进入 Nuxt payload,客户端水合时读取而不重复调用;若 handler 返回 undefined/null 或值无法正确序列化,客户端可能再次执行。handler 应保持无副作用、返回明确值,数据库写入、cookie 修改和埋点不要藏在里面。
export function useCatalog(locale: Ref<string>) {
return useAsyncData(
() => `catalog:${locale.value}`,
(_nuxtApp, { signal }) =>
$fetch('/api/catalog', {
query: { locale: locale.value },
signal,
}).then(parseCatalog),
{
watch: [locale],
deep: false,
dedupe: 'cancel',
default: () => [],
},
);
}key 是跨 SSR、payload、水合和同页消费者的身份。相同 key 的 handler、deep、transform、pick、getCachedData 等关键选项必须一致,否则两个组件以为共享同一数据,实际对结果形状和刷新规则理解不同。把 locale、tenant、排序或权限等影响结果的维度漏出 key,会复用错误内容;把时间戳放 key 又会消灭去重并无限增长 payload。
lazy: false 的数据默认参与导航等待;lazy: true 允许先导航再展示 pending,useLazyAsyncData 是相同意图的便捷形式。server: false 会让首个 SSR 不执行 handler,水合完成前 data 仍为空,适合纯浏览器能力但会牺牲首屏内容。immediate: false 需要手工 execute,不应假设 await composable 后已有值。
deep: true 为结果创建深层响应式,庞大只读响应可用 deep: false 减少代理成本;pick 或 transform 在进入 payload 前裁剪字段。裁剪是传输/性能决策,不是权限措施:无权数据根本不应从服务端查询或交给 handler。
refresh() 遵循当前 key 和 handler 重新获取,clear() 清掉当前 AsyncData 状态,refreshNuxtData(key) 可刷新匹配 key 的数据。它们不会自动清除 Nitro cached event handler、反向代理或浏览器 HTTP cache。调试重复请求时记录 key、SSR/CSR 环境、payload 是否命中和实际网络层,不能只数 handler 日志。
useFetch 与请求取消
useFetch 是针对 URL 请求的 useAsyncData 包装,自动生成键并使用 $fetch。响应仍是不可信输入,进入组件前要运行时验证。Nuxt 4.2 支持把 AbortSignal 传给 useAsyncData/useFetch 的请求;取消属于生命周期控制,不应显示成网络故障。Nuxt 4.2
| 情况 | 首选 | 原因 |
|---|---|---|
| 直接请求 URL | useFetch | 键与请求选项集成 |
| 多步骤或非 HTTP 异步 | useAsyncData | handler 自定义 |
| 仅客户端事件请求 | $fetch | 不需要 SSR payload |
| 服务端内部 API | 直接调用共享函数 | 避免无意义自请求 |
不要在服务端渲染中用裸 $fetch 后又在客户端重复执行同一请求;需要载荷复用时使用 useAsyncData 或 useFetch。
$fetch 基于 ofetch,适合事件触发的命令请求和 Nitro handler 内部调用。服务端 $fetch('/api/catalog') 可通过 Nitro 本地调用避免真实网络回环,但仍经过 handler 信任边界;若页面和 handler 属于同一部署且共享领域函数,直接调用服务层更容易传递类型、事务和取消。
useFetch 的 URL、query、headers 等可以是响应式来源;变化会触发重新请求并影响自动 key。不要内联每次都创建且语义变化的复杂对象,公共封装应使用稳定 option 并显式 key。请求取消只保证客户端不再等待/处理结果,服务端已开始的写操作未必回滚,因此 GET 可取消,POST 写入仍需幂等与事务。
浏览器调用同源 Nitro API 时 cookie 自然随请求策略发送;SSR 中转外部或上游 cookie/headers 必须使用受控白名单,不能把用户全部请求头转发到任意 URL。useRequestFetch 可携带请求上下文,但敏感 header、host 与 forwarded 信息仍需按可信代理配置处理。
Nitro 请求与部署边界
Nitro 处理 server/api、middleware、存储和不同平台输出。服务端 handler 使用 H3 事件读取参数、请求体和上下文,信任边界在这里执行认证、授权与运行时验证。runtimeConfig 的私有字段只留服务端,public 字段会进入客户端载荷。
Nitro preset 决定产物适配 Node、serverless、edge 或静态托管。运行时 API、文件系统、连接复用和缓存能力随部署环境变化;先选择目标环境,再决定是否使用 Node 专属模块。
Nitro/H3 handler 以文件名方法后缀和目录生成路由,例如 server/api/entries/[id].patch.ts 只处理 PATCH。事件对象承载 request、params、context 和 response;读取 body、query、cookie 后都要解析为领域值。TypeScript 的泛型只约束编译期调用方,不能证明公网请求符合类型。
export default defineEventHandler(async (event) => {
const user = await requireUser(event);
const id = getRouterParam(event, 'id') ?? '';
const body = await readValidatedBody(event, updateEntrySchema.parse);
if (!/^[a-z0-9-]+$/.test(id)) {
throw createError({ statusCode: 400, statusMessage: 'Invalid entry id' });
}
const entry = await updateOwnedEntry({
id,
ownerId: user.id,
title: body.title,
});
if (!entry) {
throw createError({ statusCode: 404, statusMessage: 'Entry not found' });
}
return { entry };
});服务端插件适合建立数据库、日志和 storage driver,event context 适合请求 ID 与认证结果。数据库连接池可以进程级复用,当前用户不可以。serverless 可能并发创建多个实例,edge 可能没有 Node fs、TCP 或某些原生依赖;本地 Node preset 成功只证明一种运行时。
Nitro storage 提供统一键值接口,cachedEventHandler/defineCachedFunction 可缓存响应或函数结果。缓存键必须包含 tenant、locale、权限和所有响应变化维度;私有响应默认不缓存。对 cookie、Authorization、Host 或 body 敏感的 handler,除非明确实现安全 vary,否则不要套公开缓存。多实例部署还要确认 driver 是本地内存、共享 Redis/KV 还是平台缓存,以及失效是否传播。
runtimeConfig 私有值只应在服务器 useRuntimeConfig(event) 读取;runtimeConfig.public 会序列化到客户端。环境变量覆盖遵循 Nuxt 的命名规则,构建期把秘密写入 public 或 payload 后,运行时换变量无法撤回泄露。错误日志同样要删除 Authorization、cookie、token 和未裁剪请求体。
混合渲染与 Route Rules
Nuxt 可按路由选择服务端渲染、预渲染、客户端渲染和缓存规则。Route Rules 是 URL 级策略,适合内容页与应用页共存;错误规则可能把用户数据缓存为公开响应。
缓存必须记录作用域、键、TTL 和失效。开发模式通常不展示完整生产缓存行为,必须用生产构建和目标 preset 验证响应头、命中与过期。
Nuxt 渲染模式是按路由组合的,不是全站四选一:
| 模式 | 产生时机 | 适合 | 关键限制 |
|---|---|---|---|
| SSR | 每次请求在服务器渲染 | 个性化且需首屏 HTML | 服务端容量、请求隔离、数据延迟 |
| Prerender/SSG | 构建或爬取时生成 | 版本化内容和文档 | 构建后数据陈旧,动态路径需枚举/发现 |
CSR (ssr: false) | 浏览器下载 JS 后渲染 | 内部工具、强浏览器依赖 | 空壳首屏、SEO/弱网和水合收益丢失 |
| 混合 routeRules | 每条路由选择 prerender、swr、redirect 等 | 内容站与登录应用共仓 | 规则匹配、缓存身份和平台实现复杂 |
export default defineNuxtConfig({
routeRules: {
'/': { prerender: true },
'/docs/**': { swr: 3600 },
'/dashboard/**': { ssr: true },
'/legacy': { redirect: '/docs' },
},
});prerender 把当时输出固化为文件;swr 可先服务缓存响应再后台再生;具体是否有分布式持久缓存、如何处理并发再生和失效取决于 Nitro preset/平台。用户 dashboard 不能仅因 URL 看起来稳定就启用 SWR。规则通配、大小写、编码和尾斜杠要在部署产物上测试,尤其不能让 route middleware 成为唯一权限防线。
Nuxt Islands/服务端组件可以只渲染局部服务端内容并减少客户端 JavaScript,但请求绑定、props 序列化和缓存会新增协议面。默认组件已足够时不要为了“零 JS”拆 island;采用后需验证其 endpoint 不可跨会话读取数据,传给客户端的 props 不含秘密,交互组件的水合顺序确定。
水合、payload 与状态恢复
Nuxt payload 保存 SSR 期间 AsyncData、useState 等可序列化状态,客户端据此复用结果。它是 HTML/外部 payload 中可被用户读取的数据,不是服务器私有内存。类实例、函数、数据库句柄和秘密不能进入;复杂类型是否能经 payload reducer/reviver 恢复必须有明确契约。
水合不一致常见于 new Date()、Math.random()、服务端/客户端 locale 不同、无效 HTML 被浏览器修正、客户端插件提前改 DOM,以及用模块变量生成 ID。修复要让首帧输入确定,或用 <ClientOnly>/onMounted 把浏览器专属部分明确推迟;不能用宽泛 suppression 隐藏整个页面差异。
useState(key, init) 创建 SSR 友好的共享 ref,并把值放进 payload。key 仍需唯一,init 应纯且请求隔离。Pinia 等 store 也必须按请求创建并安全序列化,不能在模块顶层导出带当前用户数据的单例。
错误处理、性能和可观测性
页面可用 createError/showError 进入 Nuxt 错误页,Nitro handler 应抛带明确 statusCode 的 H3 error。404、401/403、校验失败和 500 是不同契约;可恢复的表单错误留在页面,未知异常记录内部 request ID 后返回稳定消息。clearError({ redirect }) 会清理全局错误并导航,不等于修复失败数据源。
性能问题跨越 Nitro、Vue、payload 和浏览器:
- 用服务端 trace 找 API/数据库瀑布,独立请求并发启动;用 payload 面板检查重复或过大字段。
- 为
useAsyncData设计稳定 key,以pick/transform/deep: false控制传输与代理成本。 - 只缓存可共享且容忍陈旧的数据,记录命中率、再生延迟和源站失败行为。
- 路由级代码分割和组件惰性加载要以产物验证;自动导入不会自动消除所有第三方副作用。
- 用 Vue Devtools/Performance 查重复 watcher、长任务、布局抖动;SSR 快不代表水合和交互快。
建议为每次请求生成 request ID,贯穿 Nitro 日志、上游请求和错误页 digest;日志标注 SSR/CSR、route、AsyncData key、cache status 和部署 region。开发 DevTools 能解释本机执行,但 serverless 冷启动、edge 限制、CDN cache 和多实例失效只能在目标 preset/预发布环境证明。
知识目录示例的运行证据
Nuxt 示例在服务端 setup 中导入并验证共享目录,首屏包含八项内容;客户端 ref 保存查询和选择,computed 派生结果,watch 只同步路由 query。
pnpm --filter @roadmap/nuxt-slice test
pnpm --filter @roadmap/nuxt-slice preview --port 4206输入:访问 http://127.0.0.1:4206/,检查源代码,搜索 nitro,选择两项后刷新。
预期结果:模型测试与 Nitro 生产构建通过;服务端首屏含目录,浏览器控制台无水合警告,URL 恢复查询与选择。示例数据是随部署发布的本地事实,不引入 API 自请求或缓存层。
Nuxt DevTools 与错误定位
Nuxt DevTools 检查路由、payload、组件和模块;服务端终端检查 Nitro;浏览器 Network 区分 HTML、payload 与 API。出现重复请求时先比较 key 和执行环境,出现水合错误时比较服务端 HTML 与客户端首次 setup 输入。
模板默认转义文本,v-html 绕过边界;服务端密钥不能进入 runtimeConfig.public、payload 或错误消息。错误页应区分 404、权限和服务器故障,不把内部堆栈发给浏览器。Nuxt Security
重大重构、路由绕过与 Nuxt 2 到 4 迁移
Nuxt 2 到 3 是完整运行时重构:Vue 2/webpack/传统 serverMiddleware 转为 Vue 3、Vite、Nitro、Composition API 和跨平台产物;Nuxt 4 再把应用移入 app/,分离 app/server/shared TypeScript 上下文,并收紧数据获取一致性。迁移不能只移动目录,必须逐一验证插件执行环境、模块兼容、payload 序列化、runtimeConfig 可见性和 Nitro preset。
| 节点 | 重大变化 | 易出严重故障的边界 |
|---|---|---|
| 2 → 3 | Vue 3 + Nitro + Composition API + 新模块体系 | 旧插件全局副作用、serverMiddleware、Vuex/axios 模块和 SSR 差异 |
| 3 → 4 | app/ 目录、数据获取清理、独立 TS 项目 | 自动导入来源、相对路径、同 key handler 与 payload 变化 |
| 4.2–4.4 | AbortSignal、smarter payload、vue-router v5、可访问性 announcer | 取消被误报、导航/route rules 匹配、缓存 payload 不一致 |
2026-06 的 CVE-2026-53721(GHSA-mm7m-92g8-7m47)是高危 route-rule middleware 绕过:vue-router 默认大小写不敏感,而 routeRules matcher 大小写敏感,攻击者改变 /admin 静态段大小写后,页面仍匹配但 routeRules.appMiddleware 未执行,SSR 甚至会返回受保护的 useFetch/useAsyncData 数据。Nuxt 4.0.0–4.4.6 与 3.11.0–3.21.6 受影响,修复为 4.4.7/3.21.7;本示例锁定 4.4.8。Nuxt 路由绕过公告
根因修正不止升级:route middleware 从来不应是服务端数据的唯一授权边界。每个 Nitro handler、外部 API 和 SSR 数据入口都重新认证授权;回归同时请求原始、大小写变体、编码变体和尾斜杠路径。同期公告还涉及 navigateTo/NuxtLink 危险协议、开发服务器暴露和 island 请求绑定,生产依赖应按官方 advisory 列表保持最新 patch,并限制外部 URL 协议。Nuxt Security Advisories
Nuxt 2 基于 Vue 2 与旧模块体系;Nuxt 3 引入 Vue 3、Nitro 与 Composition API;Nuxt 4 稳定 app/ 结构、改进数据获取一致性和 TypeScript 项目隔离。当前 v4 是 active,v3 维护至 2026-07-31;升级先启用兼容模式,再处理目录和数据获取差异。Nuxt Roadmap
验收标准:能解释 useAsyncData key、payload 与重新获取;能区分 Nuxt app 和 Nitro server;能为每条 Route Rule 写出缓存与部署前提。
Nuxt 官方资料
- Nuxt Data Fetching:
useAsyncData、useFetch与 payload;核验于 2026-06-30。 - Nuxt Server:Nitro API 与 middleware;核验于 2026-06-30。
- Nuxt Rendering Modes:SSR、CSR、预渲染和混合规则;核验于 2026-06-30。
- Nuxt 4 Release:目录、数据获取和类型变化;核验于 2026-06-30。