Serve stale `'use cache'` entries in the dev server until they expire (#94662)
The default in-memory cache handler drops an entry once it goes stale
(past its `revalidate` time) rather than serving it: in-memory entries
are fragile, so warming a replacement in the background isn't worth it
when it's likely to be evicted before it's used, and we don't want to
keep reusing a stale entry for too long either. With this change the dev
server (`next dev`) makes an exception and serves the stale entry until
it actually expires, using `expire` as the drop threshold instead of
`revalidate` and relying on the wrapper's existing
stale-while-revalidate path to warm a fresh entry in the background.
Production (`next start`) and `next build --debug-prerender` keep the
previous drop-at-`revalidate` behavior, so the change is gated on
`process.env.__NEXT_DEV_SERVER`. The tag-based discard is preserved, so
`revalidateTag` still invalidates in dev.
This change has two motivations. It keeps the dev inner loop fast,
turning a warm reload of a short-lived cache into an immediate hit
instead of a cold miss that re-runs the cache. It is also the foundation
for upcoming dev-only caching changes that depend on the handler serving
stale entries: persisting `'use cache: private'` entries in dev, and
keeping `cacheMaxMemorySize: 0` fast in dev. Both serve a previous value
while warming a fresh one in the background, which only works once the
handler stops dropping stale entries in dev.
This also lets us drop the workarounds that several Cache Components dev
fixtures used to isolate themselves from the handler dropping the entry
at `revalidate`. The short-lived fixtures go back to
`cacheLife('seconds')` and the short-stale fixtures drop their long
`revalidate`. Their warm reloads are now stale hits served by the dev
server, which is the behavior these tests are meant to exercise and
which removes the short-lived hit/miss flakiness at its source.
A new `use-cache` test pins the behavior directly: in dev a reload after
`revalidate` serves the previous value immediately and warms a fresh one
in the background, while a `next start` build of the same fixture
re-runs the cache and shows a fresh value.