cdn-caching

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Vercel Caching

Vercel 缓存机制

You are an expert in understanding Vercel's caching infrastructure, and how the CDN Cache, ISR, and PPR work.
你是Vercel缓存基础设施以及CDN缓存、ISR和PPR工作原理方面的专家。

Core Knowledge

核心知识

  • ISR (and PPR, a rendering strategy built on it) is a framework feature — Next.js, SvelteKit, Nuxt, and Astro all use it on Vercel, and the layers, metrics, and CLI here apply regardless. (For caching data between your function and a backend, that's the Runtime Cache — a separate layer; see References.)
  • PPR (Partial Prerendering) — a rendering strategy, not a cache layer: the static shell lives in the ISR cache while a function renders the dynamic holes per request and streams them into the same response. A route with holes still invokes the function on a shell hit; a holeless route is just ISR (a pure
    prerender
    HIT).
  • ISR(以及基于它构建的渲染策略PPR)是一种框架特性——Next.js、SvelteKit、Nuxt和Astro在Vercel上均支持该特性,本文提及的缓存层级、指标和CLI工具均通用。(若要在函数与后端之间缓存数据,需使用Runtime Cache——这是一个独立的缓存层级,详见参考文献。)
  • PPR(Partial Prerendering,部分预渲染)——一种渲染策略,而非缓存层级:静态壳存储在ISR缓存中,同时函数会针对每个请求渲染动态内容片段并将其流式传输到同一响应中。包含动态片段的路由在命中静态壳时仍会触发函数执行;无动态片段的路由则完全属于ISR(纯
    prerender
    命中)。

How caching works

缓存工作流程

Vercel caches at multiple layers between the visitor and your backend. A request reaches the nearest PoP, which routes to a Vercel region; the CDN then checks each layer in order and returns a cached response as soon as one is available, so your function runs only when nothing upstream has a valid copy.
Vercel在访问者与后端之间设置了多层缓存。请求会先到达最近的PoP(边缘节点),再路由到Vercel区域;CDN会按顺序检查每个层级,一旦找到有效缓存响应就立即返回,因此只有当上游所有层级都没有有效副本时,才会触发函数执行。

Cache layers

缓存层级

  • CDN cache — regional, ephemeral. On a hit the region returns the response with no function call. Reads/writes are free.
  • ISR cache — durable, in a single Function region. On a CDN miss, Vercel reads here before invoking your function (cache shielding), then replicates the result back to the CDN. Survives deploys for 31 days or until revalidated; reads/writes are billed in 8 KB units.
  • Function invocation — runs only if neither cache has a valid copy. It may read the Runtime/data cache (a separate layer; see References) and your backend, then Vercel stores the response in the ISR cache.
  • Image cache — optimized images, cached on the CDN after the first transform.
  • Purges propagate globally in ~300 ms.
Request collapsing: when many requests hit the same uncached path at once, Vercel collapses them into one function invocation per region to protect the origin.
  • CDN缓存——区域级、临时缓存。命中时,区域节点直接返回响应,无需调用函数。读写操作免费
  • ISR缓存——持久化缓存,存储在单个函数区域。当CDN缓存未命中时,Vercel会先读取ISR缓存(缓存屏蔽),再将结果同步回CDN。缓存内容会保留31天或直到被重新验证,读写操作按8 KB单位计费
  • 函数执行——仅当CDN和ISR缓存均无有效副本时才会触发。函数可能会读取Runtime/数据缓存(独立层级,详见参考文献)和后端数据,之后Vercel会将响应存储到ISR缓存中。
  • 图片缓存——经过优化的图片,首次转换后会被CDN缓存。
  • 缓存清除操作会在约300毫秒内全局生效。
请求合并:当多个请求同时访问同一未缓存路径时,Vercel会将其合并为每个区域一次函数执行,以保护源站。

Key concepts

关键概念

  • Cache hit rate — share served from cache (
    HIT
    /
    STALE
    /
    PRERENDER
    ) versus origin (
    MISS
    /
    REVALIDATED
    ). Measure it over cacheable requests — exclude
    BYPASS
    and
    (not set)
    (redirects, errors, uncacheable methods), or they drag the ratio down for non-cache reasons. Low hit rate means more origin load and higher latency.
  • Revalidation — refreshing cached content. Time-based runs automatically after an interval; on-demand runs when you call an API. Both use stale-while-revalidate: visitors keep getting the cached version while the new one regenerates in the background.
  • Invalidate vs. dangerously-delete — two ways to clear content, with very different blast on hit rate:
    • Invalidate (
      invalidateByTag
      , Next.js
      revalidateTag
      /
      revalidatePath
      ) = stale-while-revalidate. Keeps serving stale while refreshing in the background → response shows
      x-vercel-cache: STALE
      .
    • Dangerously-delete (
      dangerouslyDeleteByTag
      , Next.js
      updateTag
      or a revalidate with no lifetime) = hard removal. The next request blocks in the foreground to regenerate →
      x-vercel-cache: REVALIDATED
      .
  • Cache tags & blast radius — tags group cached entries so one call can clear many. A coarse tag attached to thousands of paths has a large blast radius: a single write drops them all and the hit rate collapses until they re-warm. Prefer granular tags (
    product-${id}
    ) plus a roll-up tag.
  • Cache status (
    x-vercel-cache
    response header) — the outcome:
    ValueMeaning
    HIT
    Served from cache; no function ran
    MISS
    Not cached; origin/function ran
    STALE
    Served stale while revalidating in background (SWR / invalidate)
    PRERENDER
    Served a prerendered ISR/PPR shell
    REVALIDATED
    Foreground revalidation after a delete (or
    Pragma: no-cache
    )
    BYPASS
    Caching skipped (
    no-store
    ,
    private
    , cookies, etc.)
  • Cache reason (
    cacheReason
    ) — the finer explanation of that outcome for a single request. The
    cache_result
    metric lumps all
    MISS
    es (and all
    STALE
    s) together; the reason is the only thing that tells them apart. Nine values, three per group:
    cacheReason
    RefinesMeaning
    cold
    MISSCache empty for this key/variant (first request or evicted); the function ran
    collapsed
    MISSConcurrent requests to one uncached path collapsed into a single invocation
    error
    MISSAn error prevented serving from cache
    draft_mode
    → BYPASSNext.js Draft Mode active — bypassed so editors see live content
    prerender_bypass
    → BYPASSPrerender-bypass cookie/token present
    crawler
    → BYPASSSEO-crawler UA — full response served so bots index real content
    stale_time
    STALETime-based
    revalidate
    interval elapsed; regenerating in background (SWR)
    stale_tag
    STALETag invalidated (
    revalidateTag
    /
    invalidateByTag
    ); regenerating
    stale_error
    STALEA revalidation attempt failed; serving the last-good copy (a bug signal)
    A raw
    MISS
    with reason
    draft_mode
    /
    prerender_bypass
    /
    crawler
    is displayed as
    BYPASS
    (all usually expected). The three
    stale_*
    reasons separate a healthy time refresh (
    stale_time
    ) from a broad-tag blast (
    stale_tag
    ) from a failing regen (
    stale_error
    ). Read
    cacheReason
    from
    vercel logs
    or the dashboard Logs "Reason" row — the
    x-vercel-cache-reason
    header is internal-only and not visible via
    curl
    .
  • PPR state (
    ppr_state
    ) — for a Partial Prerendering route, how much of the response was prerendered versus computed per request. Only set on
    partial_prerender
    serves; blank for plain
    prerender
    /
    func
    /
    static
    routes and for cases the proxy can't classify (cold shell miss,
    BYPASS
    ). Three states:
    ppr_state
    Meaning
    StaticFully prerendered — no postponed state, so the function is not invoked for the body
    PartialA static shell serves from cache + a postponed hole the function resumes per request
    DynamicThe whole body is postponed and rendered by the function per request
    A Partial serve that still invokes the function is not a cache miss — the cached shell serves immediately while the function fills only the dynamic holes. Read
    ppr_state
    from
    vercel logs
    or the dashboard Logs panel, or aggregate with
    vercel metrics vercel.request.count --group-by ppr_state
    . Like
    cacheReason
    , the
    x-vercel-ppr-state
    header is internal-only and not visible via
    curl
    .
  • 缓存命中率——从缓存(
    HIT
    /
    STALE
    /
    PRERENDER
    )提供服务的请求占比,对比从源站(
    MISS
    /
    REVALIDATED
    )提供服务的请求占比。需针对可缓存请求统计——排除
    BYPASS
    (not set)
    (重定向、错误、不可缓存方法),否则这些非缓存原因会拉低命中率。低命中率意味着源站负载更高、延迟更长。
  • 重新验证——刷新缓存内容。基于时间的重新验证会在间隔时间后自动触发;按需重新验证会在调用API时触发。两者均采用stale-while-revalidate策略:访问者会继续获取缓存版本,同时后台生成新内容。
  • 失效与危险删除——两种清除缓存的方式,对命中率的影响差异极大:
    • 失效
      invalidateByTag
      、Next.js的
      revalidateTag
      /
      revalidatePath
      )= stale-while-revalidate。在后台刷新的同时继续提供过期内容 → 响应头显示
      x-vercel-cache: STALE
    • 危险删除
      dangerouslyDeleteByTag
      、Next.js的
      updateTag
      或无生命周期的重新验证)= 强制移除缓存。下一次请求会在前台阻塞以重新生成内容 →
      x-vercel-cache: REVALIDATED
  • 缓存标签与影响范围——标签用于分组缓存条目,以便一次调用即可清除多个条目。附加到数千个路径的粗粒度标签会产生较大的_影响范围_:单次写入操作会清除所有关联条目,命中率会在缓存重新预热前大幅下降。建议优先使用细粒度标签(如
    product-${id}
    ),再搭配汇总标签。
  • 缓存状态
    x-vercel-cache
    响应头)——缓存结果:
    取值含义
    HIT
    从缓存返回响应;未触发函数执行
    MISS
    无缓存内容;触发源站/函数执行
    STALE
    返回过期内容的同时在后台重新验证(SWR / 失效操作)
    PRERENDER
    返回预渲染的ISR/PPR静态壳
    REVALIDATED
    删除缓存后进行前台重新验证(或使用
    Pragma: no-cache
    BYPASS
    跳过缓存(使用
    no-store
    private
    、Cookie等)
  • 缓存原因
    cacheReason
    )——单个请求缓存结果的详细解释。
    cache_result
    指标会将所有
    MISS
    (以及所有
    STALE
    )归为一类,而缓存原因是区分它们的唯一依据。共有9种取值,分为三组:
    cacheReason
    对应结果含义
    cold
    MISS该键/变体的缓存为空(首次请求或已被驱逐);触发了函数执行
    collapsed
    MISS对同一未缓存路径的并发请求被合并为一次函数执行
    error
    MISS错误导致无法从缓存提供服务
    draft_mode
    → BYPASSNext.js草稿模式已激活——跳过缓存以便编辑者查看实时内容
    prerender_bypass
    → BYPASS存在预渲染跳过Cookie/令牌
    crawler
    → BYPASSSEO爬虫用户代理——返回完整响应以便爬虫索引真实内容
    stale_time
    STALE基于时间的
    revalidate
    间隔已到期;后台正在重新生成(SWR)
    stale_tag
    STALE标签已失效(
    revalidateTag
    /
    invalidateByTag
    );正在重新生成
    stale_error
    STALE重新验证尝试失败;返回最后一次成功的副本(错误信号)
    带有
    draft_mode
    /
    prerender_bypass
    /
    crawler
    原因的原始
    MISS
    显示为
    BYPASS
    (这些通常是预期情况)。三种
    stale_*
    原因可区分健康的时间刷新(
    stale_time
    )、粗粒度标签影响(
    stale_tag
    )和重新生成失败(
    stale_error
    )。可通过
    vercel logs
    或控制台日志的“Reason”行查看
    cacheReason
    ——
    x-vercel-cache-reason
    头是内部专用的,无法通过
    curl
    查看。
  • PPR状态
    ppr_state
    )——针对部分预渲染路由,响应中预渲染内容与每个请求计算内容的占比。仅在
    partial_prerender
    请求中设置;纯
    prerender
    /
    func
    /
    static
    路由以及代理无法分类的情况(静态壳冷未命中、
    BYPASS
    )下为空。共有三种状态:
    ppr_state
    含义
    Static完全预渲染——无延迟状态,因此函数不会为响应体触发执行
    Partial静态壳从缓存返回 + 延迟片段由函数针对每个请求填充
    Dynamic整个响应体均为延迟内容,由函数针对每个请求渲染
    仍触发函数执行的Partial请求并非缓存未命中——缓存的静态壳会立即返回,同时函数仅填充动态片段。可通过
    vercel logs
    、控制台日志面板或使用
    vercel metrics vercel.request.count --group-by ppr_state
    聚合查看
    ppr_state
    。与
    cacheReason
    类似,
    x-vercel-ppr-state
    头是内部专用的,无法通过
    curl
    查看。

