Vercel keeps copies of your pages close to visitors so they load fast — this explains where those copies live, why one is stale or being rebuilt more often than it should, and what that is costing.
WHEN TO USE
SCOPE
USE FOR
Find out why a page's cache hit rate is low and which routes cause the misses.
Track down which cache tag has a blast radius wide enough to be driving up costs.
Read one response's cache headers to see if it hit, missed, or bypassed the CDN.
Decide whether to switch a route from time-based to tag-based revalidation.
NOT FOR
Caching data between your function and a backend — that's the Runtime Cache skill.
Tuning Next.js use cache, cacheLife, or cacheTag calls — see next-cache-components.
This is a saved copy of the skill's SKILL.md instructions. It may differ from the upstream file or the version you install.
Vercel Caching
You are an expert in understanding Vercel's caching infrastructure, and how the CDN Cache, ISR, and PPR work.
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).
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.
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. Scoped to its deployment (a new deploy doesn't reuse it); kept until revalidated or unaccessed for 31 days; 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.
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 16+ revalidateTag(tag, 'max')) = stale-while-revalidate. Keeps serving stale while refreshing in the background → response shows x-vercel-cache: STALE.
Dangerously-delete (dangerouslyDeleteByTag, Next.js updateTag, revalidatePath, or revalidateTag(tag) with no profile) = 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:
Value
Meaning
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)
Cache reason (cacheReason) — the finer explanation of that outcome for a single request. The cache_result metric lumps all MISSes (and all STALEs) together; the reason is the only thing that tells them apart. Eleven values: four for MISS, three map to BYPASS, three for STALE, one for REVALIDATED:
cacheReason
Refines
Meaning
cold
MISS
Cache empty for this key/variant (first request or evicted); the function ran
collapsed
MISS
Concurrent requests to one uncached path collapsed into a single invocation
error
MISS
An error prevented serving from cache
vary_key_denied
MISS
Origin's Vary header names a high-cardinality header (e.g. Cookie); response can't be cached
draft_mode
→ BYPASS
Next.js Draft Mode active — bypassed so editors see live content
prerender_bypass
→ BYPASS
Request matched the route's experimentalBypassFor config (e.g. bot UA on PPR)
crawler
→ BYPASS
SEO-crawler UA — full response served so bots index real content
stale_time
STALE
Time-based revalidate interval elapsed; regenerating in background (SWR)
stale_tag
STALE
Tag invalidated (invalidateByTag / revalidateTag(tag, 'max')); regenerating
stale_error
STALE
A revalidation attempt failed; serving the last-good copy (a bug signal)
Tag-based deletion
REVALIDATED
Tag deleted (dangerouslyDeleteByTag / revalidateTag(tag) with no profile / dashboard purge by tag); foreground regen
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, or aggregate with vercel metrics vercel.request.count --group-by cache_reason — 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
Static
Fully prerendered — no postponed state, so the function is not invoked for the body
Partial
A static shell serves from cache + a postponed hole the function resumes per request
Dynamic
The 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.
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 --prod (equivalent to --filter 'environment:production'; the CLI's filter syntax is KQL — the older OData eq/and syntax is deprecated). Run vercel metrics schema <metric> to discover dimensions; use --format json for machine-readable output. With -g, remember --limit is per time bucket — omit -g when you need totals across the whole window.
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.
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.
Analyzing ISR costs
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 when a revalidation/regeneration produces changed output (unchanged content incurs none). 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.
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
# numerator: cache serves — sum the HIT + STALE + PRERENDER buckets
vercel metrics vercel.request.count -S <team> -p <project> \
--prod -f "cache_result:(HIT OR STALE)" \
--group-by route -a sum --since 24h
# denominator: ISR writes
vercel metrics vercel.isr_operation.write_units -S <team> -p <project> \
--prod --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:
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):
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):
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.
Debugging BYPASS traffic
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:
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.
Reducing ISR cost
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, check cacheLife calls against the Next.js bundled docs (node_modules/next/dist/docs/).
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 bundled docs or the official next-cache-components-optimizer skill (npx skills add vercel/next.js --skill next-cache-components-optimizer).
Use CDN-Cache-Control headers to cache dynamic functions.
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:
vercel logs --request-id <request-id> --json
Use --json so the agent can parse cache status, path, and timing fields programmatically.
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.
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.
vercel metrics requires the Observability Plus plan; without it, use vercel logs for per-request cache debugging instead.
🔌 This skill ships inside the Vercel Plugin. Listed package contents include 4 slash commands, 3 agents, hooks and an MCP server. Installed components and skill versions may vary by AI tool.
Install just this skill
Run this outside your AI tool, using Node.js 22.20.0 or newer. Skills CLI 1.7.2 skips skill selection; prompts cancel installation without an interactive terminal. If choosing Project when asked about installation scope, open your project's folder before running this.
Install checklist
Check selected tools if a picker appears; Claude Code may be unchecked under Additional agents. ↑/↓ moves, Space toggles selections, Enter confirms.
Without a tool picker, check Installing to: for your intended tool. Stop if it names a different tool.
If asked, choose Symlink (Recommended) for shared files or Copy to all agents for separate copies; review the summary before confirming installation.
Install the find-skills skill? defaults to Yes, adding another skill for all projects. Choose No if you don't want that extra installation.
Skills CLI 1.7.2 sends install data to skills.sh by default, including the source, skill names, target tools and file details; GitHub sources are reported only when GitHub confirms the repository is public, while other remote sources can also be reported. Confirmed-public GitHub sources also trigger a security-audit request. Before installing, set the environment variable DO_NOT_TRACK=1 or DISABLE_TELEMETRY=1 to disable both.
After installing for Claude Code, open it in the installation project if you chose Project; Global makes the skill available across projects on this computer. Once Claude Code loads the skill, its description helps Claude decide when to use it.
If installation created a top-level skills folder that did not exist when your Claude Code session started, run /reload-skills in that session. Run it again after later changes in that folder.
Install the whole plugin
This route installs the Vercel Plugin package selected for your AI tool.
Run this command in a terminal
npx plugins add vercel/vercel-plugin
Plugins CLI 1.3.4 sends an install report by default, including the source exactly as entered, plugin names, target tools and scope; it does not check whether repositories are public or private. Before installing, set the environment variable DO_NOT_TRACK=1 or DISABLE_TELEMETRY=1 to disable this report. For data sent by an installed plugin, check that plugin's documentation.
Access and test Vercel deployments protected by Vercel Authentication, SSO, or Deployment Protection. Use when curl, agent-browser, Playwright, or another automated request reaches a Vercel login or protection page; when a protected preview or production URL returns 401 or 403; when TRUSTED_SOURCES_ENVIRONMENT_MISMATCH appears; or when choosing between vercel curl and the x-vercel-trusted-oidc-idp-token header.Read full description
Vercel AI Gateway guidance for setup, model discovery, authentication, routing, fallbacks, virtual models, evaluation models, BYOK, budgets, spend reporting, observability, compatible APIs, and coding-agent configuration. Use when adding AI Gateway to an app, migrating provider calls, choosing models or providers, centralizing model configuration, evaluating application state, debugging gateway requests, or running vercel ai-gateway commands.Read full description
Authentication integration guidance — Clerk (native Vercel Marketplace), Better Auth, Descope, and Auth0 setup for Next.js applications, plus Sign in with Vercel, Vercel Passport, and Vercel KMS. Covers server config, route handlers, proxy.ts auth patterns, sign-in/sign-up flows, and Marketplace provisioning. Use when implementing user authentication, sessions, protected routes, or protecting deployments.Read full description
Project bootstrapping orchestrator for repos that depend on Vercel-linked resources (databases, auth, and managed integrations). Use when setting up or repairing a repository so linking, environment provisioning, env pulls, and first-run db/dev commands happen in the correct safe order.Read full description
Default guidance for building AI agents. Use for generic requests to build, create, scaffold, design, architect, or implement an AI agent, agent app, tool-calling agent, durable agent, multi-agent system, or scheduled agent.Read full description
Backend architecture guidance. Use when planning, building, or migrating an API or backend; choosing between Functions, Services, containers, Workflow, Queues, and Marketplace databases; or selecting a supported backend framework or runtime.Read full description