mirror of
https://github.com/hanzoai/next.js.git
synced 2026-09-24 17:00:51 +00:00
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.
49 lines
2.0 KiB
TypeScript
49 lines
2.0 KiB
TypeScript
import { nextTestSetup } from 'e2e-utils'
|
|
import { retry } from 'next-test-utils'
|
|
|
|
const isoDateRegExp = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/
|
|
|
|
describe('use-cache-default-profile-expire-zero', () => {
|
|
const { next } = nextTestSetup({
|
|
files: __dirname,
|
|
})
|
|
|
|
it('treats a short default cacheLife profile as a dynamic hole, not a nested-cache error', async () => {
|
|
const browser = await next.browser('/')
|
|
const initialValue = await browser.elementById('value').text()
|
|
expect(initialValue).toMatch(isoDateRegExp)
|
|
|
|
// The short `expire` comes from the app's configured default profile, and
|
|
// the cache is not nested inside another one, so the nested-cache "no
|
|
// explicit cacheLife" error must never fire (in production it kills the
|
|
// build; in dev it throws on the request).
|
|
expect(next.cliOutput).not.toContain(
|
|
'nested-use-cache-no-explicit-cachelife'
|
|
)
|
|
|
|
// An `expire: 0` cache is a dynamic hole. In production it regenerates on
|
|
// every request; in dev it is served warm across reloads and re-warmed in
|
|
// the background. Either way, reloads converge to a fresh value.
|
|
await retry(async () => {
|
|
await browser.refresh()
|
|
const value = await browser.elementById('value').text()
|
|
expect(value).toMatch(isoDateRegExp)
|
|
expect(value).not.toEqual(initialValue)
|
|
})
|
|
})
|
|
|
|
it('does not error when a short-lived cache is nested under a dynamic default profile', async () => {
|
|
// A short-lived inner cache nested inside an outer cache without an
|
|
// explicit `cacheLife` normally errors during prerendering. But because the
|
|
// default profile is itself dynamic, the outer cache is omitted from
|
|
// prerenders by default, so there is no silent degradation to warn about
|
|
// and the nested cache renders as a dynamic hole instead.
|
|
const browser = await next.browser('/nested')
|
|
expect(await browser.elementById('value').text()).toMatch(isoDateRegExp)
|
|
|
|
expect(next.cliOutput).not.toContain(
|
|
'nested-use-cache-no-explicit-cachelife'
|
|
)
|
|
})
|
|
})
|