mirror of
https://github.com/hanzoai/next.js.git
synced 2026-09-24 17:00:51 +00:00
Private caches were never stored in a cache handler, so every reload in `next dev` re-ran them from scratch and they registered as a cache miss on each load. This change persists `'use cache: private'` entries in development in a dedicated built-in in-memory handler so that warm reloads are fast. The handler is gated on `process.env.__NEXT_DEV_SERVER` and is kept out of the kind-keyed handlers map so it can never be replaced by a user-configured `default` handler: private cache entries can hold data specific to the incoming request (for example, derived from its cookies or headers) and must never reach a remote or otherwise persistent handler. Production keeps private caches non-persisted as before. The coarse cache-handler key for a dev private cache is scoped by the request's cookies and headers so entries for requests with different request data don't collide. It excludes Next-internal cookies that aren't application data (the HMR refresh hash, already part of the cache key, and the instant-navigation cookie, which toggles while a navigation lock is held) and the transport and content-negotiation headers that vary between otherwise-equivalent requests (a browser reload adds `cache-control`, and `accept` and `sec-fetch-*` differ between an HTML navigation and an RSC request). Keying by only the cookies and headers a cache actually reads is left as a follow-up; read root params are already tracked that way, the same as for public caches. The cache life is forced to `revalidate: 0` with a 5-minute `expire`, so each read serves the stale entry immediately and warms a fresh one in the background through the existing stale-while-revalidate path. Cross-request deduplication now applies to private caches in development too, so concurrent requests with identical request data share a single fill; it remains skipped in production where request-specific data must not be shared across requests. To make this deterministic, `saveToCacheHandler` resolves the metadata a cross-request joiner awaits only after the entry has been written to the handler, so the joiner finds it when re-reading its recomputed key. This closes a pre-existing race in that path, present for public caches too but never surfaced by their cross-request test, where the joiner's metadata could resolve before the handler write had landed. The defensive invariant that rejected reading a private entry from a handler is removed: it only existed to narrow `cacheContext.kind` for a code path that no longer needs it, and dev now legitimately reads persisted private entries while production never registers a private handler to read from.
17 lines
375 B
TypeScript
17 lines
375 B
TypeScript
import { Suspense } from 'react'
|
|
import { setTimeout } from 'timers/promises'
|
|
|
|
async function PrivateCached() {
|
|
'use cache: private'
|
|
await setTimeout(1500)
|
|
return <p id="private">{new Date().toISOString()}</p>
|
|
}
|
|
|
|
export default function Page() {
|
|
return (
|
|
<Suspense fallback={<p id="private-fallback">Loading...</p>}>
|
|
<PrivateCached />
|
|
</Suspense>
|
|
)
|
|
}
|