Files
Hendrik LiebauandGitHub b420536fe8 Fix false-positive nested-cache error for a short default profile (#95373)
Overriding the `default` `cacheLife` profile with a short cache life
(the built-in `expire` is `INFINITE_CACHE`) makes every `'use cache'`
without an inline `cacheLife()` inherit that short life. In this
configuration the nested-`'use cache'` error, in both its short-`expire`
and zero-`revalidate` variants, fired in two cases where it should not
have.

The error exists to catch a nested cache silently shortening an outer
cache that has no explicit `cacheLife`, degrading it to a dynamic hole
the developer did not choose. But with a short default profile, a single
non-nested `'use cache'` already resolves to a short life with nothing
nested, so the error fired with no nesting at all: in production this
killed the build, and in development it threw on the request. And even
for a genuinely nested cache, the warning is pointless here, because the
default profile already makes every cache a dynamic hole, so there is no
silent degradation to surprise the developer.

The throw condition keyed on a short life that was not set through an
inline `cacheLife()`, and a value inherited from the `default` profile
is non-explicit, so it matched. This change additionally requires that a
dynamic nested cache actually propagated its short life upward
(`dynamicNestedCacheError` is set) and that the `default` profile is
itself prerenderable. Otherwise the short-lived entry is omitted as a
dynamic hole instead, exactly as an inline short `cacheLife()` already
was. Genuine nesting under a prerenderable default profile still errors.

The nesting condition also subsumes the dev-only private-cache
exclusion: a private cache's `revalidate: 0` is forced by us rather than
by a nested cache and never carries a nested error, so the explicit
`kind !== 'private'` check is no longer needed.
2026-07-02 23:53:17 +02:00

32 lines
936 B
TypeScript

import { Suspense } from 'react'
import { cacheLife } from 'next/cache'
async function innerCache() {
'use cache'
cacheLife({ expire: 60 }) // short, under the prerenderable minimum
return new Date().toISOString()
}
async function outerCache() {
'use cache'
// No explicit `cacheLife`. Normally a short-lived cache nested inside an
// outer cache without an explicit `cacheLife` errors during prerendering,
// because the outer is silently degraded to a dynamic hole. Here it must not:
// this app's default profile is itself dynamic (`expire: 0`), so every cache
// is omitted from prerenders by default and there is nothing to warn about.
return innerCache()
}
async function NestedValue() {
const value = await outerCache()
return <p id="value">{value}</p>
}
export default function Page() {
return (
<Suspense fallback={<p id="fallback">Loading...</p>}>
<NestedValue />
</Suspense>
)
}