Files
5af030c959 Normalize and validate expire and revalidate values to handle Infinity and surface mistakes early (#95493)
Supersedes https://github.com/vercel/next.js/pull/93475

## Summary

There's a few fixes bundled in here.

We're fixing `Infinity` getting serialized to `null`, which caused cache
handlers and build workers to misinterpret it as "don't cache" instead
of "cache forever". We use the existing infinity sentinel for that.

We're also making the checks a bit more specific from `typeof !==
'number'` to forbidding `NaN` and `-Infinity`. The checks are applied
consistently for profiles, inline cache life, and arguments to `fetch`
and `unstable_cache`.

Now that configs are also validated, we need to make sure that we only
do `revalidate > expire` validation when both values are specified.
There's a regression test for that, which
https://github.com/vercel/next.js/pull/93475 didn't do correctly.

Also, per review below:

- Now actually using returned value for `unstable_cache`
- Added validation + infinity fix for `revalidateTag` profile
- Added validation + infinity fix in `config.experimental.staleTimes`
and `config.expireTime`
- Added deep clone for `defaultConfig.cacheLife`

## Test Plan

New e2e tests that fail before the fixes.

---------

Co-authored-by: Zack Tanner <[email protected]>
2026-07-09 02:00:38 +01:00

34 lines
831 B
TypeScript

import { Suspense } from 'react'
import { cacheLife } from 'next/cache'
// The value is keyed on a search param so that it's not part of the
// prerender, and is instead stored and read back through the JSON-backed
// cache handler at request time.
async function frozenValue(key: string) {
'use cache'
cacheLife({ stale: 300, revalidate: Infinity, expire: Infinity })
return `${key} ${crypto.randomUUID()}`
}
async function Value({
searchParams,
}: {
searchParams: Promise<{ key?: string }>
}) {
const { key = 'default' } = await searchParams
return <p id="value">{await frozenValue(key)}</p>
}
export default function Page({
searchParams,
}: {
searchParams: Promise<{ key?: string }>
}) {
return (
<Suspense fallback={<p>loading</p>}>
<Value searchParams={searchParams} />
</Suspense>
)
}