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

40 lines
922 B
JavaScript

// @ts-check
const defaultCacheHandler =
require('next/dist/server/lib/cache-handlers/default.external').default
/**
* A cache handler whose backing store serializes entries as JSON, like a
* remote store would.
* @type {import('next/dist/server/lib/cache-handlers/types').CacheHandler}
*/
const cacheHandler = {
async get(cacheKey, softTags) {
return defaultCacheHandler.get(cacheKey, softTags)
},
async set(cacheKey, pendingEntry) {
return defaultCacheHandler.set(
cacheKey,
pendingEntry.then(({ value, ...metadata }) => ({
...JSON.parse(JSON.stringify(metadata)),
value,
}))
)
},
async refreshTags() {
return defaultCacheHandler.refreshTags()
},
async getExpiration(tags) {
return defaultCacheHandler.getExpiration(tags)
},
async updateTags(tags) {
return defaultCacheHandler.updateTags(tags)
},
}
module.exports = cacheHandler