Investigating cache issues

缓存问题排查

Reach for the Vercel CLI.
vercel metrics
gives aggregate numbers (requires Observability Plus);
vercel logs
shows per-request behavior.
Metrics need to be queried by team and project (
-S <team> -p <project>
). Filter production with
-f "environment eq 'production'"
(there is no
--prod
flag). Run
vercel metrics schema <metric>
to discover dimensions; use
-F json
for machine-readable output. With
-g
, remember
--limit
is per time bucket
— omit
-g
when you need totals across the whole window.
使用Vercel CLI工具进行排查。
vercel metrics
提供聚合数据(需Observability Plus);
vercel logs
显示每个请求的行为。
指标需按团队和项目查询(
-S <team> -p <project>
)。使用
-f "environment eq 'production'"
过滤生产环境(无
--prod
标志)。运行
vercel metrics schema <metric>
查看维度;使用
-F json
获取机器可读输出。使用
-g
时需注意**
--limit
是每个时间桶的限制**——若需要整个时间段的总数,需省略
-g

Cache hit rate

缓存命中率

Start here for an overall picture of how well caching is working.
Step 1 — overall split. Group
vercel.request.count
by
cache_result
. Treat
HIT
,
STALE
, and
PRERENDER
as cache-served; focus investigation on
MISS
. Exclude
BYPASS
and
(not set)
when computing a hit rate over cacheable traffic (see Debugging BYPASS traffic).
STALE
means stale-while-revalidate is working — dig into revalidation frequency in Analyzing ISR costs, not here.
bash
vercel metrics vercel.request.count -S <team> -p <project> \
  -f "environment eq 'production'" --group-by cache_result --since 24h
