When accessing search params, we only track dynamic access during static
generation. This has implications on fetch caching, because the fetch
cache relies on hints set during render that it's in a dynamic scope. In
15, this would signal that it should not attempt to cache the fetch at
all. In 14, this could impact the heuristic that bails from fetch cache
if dynamic access came before certain requests types, e.g. POSTs.
This disables tests that should not be run in a deployed environment,
because they use incompatible APIs or there's no reason to test them
outside of `next start`. Specifically disables for things like:
- Using `next.patchFile`, `next.renameFile`, etc.
- Attempting to use `next.cliOutput` to query runtime logs. When
deployed, these are only build-time logs.
[Latest Run](https://github.com/vercel/next.js/actions/runs/9483807368)
In #60645, dynamic access tracking was refactored but we erroneously
stopped tracking dynamic access in route handlers. Request proxying, as
well as tracking segment level configs (such as `export const dynamic =
'force-dynamic'`), were only enabled during static generation. This was
an unintended breaking change that consequently caused dynamic access to
not properly bail from data cache in various circumstances.
This adds some more rigorous testing for route handlers, as this seems
to be a fairly large gap in our fetch cache testing currently.
This PR is easiest to review with [whitespace
disabled](https://github.com/vercel/next.js/pull/66446/files?w=1).
This modifies the patched fetch implementation to better handle when
called within an `unstable_cache` callback. In these callbacks, it
should not throw an error related to dynamic access.
This replaces the verbose `trackDynamicFetch` instead with
`markCurrentScopeAsDynamic` which already has support for checking if
inside an unstable cache context. It also has been adjusted to be a
no-op when `export const dynamic = "force-static"`, further simplifying
the code within the patch fetch implementation.
## Background
Previously we introduced automatic caching for `fetch` based on certain
heuristics that were a bit tricky to grasp all scenarios. The scenarios
we would automatically cache were no dynamic data access before the
fetch call e.g. `headers()` or `cookies()`, the fetch call is inside of
a dynamic page e.g. `POST` method or `export const revalidate = 0` page
and the fetch is a non-`GET` request or has `Authorization` or `Cookie`
headers, or the fetch had `cache: 'no-store' | 'no-cache'` or
`revalidate: 0`.
## New Behavior
By default fetches will no longer automatically be cached. Instead they
need to be opted-in to caching via `export const fetchCache =
'default-cache' | 'force-cache',` `next: { revalidate: false or value >
0 }` or `cache: 'force-cache' | 'default-cache'`.
When the fetch call is automatically skipping the cache it won't impact
the page level ISR cacheability although if a fetch call manually
specifies `cache: 'no-store'` or `revalidate: 0` it will still bail from
the page being statically generated as it was before.
To achieve the previous behavior of automatic fetch caching all that
needs to be added is `export const fetchCache = 'default-cache'` in the
root layout(s) of your project.
<!-- Thanks for opening a PR! Your contribution is much appreciated.
To make sure your PR is handled as smoothly as possible we request that
you follow the checklist sections below.
Choose the right checklist for the change(s) that you're making:
## For Contributors
### Improving Documentation
- Run `pnpm prettier-fix` to fix formatting issues before opening the
PR.
- Read the Docs Contribution Guide to ensure your contribution follows
the docs guidelines:
https://nextjs.org/docs/community/contribution-guide
### Adding or Updating Examples
- The "examples guidelines" are followed from our contributing doc
https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md
- Make sure the linting passes by running `pnpm build && pnpm lint`. See
https://github.com/vercel/next.js/blob/canary/contributing/repository/linting.md
### Fixing a bug
- Related issues linked using `fixes #number`
- Tests added. See:
https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs
- Errors have a helpful link attached, see
https://github.com/vercel/next.js/blob/canary/contributing.md
### Adding a feature
- Implements an existing feature request or RFC. Make sure the feature
request has been accepted for implementation before opening a PR. (A
discussion must be opened, see
https://github.com/vercel/next.js/discussions/new?category=ideas)
- Related issues/discussions are linked using `fixes #number`
- e2e tests added
(https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs)
- Documentation added
- Telemetry added. In case of a feature if it's used or not.
- Errors have a helpful link attached, see
https://github.com/vercel/next.js/blob/canary/contributing.md
## For Maintainers
- Minimal description (aim for explaining to someone not on the team to
understand the PR)
- When linking to a Slack thread, you might want to share details of the
conclusion
- Link both the Linear (Fixes NEXT-xxx) and the GitHub issues
- Add review comments if necessary to explain to the reviewer the logic
behind a change
### What?
### Why?
### How?
Closes NEXT-
Fixes #
-->
### What?
When pages are rendered using partial prerendering (PPR), they can
easily trigger cases that attempt to interrupt static rendering. If a
user calls `fetch` from within a `unstable_cache` callback with `cache:
"no-store"`, we currently break out, as that fetch operation is called
within a cached callback.
### Why?
Without this change, using `unstable_cache` would have limited affect
when dealing with external code that explicitly sets `cache: "no-store"`
preventing it from utilizing the user-specified-cache.
### How?
This modifies the fetch patching so it doesn't bail static rendering due
to dynamic fetches within an `unstable_cache` callback.
Closes NEXT-3220
### What?
I submitted PR #64499 , it got merged, but it contains a mistake.
I'm terribly sorry about this!
By removing the traceparent from the cachekey, we mistakenly removed the
header from the original object.
Causing the actual request to be executed without the traceparent
header.
### Why?
Creating a cachekey should not alter the original object.
### How?
Flip the arguments for Object.assign
---------
Co-authored-by: Jeffrey <jeffrey@jeffreyzutt.nl>
Co-authored-by: JJ Kasper <jj@jjsweb.site>
This ensures we properly skip calling a fetch during build-time that has
`cache: 'no-store'` as it should only be called during runtime instead.
Fixes: https://github.com/vercel/next.js/issues/64462
Closes NEXT-3114
`fetchCache` is a more fine-grained segment level cache-control
configuration that most people shouldn't have to use. Current semantics
of `dynamic = "force-dynamic"` will still treat the fetch as cacheable
unless explicitly overriding it in the fetch, or setting a segment level
`fetchCache`.
The `dynamic` cache configuration should be seen as a "top-level"
configuration, while more fine-grained controls should inherit logical
defaults from the top-level. Otherwise this forces people to opt-into
the `fetchCache` configuration, or manually override each `fetch` call,
which isn't what you'd expect when forcing a segment to be dynamic.
This will default to not attempting to cache the fetch when
`force-dynamic` is used. As a result, I had to update one of the
`app-static` tests to use `revalidate: 0` rather than `force-dynamic`,
as the revalidate behavior is slightly different in that it won't modify
the revalidation time on a fetch if it's non-zero.
Closes NEXT-2067
## Why?
When we fetch the same cache key (URL) but add an additional tag, the
revalidation does not re-fetch correctly (the bug just uses in-memory
cache again) when deployed.
:repro: →
https://github.com/lostip/nextjs-revalidation-demo/tree/main
---------
Co-authored-by: Ethan Arrowood <ethan@arrowood.dev>
Currently we aren't detecting the draft mode case properly in
`unstable_cache` so the cache is unexpectedly being leveraged. This
ensures we bypass the cache for `unstable_cache` in draft mode the same
way we do for the fetch cache handling.
Fixes: https://github.com/vercel/next.js/issues/60445
Closes NEXT-2987
Trying to submit a server action when JS is disabled (ie, no action
header in the request, and in the "progressively enhanced" case) for a
static resource results in a 405 error when deployed to Vercel. In the
absence of an action ID header, the request content-type is used to
signal that it shouldn't try and hit the static cache. However with
multipart/form-data, this will include the boundary. This updates the
matcher to consider a boundary string.
Fixes#58814
Closes NEXT-2980
Currently when we generate payloads in app router, the order of RSC
chunks aren't deterministic even if the content stays the same. This
means that any caches that rely on etags for detecting changes in
content aren't able to reliably cache/and avoid invalidating properly.
To avoid this we can manually sort the content before generating the
etag. Eventually this can be fixed upstream in react although that is a
bigger lift so we are doing this for now to alleviate the issue.
x-ref: [slack
thread](https://vercel.slack.com/archives/C042LHPJ1NX/p1709937748240119?thread_ts=1709856182.174879&cid=C042LHPJ1NX)
Closes NEXT-2825
### What
When a route handler uses an API that opts it into dynamic rendering
(such as `no-store` on a fetch), and also specifies a `revalidate` time,
the `revalidate` time is ignored and route is treated as fully static.
### Why
`revalidate: 0` and `revalidate: false` have different semantic
meanings: `false` essentially means cache forever, whereas `0` means
it's dynamic. Since `0` is also falsey, the code we have to fallback
with a default `revalidate` value for route handlers is incorrectly not
marking the route as dynamic, and as a result, caching the route without
an expiration time.
### How
This updates the fallback handling for app routes respect a revalidation
value of `0`, so that the page can properly be marked dynamic.
### Test Explanation
This adds 2 new routes handlers: both have a revalidation time specified
& use `no-store` on a fetch, but only one of them specifies `export
const dynamic = 'force-static'`. The one that doesn't specify
`force-static` is correctly omitted from the prerender manifest. The one
that is `force-static` is correctly in the prerender manifest with the
right expiration time. An additional test case was added to verify that
this data refreshes after the specified interval.
Closes NEXT-2764
<!-- Thanks for opening a PR! Your contribution is much appreciated.
To make sure your PR is handled as smoothly as possible we request that
you follow the checklist sections below.
Choose the right checklist for the change(s) that you're making:
## For Contributors
### Improving Documentation
- Run `pnpm prettier-fix` to fix formatting issues before opening the
PR.
- Read the Docs Contribution Guide to ensure your contribution follows
the docs guidelines:
https://nextjs.org/docs/community/contribution-guide
### Adding or Updating Examples
- The "examples guidelines" are followed from our contributing doc
https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md
- Make sure the linting passes by running `pnpm build && pnpm lint`. See
https://github.com/vercel/next.js/blob/canary/contributing/repository/linting.md
### Fixing a bug
- Related issues linked using `fixes #number`
- Tests added. See:
https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs
- Errors have a helpful link attached, see
https://github.com/vercel/next.js/blob/canary/contributing.md
### Adding a feature
- Implements an existing feature request or RFC. Make sure the feature
request has been accepted for implementation before opening a PR. (A
discussion must be opened, see
https://github.com/vercel/next.js/discussions/new?category=ideas)
- Related issues/discussions are linked using `fixes #number`
- e2e tests added
(https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs)
- Documentation added
- Telemetry added. In case of a feature if it's used or not.
- Errors have a helpful link attached, see
https://github.com/vercel/next.js/blob/canary/contributing.md
## For Maintainers
- Minimal description (aim for explaining to someone not on the team to
understand the PR)
- When linking to a Slack thread, you might want to share details of the
conclusion
- Link both the Linear (Fixes NEXT-xxx) and the GitHub issues
- Add review comments if necessary to explain to the reviewer the logic
behind a change
### What?
### Why?
### How?
Closes NEXT-
Fixes #
-->
The value `undefined` can be saved to the incremental cache as
`undefined` (`JSON.stringify(undefined)`) with no errors, but when
retrieving it, we attempt to parse it as JSON using
`JSON.parse(undefined)`. This throws an error. We should instead
deserialize `undefined` as `undefined` when retrieving.
relevant discussion: #59087
---------
Co-authored-by: Sam Ko <sam@vercel.com>
## What?
#62528 caused test/e2e/app-dir/not-found/conflict-route to fail
compilation in Turbopack, this compiler error was previously already
reported by Turbopack but Next.js didn't show it, which #62528 resolved.
This PR changes the handling for the not-found handling to be consistent
between development and build, which ensures that the "special" page no
longer conflicts with app/not-found/page.js.
Closes NEXT-2617
Note: this is a reworked iteration of
https://github.com/vercel/next.js/pull/62585 which wasn't sufficient.
<!-- Thanks for opening a PR! Your contribution is much appreciated.
To make sure your PR is handled as smoothly as possible we request that
you follow the checklist sections below.
Choose the right checklist for the change(s) that you're making:
## For Contributors
### Improving Documentation
- Run `pnpm prettier-fix` to fix formatting issues before opening the
PR.
- Read the Docs Contribution Guide to ensure your contribution follows
the docs guidelines:
https://nextjs.org/docs/community/contribution-guide
### Adding or Updating Examples
- The "examples guidelines" are followed from our contributing doc
https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md
- Make sure the linting passes by running `pnpm build && pnpm lint`. See
https://github.com/vercel/next.js/blob/canary/contributing/repository/linting.md
### Fixing a bug
- Related issues linked using `fixes #number`
- Tests added. See:
https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs
- Errors have a helpful link attached, see
https://github.com/vercel/next.js/blob/canary/contributing.md
### Adding a feature
- Implements an existing feature request or RFC. Make sure the feature
request has been accepted for implementation before opening a PR. (A
discussion must be opened, see
https://github.com/vercel/next.js/discussions/new?category=ideas)
- Related issues/discussions are linked using `fixes #number`
- e2e tests added
(https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs)
- Documentation added
- Telemetry added. In case of a feature if it's used or not.
- Errors have a helpful link attached, see
https://github.com/vercel/next.js/blob/canary/contributing.md
## For Maintainers
- Minimal description (aim for explaining to someone not on the team to
understand the PR)
- When linking to a Slack thread, you might want to share details of the
conclusion
- Link both the Linear (Fixes NEXT-xxx) and the GitHub issues
- Add review comments if necessary to explain to the reviewer the logic
behind a change
### What?
### Why?
### How?
Closes NEXT-
Fixes #
-->
This ensures our dynamic routes that have the same specificity as
`_next/static/:path*` don't get matched unexpectedly when the
`_next/static` asset doesn't exist. We were holding off on making this
change explicit due to compatibility concerns but these are no longer a
concern and the unexpected matching is more of a concern.
Closes: CSM-11
Fixes: https://github.com/vercel/next.js/issues/19270
Closes NEXT-2613
This reapplies the `experimental.missingSuspenseWithCSRBailout` option
to bail out during build if there was a missing suspense boundary when
using something that bails out to client side rendering (like
`useSearchParams()`). See #57642
Closes [NEXT-1770](https://linear.app/vercel/issue/NEXT-1770)
### What?
This PR adds a new flag called
`experimental.missingSuspenseWithCSRBailout`.
### Why?
Via this PR we can break a build when calling `useSearchParams` without
wrapping it in a suspense boundary.
If no suspense boundaries are present, Next.js must avoid doing SSR and
defer the entire page's rendering to the client. This is not a great
default. Instead, we will now break the build so that you are forced to
add a boundary.
### How?
Add an experimental flag. If a `BailoutToCSRError` error is thrown and
this flag is enabled, the build should fail and log an error, instead of
showing a warning and bail the entire page to client-side rendering.
Closes NEXT-1770
---------
Co-authored-by: Balázs Orbán <info@balazsorban.com>
Co-authored-by: Wyatt Johnson <accounts+github@wyattjoh.ca>
### What?
In the previous PR (#60249), it was found that the case when the
response size is larger than 2MB is not sufficiently covered by tests
[[comment](https://github.com/vercel/next.js/pull/60249#pullrequestreview-1807140518)]
### How?
I added a few more checks.
Just checking the number of API calls is not enough - I believe it is
important to additionally verify what exactly the page received.
If I'm honest, I couldn't find the exact reason why data is loaded after
application start when using `customCacheHandler`, but without it -
during build. It seems like something in the butcher. Therefore, in this
part, I simply configured it for the current version.
---------
Co-authored-by: JJ Kasper <jj@jjsweb.site>
### Fixing a bug
### What?
Disable 2MB limit for custom incrementalCacheHandler
### Why?
The limit is necessary because `FetchCache` has a 2MB limit, but it
seems there was a miscommunication regarding the key coincidence, where
`fetchCache` is a flag indicating that the method is called from fetch,
rather than indicating that the `FetchCache` Provider is currently being
used.
We do not use Vercel, and as I understand it, we do not have the
opportunity to use this functionality.
In any case, it is more important for us to increase the limits, and in
some cases, using a file storage is even preferable.
### How?
I have created a flag that determines whether the use of `FetchCache` is
possible at least in theory - if no custom provider is passed, and
additionally configured it so that it is not an implementation of
`FetchCache` as a protection against special individuals (*like me :)*).
If everything is fine, I will write proper tests.
Also, I would like to recommend making `FileSystemCache` public (_i.e.
support it as public functionality_) so that it can be imported and
extended or simply used to fix only it.
Fixes#48324 (partially)
---------
Co-authored-by: JJ Kasper <jj@jjsweb.site>
This ensures that `export const dynamic = 'force-static'` is properly
honored when a page contains fetches with `cache: 'no-store'`, `cache:
'no-cache'` or `next: { revalidate: 0 }`.
Closes NEXT-1858
This makes some critical modifications to the app render pipeline when
PPR has been enabled for pages with segments defining:
```js
export const dynamic = "force-dynamic"
```
Importantly, it no longer modifies the revalidation time to zero for
those pages, and now falls back to the provided default revalidation
time. When static render occurs, if the page being rendered has a
segment config defining `dynamic === "force-dynamic"`, then it will
postpone at the root of the component tree. This ensures that no render
code is executed for the page, as the entirety of the tree will have
postponed. This fixes the bug where the flight prefetch wasn't generated
correctly as well.
### What?
When a layout segment forces dynamic rendering (such as with
`force-dynamic` or `revalidate: 0`), navigating to sub-pages of that
layout will attempt to re-render the layout, also resulting in side
effects re-running. This means if your layout relies on a data fetch and
you render the result of that data in the layout, it will unexpectedly
change when navigating between sub-paths, as described in #57326.
As a separate issue (but caused by the same underlying mechanism), when
using `searchParams` on a dynamic page, changes to those search params
will be erroneously ignored when navigating, as described in #57075
### Why?
As a performance optimization we generate static prefetch files for
dynamic segments ([original
PR](https://github.com/vercel/next.js/pull/54403)). This makes it so
that when prefetching is turned on, the prefetch can be served quickly
from the edge without needing to invoke unnecessarily. We're able to
eagerly serve things that can be safely prefetched. This is nice for
cases where a path has a `loading.js` that we can eagerly render while
waiting for the dynamic data to be loaded.
This causes a problem with layouts that opt into dynamic rendering: when
the page loads and a prefetch is kicked off for the sub-page, it'll load
the static prefetch, which won't be generated with the same router state
as the dynamically rendered page. This causes a mismatch between the two
trees, and when navigating within the same segment, a refetch will be
added to the router because it thinks that it's navigating to a new
layout.
This also causes issues for dynamic pages that use `searchParams`. The
static prefetch will be generated without any knowledge of search
params, and when the prefetch occurs, we still match to the prefetch
generated without search params. This will make the router think that no
change occurs, and the UI will not update to reflect the change.
### How?
There's ongoing work by @acdlite to refactor the client router.
Hopefully it will be easier to re-land this once that work is finished.
For now, I've reverted the behavior as it doesn't seem to be worth the
bugs it currently causes. I've also added tests so that when we do
re-land this behavior, we can catch these subtleties.
Fixes#57326Fixes#57075
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
### What?
I changed the behavior of fetch() when 'force-dynamic' is specified in the `dynamic` of Route Segment Config to be similar to when 'force-no-store' is specified in `fetchCache`.
### Why?
The document (https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamic) contains an explanation that when 'force-dynamic' is specified for `dynamic`, it will behave equivalently to the following:
> Setting the segment config to export const fetchCache = 'force-no-store'
I tried to correct it because it was not actually behaving this way.
### How?
When determining if `fetchCache` is 'force-no-store', I have modified the code to also check the `dynamic` setting.
Fixes#47033
Co-authored-by: Zack Tanner <1939140+ztanner@users.noreply.github.com>
This ensures when an error occurs during a revalidate in app router that
properly throw the error fully and don't store the error page in the
cache which matches the expected behavior for full route ISR as errors
are not meant to update the cache so that the last successful cache
entry can continue being served.
Fix was tested against the provided reproduction here
https://app-dir-example-ghl01cxtn-basement.vercel.app/
Fixes: https://github.com/vercel/next.js/issues/53195
This PR adds the optional `limit` parameter on String.prototype.split uses.
> If provided, splits the string at each occurrence of the specified separator, but stops when limit entries have been placed in the array. Any leftover text is not included in the array at all.
[MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/split#syntax)
While the performance gain may not be significant for small texts, it can be huge for large ones.
I made a benchmark on the following repository : https://github.com/Yovach/benchmark-nodejs
On my machine, I get the following results:
`node index.js`
> normal 1: 570.092ms
> normal 50: 2.284s
> normal 100: 3.543s
`node index-optimized.js`
> optmized 1: 644.301ms
> optmized 50: 929.39ms
> optmized 100: 1.020s
The "benchmarks" numbers are :
- "lorem-1" file contains 1 paragraph of "lorem ipsum"
- "lorem-50" file contains 50 paragraphes of "lorem ipsum"
- "lorem-100" file contains 100 paragraphes of "lorem ipsum"
This PR introduces a new API, `unstable_noStore`, which will allow users to declaratively opt out of caching anywhere during static generation in the same way that you can specify `cache: 'no-store'` on a fetch call in Next.js.
An important caveat and difference from just calling `cookies()` to opt-out of static generation is that this won't opt you out when called from within `unstable_cache` and instead defers to the cache configuration to it.
```
import {unstable_noStore as noStore} from 'next/cache';
export default async function Component() {
noStore();
const result = await db.query(...);
}
```
Reland #54403
Also modifies the implementation of #55950 to not change the prefetch behavior when there is flight router state - we should only check the entire loader tree in the static prefetch case, otherwise we're inadvertently rendering the component tree for prefetches that match the current flight router state segment. ([slack x-ref](https://vercel.slack.com/archives/C03S8ED1DKM/p1695862974745849))
This includes a few other misc fixes for static prefetch generation:
- `next start` was not serving them (which also meant tests weren't catching a few small bugs)
- the router cache key casing can differ between build and runtime, resulting in a bad cache lookup which results suspending indefinitely during navigation
- We cannot generate static prefetches for pages that opt into `searchParams`, as the prefetch response won't have the right cache key in the RSC payload
- Layouts that use headers/cookies shouldn't use a static prefetch because it can result in unexpected behavior (ie, being redirected to a login page, if the prefetch contains redirect logic for unauthed users)
Closes NEXT-1665
Closes NEXT-1643
Investigating problems this is causing where incorrect flight data is being generated (potentially not correctly bailing on non-static data) causing navigation issues.
Reverts #54403
This ensures we don't block sending the stream down when handling stale revalidates, in edge runtime we leverage the existing `waitUntil` handling and in node.js runtime we couple this into our pipe readable handling to wait to close the writable until after the promise resolves.
x-ref: https://github.com/vercel/next.js/issues/54193
### What?
Pages marked with `generateStaticParams` don't currently support server actions, and instead return a 405 Method Not Allowed, with no action being taken on the client. Additionally, pages that are marked static & use server actions are opted into dynamic rendering.
### Why?
The page that has `generateStaticParams` is marked as `isSSG` [here](ee2ec3dd1d/packages/next/src/server/base-server.ts (L1337)).
As a result, the request is short-circuited because a POST request isn't supported on static pages. Upon detecting a server action on a page marked SSG, we bypass the static cache and go straight to the lambda.
This PR introduces an experimental option to the prerender manifest that will allow for selectively bypassing the static cache
This also removes the need to bail out of static generation
Closes NEXT-1167
Closes NEXT-1453
Fixes#49408Fixes#52840Fixes#50932
Optimizes how we handle cache tags for soft tags (auto-added by Next.js)
and normal tags (added manually) and adds differentiating between
`revalidatePath('/blog/first')` and page/layout.
Soft tags are not stored across cache entry and instead auto sent along
when checking cache entries. This allows us to prevent storing
exponential amounts of tags across cache entries while still having the
relationship between them so that single path revalidation can work
properly.
x-ref: [slack
thread](https://vercel.slack.com/archives/C042LHPJ1NX/p1690586837903309)