cloudflare / cloudflare/vinext

Cache: derive foreground stale-entry revalidation from whether the consumer persists to a server cache (not just prerender state)

Open
#2,821 0 comments 0 reactions 0 assignees View on GitHub
nextjs-tracking
Dominant language
TypeScript
Stars
8.8k
Forks
406
Avg merge
2d 6h
Merged PRs (30d)
120

Description

## Next.js Change

**Commit:** [`89ca693`](https://github.com/vercel/next.js/commit/89ca693c2082770f49a2c1964ba78e8971f3d146)
**PR:** [#96731](https://github.com/vercel/next.js/pull/96731)

## What changed

Fixes stale-cache lifetime extension across nested caches. Foreground (synchronous) revalidation of a stale cache entry is necessary whenever a stale result **will be persisted by another server cache**, not only when the request is prerendering. Otherwise a cache created during a dynamic request can consume a stale inner value and **extend its lifetime** by persisting it into the outer cache.

Previously this decision came from `shouldRevalidateStaleCacheEntryInForeground(workUnitStore)`, which was tied to whether the request was prerendering. That missed the dynamic-request case where an outer `'use cache'` (or `unstable_cache`) scope reads a stale inner entry and stores it.

### Mechanism (from the diff)

- `shouldRevalidateStaleCacheEntryInForeground` is replaced by `willConsumerServerCache(workUnitStore)`, modeling the capability as **whether the immediate work-unit consumer will persist the result in a server cache** — rather than inheriting it through the full outer scope chain.
- Cache-store creation now sets `consumerWillServerCache: true` on `'cache'` and `'private-cache'` work units (replacing the per-store `shouldRevalidateStaleCacheEntryInForeground` value).
- **Server cache** and **static prerender** work units are treated as server-caching consumers; **runtime prerenders** are not.
- In `cache()`, the stale-entry regeneration guard uses `willConsumerServerCache(workUnitStore)` instead of the old prerender-derived check. When the entry is stale and its consumer will persist the result in a server cache, don't serve the stale entry (which would unnecessarily shorten the generated result's lifetime) — regenerate it now since we're not time-constrained.

Touched files: `work-unit-async-storage.external.ts`, `patch-fetch.ts`, `use-cache-wrapper.ts`, `unstable-cache.ts`.

## Impact on vinext

vinext reimplements `'use cache'`, `unstable_cache`, and ISR stale-while-revalidate. If vinext decides foreground vs background revalidation purely from "is this a prerender", it has the same bug: during a dynamic request, an outer cache scope that reads a stale inner entry will persist that stale value, extending its lifetime instead of regenerating it synchronously.

What to check/do:

1. **Base foreground revalidation on the consumer's persistence capability**, not on prerender state alone. Model a `willConsumerServerCache` / `consumerWillServerCache` flag: is the immediate work-unit consumer going to persist the result in a server cache?
2. **Classify consumers correctly:** server-cache scopes (`'use cache'`, `unstable_cache`) and static prerenders are server-caching consumers; a plain dynamic request and runtime prerenders are not — **unless** they are nested inside a server-caching scope.
3. **Regenerate stale entries in the foreground when the consumer will persist them**, so a stale inner value doesn't get its lifetime extended by an outer cache.
4. **Propagate the capability to the immediate consumer** rather than inheriting through the whole outer scope chain (an inner cache read from a dynamic request whose immediate consumer is an outer `'use cache'` must revalidate in foreground).
5. **Port the regression test.** Next.js added a `cache-consumer-foreground-revalidate` fixture: an outer `'use cache'` scope consuming a stale `unstable_cache` entry during a dynamic request.

## Related

- #2820 — Cache: reuse completed entries for the rest of a request
- #2819 — Cache: discard only entries that predate a tag revalidation
- #2002 — Cache Components: exclude short-stale `'use cache'` entries from prerenders
- #1936 — Dev cache handler: serve stale `'use cache'` entries until `expire`

Contributor guide

Open the contributing guide

Research direction

Inspect vinext's implementations of 'use cache', unstable_cache, and ISR, using work-unit-async-storage.external.ts, patch-fetch.ts, use-cache-wrapper.ts, and unstable-cache.ts from the referenced Next.js change as context. Find or port the cache-consumer-foreground-revalidate fixture, then verify that an outer server cache regenerates a stale inner entry during a dynamic request while plain dynamic and runtime-prerender consumers do not.

Written by the indexing model from the issue text.

Assessment

Tech stack
next.js, typescript
Domain
backend, performance
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.