乐极生悲的 Next.js+OpenNext 生产踩坑排错实录:被10+M R2读操作和10M+ Worker 请求轮番肘击的一天

乐极生悲的 Next.js+OpenNext 生产踩坑排错实录:被10+M R2读操作和10M+ Worker 请求轮番肘击的一天

💡 原文中文,约8300字,阅读约需20分钟。
📝

内容提要

作者在Cloudflare Workers上部署Next.js+OpenNext网站时,遭遇两大计费事故:R2读操作达787万次,因RegionalCache后台偷读及CDN TTL过短放大;Worker请求超1000万次,因Next.js 16缓存拦截Bug导致RSC预取死循环。通过关闭后台更新、拉长缓存、禁用拦截及优化prefetch解决,并总结出缓存配置、预取分级等最佳实践。

🔎

延伸解读

缓存命中为何还会产生巨额读操作?

文章揭示了一个关键陷阱:在 OpenNext 的 RegionalCache 中,即使缓存命中,后台仍可能通过 waitUntil 发起 R2 读操作来刷新缓存。这种“后台偷读”在 CDN TTL 过短和全球多节点独立缓存的双重放大下,将少量用户请求放大成数百万次计费操作。排查时,A/B 操作比值是重要线索:若命中远多于写入,问题往往出在缓存刷新逻辑而非缓存穿透。

RSC 预取死循环:一个隐蔽的计费炸弹

Next.js 16 与 OpenNext 的缓存拦截功能存在兼容性 Bug,导致部分 RSC 预取请求返回不匹配的数据,使前端误以为预取未完成而无限重试。这解释了为何访问量不大的页面会产生千万级 Worker 请求。关闭 enableCacheInterception 可立即止血,但根本教训是:升级框架或依赖时,需关注上游 Issue,并验证关键路径行为,避免激进新特性引发生产事故。

预取分级:边缘场景下的成本控制实践

文章提出按用户意图划分链接预取策略:高意图链接(如主导航、CTA)保留默认预取,低意图链接(如页脚、分页、卡片)显式设置 prefetch={false}。这一做法可削减大量静默 RSC 请求,尤其适合按请求计费的 Serverless 环境。作者通过全站 35 个组件调整,将首屏预取请求减少 70% 以上,展示了细粒度控制对成本与性能的显著影响。

Q&A

在 Cloudflare Workers 上使用 Next.js + OpenNext 时,R2 读操作突然暴增的可能原因是什么?

R2 读操作暴增的主要原因是 OpenNext 的 RegionalCache 在缓存命中时默认启用 shouldLazilyUpdateOnCacheHit,导致每次命中都会在后台发起一次 R2 读操作。此外,CDN TTL 设置过短(如首页 600 秒)和全球多个边缘节点各自回源,进一步放大了读操作次数。

如何解决 Next.js + OpenNext 在 Cloudflare Workers 上 R2 读操作过多的问题?

解决方法包括:在 open-next.config.ts 中显式设置 shouldLazilyUpdateOnCacheHit: false 以关闭后台偷读;将 R2 缓存桶与媒体桶分离,并配置 Lifecycle 规则自动清理过期对象;精简 Next.js 图片变体;将 CDN s-maxage 和路由 revalidate 拉长到 86400 秒。

Next.js 16 中 _rsc 预取请求无限循环的原因是什么?如何修复?

原因是 OpenNext 的 enableCacheInterception 在 Next.js 16 下存在 Bug,当客户端发起部分段预取时,缓存拦截器错误返回整页 RSC Payload,导致前端无法完成预取,从而反复请求。修复方法是关闭 enableCacheInterception: false。

在 Next.js 中如何通过 prefetch 属性减少不必要的预取请求?

对低意图链接(如 Footer 链接、分页页码、卡片列表、面包屑等)显式设置 prefetch={false},只对高意图链接(如主导航、品牌 Logo、核心 CTA)保留默认预取。这样可以减少首屏静默预取请求数,降低 Worker 调用量。

使用 OpenNext 的 RegionalCache 时,shouldLazilyUpdateOnCacheHit 参数的作用是什么?

该参数控制缓存命中时是否在后台异步更新区域缓存。默认值为 true,意味着每次命中都会触发一次后台 R2 读操作来刷新缓存,这会导致 R2 读操作激增。设置为 false 可以避免后台偷读,让缓存自然过期后由 ISR 或 revalidateTag 更新。

在 Cloudflare Workers 上部署 Next.js 时,有哪些最佳实践可以避免类似计费事故?

最佳实践包括:设置合适的缓存时间(如 s-maxage 86400);手动控制 <Link> 的 prefetch 范围,对次要链接关闭预取;合理设置图片优化,减少不必要的分辨率;配置 R2 Lifecycle 规则定期清理 ISR 缓存;关注 OpenNext 的 Issue 和更新,及时修复已知 Bug。

🏷️

标签

➡️

继续阅读