Nuxt SWR 缓存可能泄露访客数据:三个可复现的陷阱与修复
译注:本文翻译自 dev.to 作者 Muhammad Usama 的技术文章,原文发布于 2026 年 10 月 3 日。作者在为一个大型电商站点配置缓存时发现了多个数据泄露问题,并在 Nuxt 4.5.2 / Nitro 2.13.4 环境中逐一复现验证。原文链接见文末。
在 Nuxt 中开启缓存只需要一行代码:
routeRules: { '/products/**': { swr: 60 } }TTFB 从秒级降到毫秒级。但一个缓存响应是为某一次请求生成的,随后会被交给所有映射到同一缓存键的请求。在个性化站点上,这就是一个访客的数据出现在另一个访客浏览器中的原因。
作者在为一个大型电商前台配置缓存时踩到了这些坑,随后在一个最小化的 Nuxt 4.5.2 / Nitro 2.13.4 应用中逐一重建,以确认问题真实存在。以下是三个今天就能测试的陷阱。
陷阱一:渲染期间设置的 Cookie 被回放给所有人
一个在 SSR 期间分配 A/B 分桶的页面:
<script setup lang="ts">
const bucket = useCookie('bucket')
if (!bucket.value) bucket.value = 'b-' + Math.random().toString(36).slice(2, 8)
</script>在该路由上配置 swr: 60 后,三个全新访客得到的结果是:
visitor 1 → set-cookie: bucket=b-ua93t6
visitor 2 → set-cookie: bucket=b-ua93t6
visitor 3 → set-cookie: bucket=b-ua93t6Set-Cookie 响应头被随缓存响应一起存储了。整个实验会坍缩到同一个分桶(如果这是类似会话的 Cookie,后果更严重)。
指向修复方案的对比: 同样类型的 Cookie 如果设置在服务端中间件中,则不会被缓存——每个访客都能拿到自己的值,因为中间件在每次请求时、缓存之前运行。因此:把按访客设置的 Cookie 放在 server/middleware 中,而不是页面、组件或 Nuxt 插件里,并且只在值真正变化时才设置。
陷阱二:多域名站点共享同一个缓存条目
一个应用对应多个主机(多店铺、多租户、de. / fr. 域名)。页面打印 host:
Host: store-a.test → host=localhost
Host: store-b.test → host=localhost ← 谁先渲染就归谁默认缓存键是基于路径的。修复方案(已在同一实验环境中验证):
routeRules: {
'/': { swr: 60, cache: { varies: ['host', 'x-forwarded-host'] } },
}现在每个主机拥有独立条目,重复请求仍然走缓存。
陷阱三:按访客的内容被冻结进 HTML
页面在 SSR 期间读取 currency Cookie:
Cookie: currency=EUR → currency=USD
Cookie: currency=PKR → currency=USD ← 来自第一次无 Cookie 渲染的缓存修复方案按优先级排列:将个性化 UI 排除在 SSR 之外(<ClientOnly> / 客户端获取);渲染中性值并在客户端校验;仅对一个小型有界集合做键变化(绝不要按访客 Cookie 变化);或者干脆不缓存该路由。另外,如果价格还依赖实验或用户分群,仅检查货币是不够的。
用 60 秒测试你自己的站点
以两个新访客身份请求同一缓存路由两次,逐个比较 Cookie,而不是整块比较——一个新鲜的中间件 Cookie 可能掩盖旁边被回放的 Cookie:
curl -s -D - -o /dev/null https://staging.example.com/route | grep -i set-cookie
curl -s -D - -o /dev/null https://staging.example.com/route | grep -i set-cookie
# 两次请求中值相同的 Cookie,就是来自缓存。作者表示共整理了七个陷阱(包括重新验证期间的 ERR_HTTP_HEADERS_SENT、客户端导航间的状态泄露、按进程的缓存存储,以及错误测量 CLS 的方式),并附有实验应用、泄露测试脚本和一个用于扫描 Nuxt 仓库的 Claude Code /cache-audit 命令。