next.js
2850659b - Cache short-`expire` `'use cache'` values across dev reloads (#95362)

Commit
3 days ago
Cache short-`expire` `'use cache'` values across dev reloads (#95362) Development has recently gained several mechanisms that make `'use cache'` reloads fast under Cache Components: `'use cache: private'` entries are persisted in a dedicated in-memory handler, `cacheMaxMemorySize: 0` uses a real in-memory handler instead of the no-op stub, and custom handlers are fronted by a fast built-in handler through the tiered handler. One case was still missing. A value that opts into a dynamic, client-only life with an explicit short `expire` (for example `cacheLife({ expire: 0 })`, or the built-in `'seconds'` profile) was treated as a miss on every reload, for both the built-in default handler and custom handlers, so reloads re-ran the cache function and streamed slowly. The reason is that `expire` is the value's expiration bound, the longest it may still be served before it has to be treated as expired. That is its purpose in both dev and production; what differs is which threshold the built-in in-memory handler enforces. In `next dev` it serves stale entries up to `expire` to keep reloads fast, whereas in production it drops them earlier, once past `revalidate`. An `expire` of zero therefore leaves the dev handler no window in which a reload can be served from the cache, and the wrapper's serve-vs-regenerate check, which also keys on `expire`, regenerates instead. This change extends the same dev-only treatment to those values without altering their resolved cache life. The built-in default handler now retains an entry for at least `MIN_PRERENDERABLE_EXPIRE` in dev, a minimum the custom front handler inherits by being a built-in default handler, and the wrapper applies the same minimum when deciding whether to serve or regenerate. That affects the retain and serve decisions only; the entry keeps its real `expire`, so the staged dev render still resolves it at the appropriate stage rather than in the shell stage. A short-`expire` entry is also re-warmed in the background on every dynamic request render, so a reload serves the previously cached value immediately and the freshly recomputed one appears on the next reload. This is the same stale-while-revalidate trade-off already accepted for the private-cache and `cacheMaxMemorySize: 0` dev optimizations, which likewise favor a fast reload over serving a value these configurations would not otherwise cache at all. For custom handlers the re-warm re-executes the function and writes through to the backing. Unlike the private-cache case, we deliberately do not force a dynamic cache life here, because forcing `revalidate: 0` would leak into the cache life propagated to an enclosing `'use cache'` and trip the nested-dynamic error with the wrong message. And unlike the size-0 case, keeping the resolved life alone is not enough, because a short `expire` is exactly what makes the dev handler drop the entry, which is why the minimum retention is needed. Because the dev front handler now enforces that minimum, the tiered handler can no longer evict a stale front entry by writing `expire: 0` (the minimum would keep it alive), so `toExpiredEntry` now writes a negative `expire`, which the default handler recognizes as an eviction sentinel and reports as missing regardless of the retention minimum. This mirrors the existing `revalidate = -1` convention, though a negative `expire` means the entry is dropped rather than served-but-revalidated. Everything is gated on `process.env.__NEXT_DEV_SERVER`, so production behaves exactly as before: short-`expire` values keep their real cache life, and configured handlers are used directly. New development tests cover the built-in and custom-handler cases, asserting that a cache-miss navigation shows the Suspense fallback while a cache-hit one does not, and that a reload serves the cached value yet converges to a fresh one. --- <sub>Stack created with <a href="https://github.com/github/gh-stack">GitHub Stacks CLI</a> • <a href="https://gh.io/stacks-feedback">Give Feedback 💬</a></sub>
Author
Parents
Loading