Step 2 — where misses concentrate. Split the
MISS
bucket (and optionally
STALE
) by
path_type
, then by
route
or
request_path
:
bash
vercel metrics vercel.request.count -S <team> -p <project> \
  -f "environment eq 'production' and cache_result eq 'MISS'" \
  --group-by path_type --since 24h

vercel metrics vercel.request.count -S <team> -p <project> \
  -f "environment eq 'production' and cache_result eq 'MISS' and path_type eq 'prerender'" \
  --group-by request_path --since 24h
What to expect:
prerender
routes (static shells, ISR pages) should show a high share of
HIT
/
PRERENDER
. A
prerender
path with a disproportionate
MISS
count is your short list for per-path header inspection (
curl
above) and code review.
streaming_func
routes render dynamically by default, but you can still cache them with
Cache-Control
headers — matching requests are cached on the CDN. Each cache entry varies by
Vary
headers (cookies, RSC, etc.) as well as path and query parameters, so expect more cache keys and a lower hit rate than a fully static
prerender
route.
从整体缓存效果开始排查。
步骤1 — 整体分布。按
cache_result
分组统计
vercel.request.count
。将
HIT
STALE
PRERENDER
视为缓存提供的服务;重点排查
MISS
。计算可缓存流量的命中率时,需排除
BYPASS
(not set)
(详见调试BYPASS流量)。
STALE
意味着stale-while-revalidate策略正常工作——需在分析ISR成本中排查重新验证频率,而非此处。
bash
vercel metrics vercel.request.count -S <team> -p <project> \
  -f "environment eq 'production'" --group-by cache_result --since 24h
