首页
/ Nuxt $fetch 全解析:全局 HTTP 请求助手与 SSR 下的数据安全实践

Nuxt $fetch 全解析:全局 HTTP 请求助手与 SSR 下的数据安全实践

2026-09-07 20:07:49作者:董灵辛Dennis

$fetch 是 Nuxt 基于 ofetch 暴露的全局 HTTP 请求助手,可在 Vue 应用、组合式函数与 API 路由中直接使用。它解决了全栈框架在浏览器与服务器两端发送请求的统一性问题,同时需要开发者理解 SSR(服务端渲染)阶段"数据是否会被请求两次""请求头与 Cookie 是否被转发"等关键差异。读完本文,你将掌握 $fetch 的底层实现、与 useAsyncData/useFetch 的正确搭配方式,以及如何安全地在服务端透传用户的 Headers 与 Cookie。

$fetch 是什么:Nuxt 如何把它变成全局能力

在 Nuxt 应用中,$fetch 并非一个手写封装,而是构建阶段由模板注入的全局对象。其核心代码位于 packages/nuxt/src/core/templates.ts,Nuxt 会生成若干运行时模块把 ofetch 挂到应用上:

  • 服务端模板 fetch.server.mjs(见 templates.ts#L609-L627):若 globalThis.$fetch 尚未存在,则用 createFetch({ fetch, defaults: { baseURL: baseURL() } }) 创建,其中 fetch 取自当前 server builder 提供的运行时实现(见 useServerBuild(nuxt).runtime.fetch),baseURL() 来自 #internal/nuxt/paths,会把应用配置的 baseURL 一并带入默认值;
  • 客户端模板 fetch.client.mjs(见 templates.ts#L629-L639):直接 _$fetch.create({ baseURL: baseURL() }) 导出 $fetch,并标注 /*#__PURE__*/ 便于摇树;
  • 类型模板 fetch.d.ts(见 templates.ts#L657-L663):声明 export declare const $fetch: import('nuxt/app').TypedFetch,让 $fetch 具备类型化请求能力;
  • 为了确保模块求值顺序正确,服务端通过 fetch-setup.server.mjstemplates.ts#L645-L649)以副作用导入的方式在应用入口最先加载 #build/fetch,先于其他模块"播种"好 globalThis.$fetch;客户端因 $fetch 是自动导入项,该模块保持为空,避免把 ofetch 拉进从不使用它的打包产物(参见 templates.ts#L641-L644 的注释说明)。

从类型层面看,TypedFetchRequest 由服务端路由推断而来(见 packages/nuxt/src/app/types/fetch.ts#L13),配合路由类型生成后,$fetch('/api/xxx') 的 URL 与返回类型都能得到 IDE 校验。

SSR 中的关键优化:请求内部 API 路由时直接调用处理函数

::tip 在服务端渲染阶段,用 $fetch 请求应用内部API 路由 时,Nuxt 会直接调用对应路由函数(模拟一次请求),从而省掉一次额外的网络 API 调用。 ::

这一机制在仓库中表现为:Nitro 服务端把相对路径的内部请求通过 serverFetch 重新派发到对应 handler,而不是真的发起一次 HTTP 往返。例如 packages/nitro-server/src/runtime/middleware/base-url.tspackages/nitro-server/src/runtime/handlers/error.ts 都直接 import { serverFetch } from 'nitro' 做内部请求,且 packages/nitro-server/src/index.ts#L117-L127 注释也说明"内部重新派发的请求(internally re-dispatched request)"需要路由保持与 strip 后路径一致(启用 experimental.runtimeBaseURL 时会注册 base-url 中间件做该处理)。因此在 SSR 中请求自己的 /api/** 接口,不会产生对外网络开销。

服务端渲染会重复请求数据吗:与 useAsyncData/useFetch 的关系

::note 在组件中直接使用 $fetch 而不用 useAsyncData 包裹,会造成数据被请求两次:先是在服务端,然后在客户端 hydration 时再次请求。原因在于 $fetch 不会把状态从服务端传递到客户端,客户端必须自己再取一次数据。 ::

官方推荐用 useFetchuseAsyncData + $fetch 来避免组件数据的双重获取,例如:

<script setup lang="ts">
// 不推荐:SSR 期间数据会在服务端、客户端各请求一次
const dataTwice = await $fetch('/api/item')

// 推荐:SSR 期间只在服务端获取一次,再通过 payload 传给客户端
const { data } = await useAsyncData('item', () => $fetch('/api/item'))

// 同样推荐:useFetch 是 useAsyncData + $fetch 的快捷方式
const { data } = await useFetch('/api/item')
</script>

useFetch 之所以能避免二次请求,是因为它内部把数据装载进了 useAsyncData 的 payload 序列化机制。可以看 packages/nuxt/src/app/composables/fetch.ts#L370-L381:真正的数据获取发生在 useAsyncData(key, ...) 的回调内,即"SSR 时只跑在服务端、结果随 HTML payload 传给客户端"。此外该文件还能看到几个值得注意的实现细节:

  • 服务端实际执行请求的是 useRequestFetch(),客户端才是裸 $fetchfetch.ts#L371),这正是"SSR 阶段自动透传请求头"的实现入口(下文详解);
  • 支持大写 HTTP 方法(见 fetch.ts#L19-L20 的注释与类型定义),method 也允许是 ref;
  • 缓存键由 '$f' + hashKey([...]) 基于 URL、methodbaseURLquery/paramsbody 等分段生成(fetch.ts#L62-L104fetch.ts#L323),其中 body 会对响应式对象做统一序列化处理;
  • 若请求的是 // 开头的协议相对 URL 且未提供 baseURL,会抛出 NUXT_E3001 诊断错误(fetch.ts#L325-L327),防止误把内部请求打到外部地址。

更多数据获取的最佳实践见 数据获取指南

纯客户端场景:直接在事件回调中使用 $fetch

$fetch 可以放心地用在任何仅在客户端执行的方法里,例如表单提交:

<script setup lang="ts">
async function contactForm () {
  await $fetch('/api/contact', {
    method: 'POST',
    body: { hello: 'world' },
  })
}
</script>

<template>
  <button @click="contactForm">
    Contact
  </button>
</template>

这里没有 SSR 的重复请求与透传问题——点击事件发生在浏览器端,请求由浏览器直接发出,cookie 等用户请求头会由浏览器自动附带。

$fetch 与 Nuxt 2 时代的 HTTP 方案

$fetch 是 Nuxt 发起 HTTP 调用的首选方式,取代了面向 Nuxt 2 的 [@nuxt/http] 与 [@nuxtjs/axios]。它基于 Promise、零配置支持 JSON 序列化/解析、统一错误处理,并天然与 Nuxt 的类型化路由与 SSR 内部请求机制配合,迁移到 Nuxt 3/4 的项目应逐步用 $fetch 替换上述模块。

开发期调用自签名 HTTPS 外部接口

::note 如果在开发环境用 $fetch 请求带自签名证书的(外部)HTTPS URL,需要在环境变量中设置 NODE_TLS_REJECT_UNAUTHORIZED=0。 ::

注意这仅用于本地联调(例如调用尚在内网、使用自签证书的测试服务),它关闭了 Node.js 对 TLS 证书合法性的校验,生产环境不应开启。因为该证书校验发生在 Node 服务端进程内,浏览器端的 fetch 行为不受此环境变量影响。

在 SSR 中透传 Headers 与 Cookie:安全模型与显式转发

$fetch浏览器中执行时,用户请求头(如 cookie)会由浏览器直接随请求发给 API。

但在服务端渲染期间,出于 SSRF(服务端请求伪造)身份认证滥用 等安全风险考虑,$fetch 既不会带上用户浏览器的 Cookie,也不会转发来自 fetch 响应的 Set-Cookie:

<script setup lang="ts">
// 在 SSR 期间,这不会把用户请求头与 Cookie 转发给 /api/cookies
const { data } = await useAsyncData(() => $fetch('/api/cookies'))
</script>
export default defineEventHandler((event) => {
  const foo = getCookie(event, 'foo')
  // ... Do something with the cookie
})

服务端如果需要把请求头和 Cookie 继续透传给内部 API,必须手动转发,标准做法是使用 useRequestFetch

<script setup lang="ts">
// 这会把用户的请求头与 Cookie 转发给 /api/cookies
const requestFetch = useRequestFetch()
const { data } = await useAsyncData(() => requestFetch('/api/cookies'))
</script>

此外,当在服务端用相对 URL 调用 useFetch 时,Nuxt 会自动改用 useRequestFetch 代理请求头与 Cookie(排除掉 host 等不应转发的头)。

useRequestFetch 的底层实现:白名单式的安全转发

为什么"手动转发"能保证安全?关键在于它是按"明确白名单"进行代理的。看 packages/nuxt/src/app/composables/ssr.ts 的实现:

  • 客户端直接返回全局 $fetchssr.ts#L100);
  • 服务端则通过 createRequestFetch(event) 创建一个 per-request 的包装 fetch(结果缓存在 WeakMap<RequestEvent, TypedFetch> 中,见 ssr.ts#L76-L96)。只有当请求字符串以 / 开头(相对内部 URL)时,才会把 event.req 的请求头合并进来,并且跳过 UNFORWARDED_HEADERS 中列出的头(ssr.ts#L52-L74)。

被排除的 UNFORWARDED_HEADERS 就是那些逐跳(hop-by-hop)或描述请求体的头,包括 acceptaccept-encodingconnectioncontent-lengthcontent-md5content-typeexpecthost、条件请求头(if-matchif-modified-sinceif-none-match 等)、keep-aliveproxy-authenticateproxy-authorizationtetrailertransfer-encodingupgrade。将代理逻辑以底层 fetch 的形式注入而非使用 onRequest 钩子或默认 headers(见 ssr.ts#L80-L82 的注释),是为了让用户提供的钩子与绝对 URL 都无法绕过这套检查。

各场景请求头转发行为速查

场景 请求位置 用户 Cookie / Headers 是否转发
$fetch('/api/...') 浏览器 ✅ 由浏览器自动附带
$fetch('/api/...')useAsyncData(() => $fetch(...)) 服务端 ❌ 出于 SSRF / 认证安全默认不透传
useRequestFetch()('/api/...') 包裹在 useAsyncData 服务端 ✅ 显式转发(host 等除外)
useFetch('/api/...') 服务端 ✅ 内部自动走 useRequestFetch 代理
仅在客户端执行的 $fetch(如点击事件内) 浏览器 ✅ 正常附带

总结

$fetch 是 Nuxt 数据请求的统一入口:在浏览器端它就是带 baseURL 默认值的 ofetch 实例;在服务端则被构建模板注入为 globalThis.$fetch,并能对内部 API 路由做免网络的直接调用。使用时有两条铁律需要牢记——在组件中请求数据时用 useFetchuseAsyncData + $fetch,避免 SSR 二次请求在服务端需要透传用户身份信息(Cookie/Headers)时使用 useRequestFetch,而不是期望裸 $fetch 替你转发。理解这两点,就能在全栈开发中写出既高效又安全的接口调用代码。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
915
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388