Files
Janka Uryga 576d3a3397 [PP] Instant validation - error for unguarded static params (#94595)
Follow-up to #95151, implementing support for validating awaits of
static params (i.e. when using `generateStaticParams`)

An app shell cannot contain any link data. This poses a challenge when
we're trying to re-use the dev render for instant validation, because we
have to make a choice:
1. either we resolve static params (from `generateStaticParams`) in the
`Static` stage, and get an accurate *static HTML shell* (used for the
initial navigation), but an incorrect app shell
2. or we resolve them in the `ShellRuntime` stage and we get an accurate
App Shell, but cannot validate a static HTML shell (which would contain
static params)

We'll use order 2 in the main render whenever client-navigating to a
page with `partialPrefetching` enabled. We still need a render with
order 1 for Static Shell Validation, so we perform a second, partial
render that aborts before the dynamic stage (because only instant
navigation needs the dynamic stage). As an optimization, we can skip the
secondary render if the page doesn't have static params, because in that
case the two orders are equivalent.

If we're performing an initial load, we'll use order 1 (to reflect the
HTML shell). in this case, we'll do a full secondary render with order
2. Same as above, we can skip the secondary render if there's no static
params.

---

Where params resolve (order 1 vs order 2) is controlled by
`requestStore.needsSessionShell`. We then end up with two sets of
"validation inputs" (mainly rendered chunks) that we can feed into
static and instant validation respectively.

Also note that this PR doesn't touch `environmentName`, because that
required changing too many tests. This will be addressed in a follow-up.
2026-07-01 04:59:43 +00:00

37 lines
1.2 KiB
TypeScript

import { Suspense } from 'react'
import { setTimeout } from 'timers/promises'
async function getCachedValue(slug: string) {
'use cache'
// A slow generation, so the cold read is clearly pending at a staged-render
// boundary (which surfaces the cold cache indicator on the first load), and a
// warm reload served from the front is observably different. The slug keys
// the entry; the value itself is just a timestamp.
await setTimeout(1000)
return new Date().toISOString()
}
async function CachedValue({ slug }: { slug: string }) {
const value = await getCachedValue(slug)
return <p id="value">{value}</p>
}
// We use a distinct slug per test, so each test exercises its own cache entry and no
// cache hits are shared across tests (the first request for a slug is a genuine
// cold miss).
// NOTE: we're not using generateStaticParams because
// those resolve after the shell in `partialPrefetching`,
// and the test needs the cache to be part of the shell.
export async function PageForSlug({ slug }: { slug: string }) {
return (
<main>
<Suspense fallback={<p id="loading">Loading...</p>}>
<CachedValue slug={slug} />
</Suspense>
</main>
)
}