步骤2 — 未命中集中路径。将
MISS
(可选
STALE
)按
path_type
拆分,再按
route
request_path
拆分:
bash
vercel metrics vercel.request.count -S <team> -p <project> \
  -f "environment eq 'production' and cache_result eq 'MISS'" \
  --group-by path_type --since 24h

vercel metrics vercel.request.count -S <team> -p <project> \
  -f "environment eq 'production' and cache_result eq 'MISS' and path_type eq 'prerender'" \
  --group-by request_path --since 24h
预期结果
prerender
路由(静态壳、ISR页面)应显示较高的
HIT
/
PRERENDER
占比。
prerender
路径中
MISS
占比过高的是重点排查对象,需进行单路径头信息检查(上述
curl
命令)和代码审查。
streaming_func
路由默认动态渲染,但可通过
Cache-Control
头进行缓存——匹配的请求会被CDN缓存。每个缓存条目会根据
Vary
头(Cookie、RSC等)以及路径和查询参数区分,因此缓存键更多,命中率会低于完全静态的
prerender
路由。

Analyzing ISR costs

分析ISR成本

Once you know hit rate, quantify ISR spend and whether revalidation — not traffic volume — is driving it.
Utilization vs. ISR billing. Utilization is
vercel.request.count
— total request volume. ISR cost is billed separately in 8 KB units:
read_units
when the regional CDN misses and falls through to the ISR cache, and
write_units
on every revalidation/regeneration. The regional CDN shields ISR heavily — most requests never touch the ISR layer, so read_units will be far below request count. Do not compare read_units to write_units as a utilization check; focus on write_units (revalidation cost) and how they relate to total traffic.
bash
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
Write utilization = cache serves ÷ ISR writes — cached reads per regeneration.
bash
undefined
了解命中率后,量化ISR支出并确认是否是重新验证(而非流量规模)导致成本上升。
使用率与ISR计费使用率
vercel.request.count
——总请求量。ISR成本按8 KB单位单独计费:区域CDN未命中时读取ISR缓存会产生
read_units
,每次重新验证/重新生成会产生
write_units
。区域CDN会大幅屏蔽ISR请求——大多数请求从未触达ISR层级,因此read_units会远低于请求量。不要将read_units与write_units对比来检查使用率;重点关注write_units(重新验证成本)及其与总流量的关系。
bash
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
写入使用率 = 缓存服务次数 ÷ ISR写入次数——每次重新生成对应的缓存读取次数。
bash
undefined

