Next.js 16 guide
Debugging Next.js Caching on Vercel
Classify the cache layer, prove the response is wrong, and apply the smallest recovery that restores Acme Shop without a risky broad purge.
On this page
"The cache is stale" can mean an old deployment, a cached public HTTP response, a tagged Next.js fetch, an optimized image, or data that was never cached. The safe response is not to purge first. It is to identify the layer, preserve a harmless body marker, and choose a recovery that matches the evidence.
Use only public test data
Debug Acme Shop with a public catalog URL and a harmless revision marker. Do not put authorization headers, cookies, customer URLs, deployment-protection secrets, or full private payloads into shell history, tickets, or shared logs.
Overview
Outcome and prerequisites
Outcome: You can distinguish a Vercel CDN response state from a Next.js data-cache result, capture a header-and-body transcript, and recover a public catalog issue without touching account traffic.
Prerequisites: A linked Vercel project, the Acme Shop preview or production deployment URL, a known public catalog revision, access to non-sensitive runtime logs, and an approved known-good deployment for rollback.
Follow the decision flow
Use one URL and one expected value. For this scenario, Acme Shop expects /api/public-catalog to contain catalogRevision: "2026-07-14T10:00Z". A cache status without that body check is only a clue.
- Name the expected public revision
Use a harmless catalog marker, not a customer value.
- Capture headers and body twice
A cache status alone does not prove correctness.
- Confirm the deployment
Restore or promote a known-good deployment if it does not contain the intended release.
- Classify CDN versus tagged data
Correct the response policy for CDN issues; correct source, tags, or paths for data issues.
- Validate or recover narrowly
Record a correct body as evidence; invalidate only a reviewed public tag after the response is fixed.
Do not purge when the body is already correct. Treat a private-data exposure as a data-isolation incident, not a cache-tuning exercise.
Capture the exact response before changing anything
Request the same public URL twice. -D - prints response headers and the default output prints the body, producing one shareable transcript without a private credential.
curl -sS -D - https://acme-shop-preview.example/api/public-catalog
curl -sS -D - https://acme-shop-preview.example/api/public-catalog
HTTP/2 200
content-type: application/json
cache-control: public, max-age=0, must-revalidate
x-vercel-cache: HIT
x-vercel-id: cle1::iad1::abc123
{"catalogRevision":"2026-07-14T10:00Z","products":[{"slug":"solar-pack","name":"Solar Pack","priceCents":12900}]}For a protected preview, vercel curl requires Vercel CLI v48.8.0 or later and vercel httpstat requires v48.9.0 or later; both can avoid placing a bypass secret in the command. These commands are beta, so retain ordinary curl as the portable header-and-body capture.
vercel curl /api/public-catalog --deployment https://acme-shop-preview.example
vercel httpstat /api/public-catalog --deployment https://acme-shop-preview.example
Identify the stale layer
| Symptom | Likely layer | First evidence to collect |
|---|---|---|
| The current response lacks the intended commit or release behavior | Deployment or build output | vercel inspect <deployment-url> and the body revision |
A complete public JSON response is old and x-vercel-cache reports HIT or STALE | Vercel CDN | Headers, body marker, cache tag, and response eligibility |
| Several pages show an old catalog value | Next.js data cache | Exact fetch tag, source record, webhook result, and runtime logs |
| An image remains old after its source changed | Image optimization cache | Source-image URL and its specific invalidation workflow |
| Only signed-in visitors see it | Private data path or application logic | A controlled test account and origin/application logs, not a shared-cache change |
x-vercel-cache reports the CDN response state. It can be MISS for a dynamic route even when a server-side data fetch was cached. A harmless body revision and application logs are the evidence for the data layer.
Inspect the active deployment and contract
Confirm the deployment serving traffic before changing a cache rule. Then inspect the exact public response and the fetch that supplies it.
vercel inspect https://acme-shop-production.example
vercel logs --environment production --query "public-catalog" --since 1h --expand
// Public data: intentional persistence, fallback freshness, known invalidation target.
await fetch("https://catalog.acme-shop.example/v1/products", {
cache: "force-cache",
next: { revalidate: 300, tags: ["acme-shop:catalog"] },
})
// Private data: never make this a shared response.
await fetch("https://accounts.acme-shop.example/v1/me", {
cache: "no-store",
headers: { Authorization: `Bearer ${accessToken}` },
})
Check for Set-Cookie, request Authorization, private, no-store, Vary: *, redirects, unsupported status codes, and body differences. Any of them can explain why a public CDN response is not cacheable, and none should be bypassed to improve a cache metric.
Use the narrowest recovery
- Wrong deployment: restore the approved known-good deployment, then repeat the same body check.
- Wrong source or Next.js tagged data: correct the source and invoke Acme Shop's authenticated, allowlisted revalidation endpoint for its known tag and paths.
- Wrong public CDN response: fix the route's output or headers first. Once correct, invalidate only the response's CDN tag from a reviewed linked-project session.
- Optimized image: invalidate the specific source image; do not purge catalog or account responses.
# Only after the public response is correct and the tag scope is reviewed.
vercel cache invalidate --tag acme-shop:catalog-response
# Use only when immediate service restoration requires the approved prior release.
vercel rollback https://acme-shop-known-good.example
vercel cache invalidate --tag marks content with that Vercel CDN tag stale so a later request can revalidate in the background. It is not a substitute for correcting a bad source record, a broken cache key, or an unsafe response. Do not use vercel cache dangerously-delete or a project-wide purge as a default incident action.
Prove recovery and record it
| Validation | Expected result | Failure signal and response |
|---|---|---|
| Positive | The next public request has the expected revision; a later request remains correct and may change from STALE to HIT. | Revision stays old: confirm source data, tag spelling, and affected path before trying another invalidation. |
| Negative | /account is still dynamic/private and no public route contains a test account marker. | Any cross-account marker: remove shared eligibility, roll back if needed, and treat it as a data-isolation incident. |
| Failure | A controlled upstream error is visible as an error response or application error, not a fabricated fresh catalog. | A cached success masks the controlled failure: inspect CDN TTL and upstream error handling before restoring cache behavior. |
Keep the incident record small but complete: URL, expected and observed revision, timestamps, region header, deployment, command scope, operator, and final validation. Never record secrets or private bodies.
Troubleshooting
| Symptom | Cause | Narrow recovery |
|---|---|---|
x-vercel-cache: HIT but the body is wrong | A valid cached response contains an old or bad source representation. | Verify the source revision, correct it, then invalidate only acme-shop:catalog-response. |
x-vercel-cache: MISS but the catalog still looks old | The CDN missed while the Next.js data cache supplied the fetch result. | Inspect the fetch tag and webhook logs; do not increase CDN TTL. |
The browser response does not show s-maxage | Vercel consumes Vercel-specific shared directives before forwarding browser headers. | Use x-vercel-cache plus the body and route source as evidence. |
| Preview works but production is old | The production alias points at another deployment or production environment differs. | Run vercel inspect for the production deployment and compare revision/configuration. |
vercel cache invalidate cannot find the expected result | The response was not emitted with that Vercel CDN tag, the project is not linked, or the scope is wrong. | Verify the tag in source and linked project; use the application webhook for Next.js data tags. |
| Local development never reproduces production | Development fetch/HMR behavior differs from deployed cache behavior. | Reproduce in a preview deployment with the same public URL and body marker. |
Related guides
Authoritative references
Debug cache behavior without risky broad purges
Optimi can help build cache runbooks, response validation, and recovery paths for critical Next.js and Vercel workloads.
Discuss cache observability