后端与接口
Vercel 上缓存命中率低、内容过期、BYPASS…
适用于在 Vercel 上排查 CDN、ISR、PPR 缓存行为、重验证触发源和 ISR 计费成本。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 38/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 60/100 ×0.15
适用场景
Vercel 上缓存命中率低、内容过期、特定
解决思路
- 按缓存层定位:请求先到最近 PoP,再到 Vercel 区域;CDN 按 CDN cache、ISR cache、Function invocation 顺序检查,返回第一个有效缓存;函数仅在所有上游都无有效副本时执行。排查时先判断请求停在哪一层。
- 读 `x-vercel-cache` 响应头判断结果:`HIT`=缓存返回、无函数运行;`MISS`=未缓存、源/函数运行;`STALE`=SWR 后台重验证;`PRERENDER`=预渲染 ISR/PPR shell;`REVALIDATED`=删除后前台重验证;`BYPASS`=缓存跳过(`no-store`、`private`、cookies 等)。
- 读 `cacheReason` 细分同类结果:`cold`=MISS 缓存空;`collapsed`=MISS 并发折叠;`error`=MISS 错误阻止;`draft_mode`/`prerender_bypass`/`crawler`=原始 MISS 显示为 BYPASS;`stale_time`=时间重验证;`stale_tag`=tag 失效;`stale_error`=重验证失败。从 `vercel logs` 或 dashboard Logs 的 Reason 行读;`x-vercel-cache-reason` 仅内部,不能靠 `curl`。
- 读 `ppr_state` 判断 PPR 预渲染比例:`Static`=全预渲染、不调函数体;`Partial`=静态 shell 从缓存 + 函数按请求恢复 hole;`Dynamic`=全体延迟函数渲染。从 `vercel logs`、dashboard Logs 或 `vercel metrics vercel.request.count --group-by ppr_state` 读;`x-vercel-ppr-state` 仅内部;Partial 调用函数不是缓存未命中。
- 查总体命中率:运行 `vercel metrics vercel.request.count -S <team> -p <project> -f "environment eq 'production'" --group-by cache_result --since 24h`;把 `HIT`、`STALE`、`PRERENDER` 视为缓存服务,重点看 `MISS`;计算可缓存流量命中率时排除 `BYPASS` 和 `(not set)`。
- 定位 MISS 集中:先运行 `vercel metrics vercel.request.count -S <team> -p <project> -f "environment eq 'production' and cache_result eq 'MISS'" --group-by path_type --since 24h`,再按 `route` 或 `request_path` 细分;`prerender` 路径 MIS 多则列入短名单做头检查和代码审查。
- 判断 `streaming_func`:默认动态渲染,但仍可用 `Cache-Control` 头缓存;缓存条目按 `Vary`(cookies、RSC 等)以及路径和查询参数变化,预期更多缓存 key 和更低命中率。
- 分析 ISR 成本:运行 `vercel metrics vercel.request.count -S <team> -p <project> -a sum --since 24h` 和 `vercel metrics vercel.isr_operation.write_units -S <team> -p <project> -a sum --since 24h`;utilization 是请求量,ISR 按 8 KB 单位计费,read_units 远低于请求数,不要拿 read_units 与 write_units 比较,关注 write_units。
- 计算 write utilization:分子运行 `vercel metrics vercel.request.count -S <team> -p <project> -f "environment eq 'production' and (cache_result eq 'HIT' or cache_result eq 'STALE')" --group-by route -a sum --since 24h`,分母运行 `vercel metrics vercel.isr_operation.write_units -S <team> -p <project> -f "environment eq 'production'" --group-by route -a sum --since 24h`;高好,接近或低于 ~1 表示重生成与读取一样快,应加长 revalidate 或改按需 tag。
- 找重验证最多路由:按 `route` 和 `request_path` 拆 `vercel.isr_operation.write_units`,运行 `vercel metrics vercel.isr_operation.write_units -S <team> -p <project> -a sum --group-by route --since 24h` 和 `vercel metrics vercel.isr_operation.write_units -S <team> -p <project> -a sum --group-by request_path --since 24h`。
- 确认成本驱动:将 write_units 按 `path_type` 分组;集中在 `background_func` 确认是重验证成本而非每请求动态工作。
- 检查 tag 爆炸半径:将 write_units 按 `cache_tags` 分组运行 `vercel metrics vercel.isr_operation.write_units -S <team> -p <project> -a sum --group-by cache_tags --since 24h`;若许多不相关路由写入数接近相同,说明共享热 tag 在锁步失效。
- 找触发重验证的 tag:按 `triggering_tag` 分组 `vercel.request.count`(仅 request count 有,非 ISR operation)运行 `vercel metrics vercel.request.count -S <team> -p <project> -f "triggering_tag ne null" --group-by triggering_tag --since 24h`;频繁且大爆炸半径是 write_units 根因。
- 代码确认触发点:grep `revalidateTag(`、`invalidateByTag(`、`updateTag(`、`dangerouslyDeleteByTag(`,检查 CMS webhook 或同步 cron 是否用广 tag 在每次事件失效,而不是具体 `${type}:${id}`。
- 调试 BYPASS:按 `bot_category`、`user_agent`、`request_method` 分组 `cache_result eq 'BYPASS'`;Draft Mode 和 SEO 爬虫是最大合法来源,SEO bot 必须收到完整响应(尤其 PPR),预期不是配置错误。
- 用 Firewall/WAF(`vercel-firewall` 技能)管理已验证 SEO 爬虫、封锁滥用 bot、限流垃圾流量,避免它们扭曲命中率。
- 减少 ISR 成本:优先 tag 按需重验证而非短时间间隔;用 `revalidateTag` / `invalidateByTag` 在内容变化时触发。
- 收窄 tag:失效 `blogPost:<id>` 这类具体 ID,而不是 `blogPost`/`page` 通用 tag。
- 在框架声明处调 revalidate 间隔:Next.js `revalidate` / `cacheLife`,SvelteKit `isr`,Nuxt `routeRules`,Astro;Next.js Cache Components 参考 `next-cache-components`。
- 用 `CDN-Cache-Control` 头缓存动态函数。
- 单路径头检查:`curl -sSI https://<host>/<path> | grep -iE 'x-vercel-cache|x-matched-path|cache-control|vary|age|set-cookie'`;看 `x-vercel-cache`、缓存指令、`x-matched-path`(暴露 `/precomputed/exp~.../...` 实验/flag 预计算)、`vary`(个性化)、`set-cookie`(强制 BYPASS)。
- 若路径应缓存却显示 `MISS`/`BYPASS`:检查是否有 `private`、`no-store`、`max-age=0`、每请求输入(cookies/headers/`searchParams`)或不可缓存方法。
- 逐阶段耗时:`vercel httpstat /some/path`(CLI v48.9.0+,需安装 `httpstat` 工具)。
- 单请求日志:`vercel logs --request-id <request-id> --json`,用 `--json` 让 agent 解析缓存状态、路径和时间字段。
- 指标查询参数:用 `-S <team> -p <project>`;生产用 `-f "environment eq 'production'"`(无 `--prod`);用 `vercel metrics schema <metric>` 发现维度;`-F json` 机器可读;`-g` 时 `--limit` 按时间桶,省略 `-g` 得总。
- 处理 prerender 变体未命中:动态参数每个变体单独预渲染缓存,每区域首次命中会 miss,低流量变体难保持热;功能标志/实验预计算(middleware 选变体 `/precomputed/exp~.../...`)使 flags×routes×PPR segments 产生数千 ISR 条目;修复:收敛变体矩阵(退役已结束实验)或接受成本。
- PPR 不避免函数调用:PPR 路由有动态 hole,缓存 shell 命中仍调函数填充;无 hole 路由只是 ISR,返回纯 `PRERENDER` HIT。
- 解释函数调用多于 PPR 请求:PPR 请求有静态 shell + 动态函数;shell 需重生成时额外函数调用。
- 清除内容时区分 `invalidate` 与 `dangerously-delete`:`invalidateByTag`/`revalidateTag`/`revalidatePath` 是 SWR,响应 `STALE`;`dangerouslyDeleteByTag`/`updateTag`/无 lifetime revalidate 是硬删除,下一请求前台阻塞重生成,响应 `REVALIDATED`。
- 图像缓存:优化图片第一次转换后缓存在 CDN。
- 清除传播:purges 约 300 ms 全局传播。
- 请求折叠:大量请求同时打同一未缓存路径时,每区域折叠为一次函数调用以保护源。
- CDN cache 读写免费;ISR cache 读写按 8 KB 单位计费;ISR 在单一 Function region 持久,部署后存活 31 天或直到重验证。
- 排除非缓存流量:`BYPASS` 和 `(not set)`(重定向、错误、不可缓存方法)不应计入命中率分母,否则非缓存原因拉低比例。
- 用 `vercel metrics vercel.request.count --group-by ppr_state` 聚合 PPR 状态。
适用边界 · 注意事项
- 不适用于非 Vercel 部署的 CDN/缓存调试;`x-vercel-cache`、`cacheReason`、`ppr_state`、`vercel metrics` 等都是 Vercel 特定。
- 没有 Observability Plus 时无法使用 `vercel metrics` 聚合指标,只能退化为 `vercel logs`、`curl` 单请求诊断。
- 不适用于函数与后端之间的数据缓存;那属于 Runtime Cache,应使用 `runtime-cache` 技能。