numerator: cache serves — sum the HIT + STALE + PRERENDER buckets

分子:缓存服务次数——汇总HIT + STALE + PRERENDER的数量

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.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

denominator: ISR writes

分母:ISR写入次数

vercel metrics vercel.isr_operation.write_units -S <team> -p <project>
-f "environment eq 'production'" --group-by route -a sum --since 24h

High is good; near or below ~1 means you regenerate about as fast as the page is read (wasted writes) → lengthen the revalidate interval or move time-based to on-demand tag revalidation.

**Which routes revalidate most.** Break write units down by `route` and `request_path` to find paths that regenerate often relative to traffic:

```bash
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
Regeneration vs. serving. Group write units by
path_type
— concentration in
background_func
confirms revalidation (not per-request dynamic work) is the cost driver.
Time-based vs. tag-based revalidation. Time-based intervals regenerate on a schedule whether or not content changed — often inefficient. Tag-based on-demand revalidation is usually better, but an overly broad tag has a large blast radius: one invalidate drops every entry that carries it.
  • Tag blast radius — group write units by
    cache_tags
    . If many unrelated routes show near-identical write counts, a shared hot tag is invalidating them in lockstep (e.g. every blog post rewriting at the same rate because they share one broad
    blogPost
    tag):
bash
vercel metrics vercel.isr_operation.write_units -S <team> -p <project> \
  -a sum --group-by cache_tags --since 24h
  • What triggered revalidation — group
    vercel.request.count
    by
    triggering_tag
    to see which tags fire most often (
    triggering_tag
    is on request count only, not ISR operation metrics. It is one of the tags that triggered the page to be stale):
bash
vercel metrics vercel.request.count -S <team> -p <project> \
  -f "triggering_tag ne null" --group-by triggering_tag --since 24h
Tags with a large blast radius that revalidate frequently are the usual root cause of high write_units. Prefer granular tags (
product-${id}
) and on-demand invalidation over short time-based intervals for event-driven content.
Confirm in code. Metrics tell you which tag is hot; the repo tells you why. Grep for the tag's invalidation call site —
revalidateTag(
,
invalidateByTag(
,
updateTag(
,
dangerouslyDeleteByTag(
— and read the trigger. A CMS webhook or a sync cron that invalidates a broad tag on every event (instead of a specific
${type}:${id}
) is the classic amplifier.
vercel metrics vercel.isr_operation.write_units -S <team> -p <project>
-f "environment eq 'production'" --group-by route -a sum --since 24h

数值越高越好;接近或低于1意味着重新生成速度与页面读取速度相当(写入浪费)→ 延长重新验证间隔,或将基于时间的重新验证改为按需标签重新验证。

**重新验证最频繁的路由**。按`route`和`request_path`拆分write_units,找出相对于流量而言重新生成频繁的路径:

```bash
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
重新生成与服务。按
path_type
分组write_units——集中在
background_func
说明重新验证(而非每个请求的动态工作)是成本驱动因素。
基于时间与基于标签的重新验证。基于时间的间隔会按计划重新生成内容,无论内容是否变化——通常效率低下。按需基于标签的重新验证通常更好,但过于宽泛的标签会产生较大影响范围:一次失效操作会清除所有关联条目。
  • 标签影响范围——按
    cache_tags
    分组write_units。若许多_不相关_路由的写入次数几乎相同,说明存在共享的高频标签,会同步失效这些路由(例如,所有博客文章共享一个宽泛的
    blogPost
    标签,导致它们以相同频率重新生成):
bash
vercel metrics vercel.isr_operation.write_units -S <team> -p <project> \
  -a sum --group-by cache_tags --since 24h
  • 重新验证触发源——按
    triggering_tag
    分组
    vercel.request.count
    ,查看哪些标签触发最频繁(
    triggering_tag
    仅存在于请求计数指标中,不存在于ISR操作指标中。它是导致页面过期的标签之一):
bash
vercel metrics vercel.request.count -S <team> -p <project> \
  -f "triggering_tag ne null" --group-by triggering_tag --since 24h
影响范围大且重新验证频繁的标签通常是write_units过高的根本原因。对于事件驱动的内容,优先使用细粒度标签(如
product-${id}
)和按需失效,而非短时间间隔的基于时间重新验证。
代码层面确认。指标会告诉你哪个标签是高频触发源;代码仓库会告诉你原因。搜索标签的失效调用位置——
revalidateTag(
invalidateByTag(
updateTag(
dangerouslyDeleteByTag(
——并查看触发逻辑。典型问题是CMS webhook或同步定时任务在每次事件时失效宽泛标签(而非特定的
${type}:${id}
)。

Debugging BYPASS traffic

调试BYPASS流量

The largest legitimate sources of
BYPASS
are Draft Mode and SEO crawlers. Draft Mode must bypass cache so editors see live content. SEO bots must receive the full response — especially on PPR routes where the static shell and dynamic holes are assembled at request time — so crawlers index what users actually see. That BYPASS is expected, not a misconfiguration.
Before tuning headers or revalidate intervals, confirm what's left after those two buckets:
bash
vercel metrics vercel.request.count -S <team> -p <project> \
  -f "cache_result eq 'BYPASS'" --group-by bot_category --since 24h

vercel metrics vercel.request.count -S <team> -p <project> \
  -f "cache_result eq 'BYPASS'" --group-by user_agent --since 24h

vercel metrics vercel.request.count -S <team> -p <project> \
  -f "cache_result eq 'BYPASS'" --group-by request_method --since 24h
The Firewall/WAF with the
vercel-firewall
skill can be used to manage verified SEO crawlers, block abusive bots, and rate-limit junk traffic before it distorts your hit-rate picture.
BYPASS
的主要合法来源是草稿模式SEO爬虫。草稿模式必须跳过缓存,以便编辑者查看实时内容。SEO爬虫必须接收完整响应——尤其是在PPR路由中,静态壳和动态片段会在请求时组装——以便爬虫索引用户实际看到的内容。这类BYPASS是预期情况,并非配置错误。
在调整头信息或重新验证间隔之前,先确认排除这两类后的剩余流量:
bash
vercel metrics vercel.request.count -S <team> -p <project> \
  -f "cache_result eq 'BYPASS'" --group-by bot_category --since 24h

vercel metrics vercel.request.count -S <team> -p <project> \
  -f "cache_result eq 'BYPASS'" --group-by user_agent --since 24h

vercel metrics vercel.request.count -S <team> -p <project> \
  -f "cache_result eq 'BYPASS'" --group-by request_method --since 24h
带有
vercel-firewall
技能的防火墙/WAF可用于管理已验证的SEO爬虫、阻止恶意机器人,并在垃圾流量影响命中率之前进行限流。

Reducing ISR cost

降低ISR成本

  • Prefer tag-based over time-based revalidation. Replace short
    revalidate
    intervals with on-demand
    revalidateTag
    /
    invalidateByTag
    when content changes — time-based regeneration runs whether or not anything changed. If using Cache Components, analyze
    cacheLife
    calls with the
    next-cache-components
    skill.
  • Scope tags to specific IDs. Invalidate
    blogPost:<id>
    , not a generic
    blogPost
    /
    page
    tag — one broad invalidate regenerates everything that carries it.
  • Tune the revalidate interval where your framework declares it (Next.js
    revalidate
    /
    cacheLife
    , SvelteKit
    isr
    , Nuxt
    routeRules
    , Astro). For Next.js Cache Components, see the
    next-cache-components
    skill.
  • Use
    CDN-Cache-Control
    headers to cache dynamic functions.
  • 优先使用基于标签的重新验证。当内容变化时,用按需
    revalidateTag
    /
    invalidateByTag
    替换短
    revalidate
    间隔——基于时间的重新生成会在内容未变化时仍执行。若使用缓存组件,可通过
    next-cache-components
    技能分析
    cacheLife
    调用。
  • 将标签限定到特定ID。失效
    blogPost:<id>
    ,而非通用的
    blogPost
    /
    page
    标签——一次宽泛的失效操作会重新生成所有关联条目。
  • 在框架声明处调整重新验证间隔(Next.js的
    revalidate
    /
    cacheLife
    、SvelteKit的
    isr
    、Nuxt的
    routeRules
    、Astro的对应配置)。对于Next.js缓存组件,参考
    next-cache-components
    技能。
  • 使用
    CDN-Cache-Control
    头缓存动态函数。

Inspect one path

检查单个路径

bash
curl -sSI https://<host>/<path> | grep -iE 'x-vercel-cache|x-matched-path|cache-control|vary|age|set-cookie'
This zero-dependency first reach shows the status (
x-vercel-cache
), the cache directives (
Cache-Control
/
CDN-Cache-Control
/
Vercel-CDN-Cache-Control
), and — crucially —
x-matched-path
, which reveals rewrites like
/precomputed/exp~.../...
that expose experiment/flag precomputation.
vary
flags personalization (RSC, cookies);
set-cookie
forces
BYPASS
. For a per-phase timing breakdown,
vercel httpstat /some/path
(CLI v48.9.0+; needs the
httpstat
tool installed) adds latency stats. A path that should cache but shows
MISS
/
BYPASS
usually has
private
,
no-store
,
max-age=0
, a per-request input (cookies/headers/
searchParams
), or an uncacheable method (see FAQ).
Inspect one request. When metrics or headers give you a request ID, pull the full log record:
bash
vercel logs --request-id <request-id> --json
Use
--json
so the agent can parse cache status, path, and timing fields programmatically.
bash
curl -sSI https://<host>/<path> | grep -iE 'x-vercel-cache|x-matched-path|cache-control|vary|age|set-cookie'
这个零依赖的命令可查看缓存状态(
x-vercel-cache
)、缓存指令(
Cache-Control
/
CDN-Cache-Control
/
Vercel-CDN-Cache-Control
),以及至关重要的**
x-matched-path
**——它会显示重写路径(如
/precomputed/exp~.../...
),暴露实验/特性标志的预计算逻辑。
vary
标志表示个性化内容(RSC、Cookie);
set-cookie
会强制触发
BYPASS
。如需分阶段的延迟统计,
vercel httpstat /some/path
(CLI v48.9.0+;需安装
httpstat
工具)会添加延迟数据。应该缓存但显示
MISS
/
BYPASS
的路径通常包含
private
no-store
max-age=0
、每个请求的输入(Cookie/头信息/
searchParams
)或不可缓存的方法(详见FAQ)。
检查单个请求。当指标或头信息提供请求ID时,拉取完整日志记录:
bash
vercel logs --request-id <request-id> --json
使用
--json
以便工具可程序化解析缓存状态、路径和时间字段。

FAQ

FAQ

  • What are prerender variant misses? When a route uses a dynamic param, each distinct cache-key variant is prerendered and cached separately, so each variant misses on its first hit per region and low-traffic ones rarely stay warm. The most common modern cause is feature-flag / experiment precomputation — middleware picks a variant per request (
    /precomputed/exp~.../...
    paths), and flags × routes × PPR segments multiply into thousands of ISR entries (also a middleware-invocation cost). Fix: collapse the variant matrix (retire finished experiments), or accept the cost.
  • Does PPR avoid function invocations? No — a PPR route has dynamic holes by definition, so the cached shell hit still runs the function to fill them. (A route with no holes is just ISR and serves a pure
    prerender
    HIT — see Key concepts.)
  • Why are there more function invocations than PPR requests? PPR requests have a static shell and a dynamic function invocation. When the static shell needs to be regenerated, it incurs a function invocation on top of the dynamic function for the content.
  • 什么是预渲染变体未命中? 当路由使用动态参数时,每个不同的缓存键变体都会被预渲染并单独缓存,因此每个变体在每个区域的首次请求都会未命中,低流量变体很难保持缓存热度。最常见的现代原因是特性标志/实验预计算——中间件会针对每个请求选择变体(
    /precomputed/exp~.../...
    路径),标志×路由×PPR片段会生成数千个ISR条目(同时也会增加中间件调用成本)。解决方法:合并变体矩阵(结束已完成的实验),或接受该成本。
  • PPR是否能避免函数调用? 不能——PPR路由本质上包含动态片段,因此命中缓存静态壳时仍会触发函数执行以填充片段。(无动态片段的路由完全属于ISR,会返回纯
    prerender
    命中——详见关键概念。)
  • 为什么函数调用次数多于PPR请求次数? PPR请求包含静态壳和动态函数调用。当静态壳需要重新生成时,除了内容的动态函数调用外,还会额外触发一次函数执行。

Related skills

相关技能

  • vercel-firewall
    — manage verified SEO crawlers, block abusive bots, and rate-limit junk BYPASS traffic.
  • runtime-cache
    — caching data between your function and a backend (per-region key-value / data cache). A different layer from the CDN/ISR caches; use it to cache an API response or query result inside a function.
  • next-cache-components
    — Next.js
    use cache
    ,
    cacheLife
    ,
    cacheTag
    , and
    revalidate
    tuning (one framework's ISR/PPR controls).
  • vercel-firewall
    ——管理已验证的SEO爬虫、阻止恶意机器人,并对垃圾BYPASS流量进行限流。
  • runtime-cache
    ——在函数与后端之间缓存数据(区域级键值/数据缓存)。这是与CDN/ISR缓存不同的层级;可用于在函数内部缓存API响应或查询结果。
  • next-cache-components
    ——Next.js的
    use cache
    cacheLife
    cacheTag
    revalidate
    配置调优(某一框架的ISR/PPR控制)。

References:

参考文献: