next.js
7bbea9d4 - [Instant] Build-time validation (#90964)

Commit
55 days ago
[Instant] Build-time validation (#90964) Initial version of build-time instant validation. During prerendering, if we have `unstable_instant` configs, we perform a complete staged render of the page (including dynamic data). This render mocks request data (`params`/`searchParams`/`cookies`/`headers`) using the `samples` provided in `unstable_instant`. Then, we use the staged chunks to run instant validation in mostly the same way that we would in dev. Most changes in the validation flow are related to samples, which also need to be passed into the client render to mock APIs like `useParams`. ### Providing Mock data We run everything in a fresh workStore+workUnitStore to (hopefully) avoid any data from the prerender from sneaking in. This is quite involved. A big part of the trickiness is that if you do e.g. `(await cookies()).get('myCookie')` but the `samples` don't contain `myCookie`, we want the validation to error and require explicitly saying what state `myCookie` is in. If it's available, use ```ts export const unstable_instant = { // ... samples: { cookies: [{ name: "myCookie", value: "someValue" }] } } ``` If it's not available, use ```ts export const unstable_instant = { // ... samples: { cookies: [{ name: "myCookie", value: null }] } } ``` This explicitness makes it harder to e.g. unintentionally validate a logged-out state. Note that even validating a `prefetch: 'static'` page might require samples, because we need the shared parent for the navigation to be (mostly) complete. We might be able to relax this in the future, but it's not straightforward. Another issue is that a segment might access a missing sample in a "benign" way, i.e. where it wouldn't block validation (or be inside a suspense boundary). Ideally we wouldn't require providing a sample for usages like that, but currently we do. ### Instrumenting APIs to detect missing samples All request APIs are instrumented in this way. - `params` throw when accessing a property that was not declared in `samples`. same for the object from `useParams()`. `in` is allowed because we know the shape of the params object statically, based on the routing structure. - `searchParams` throw when accessing a property that was not declared in `samples` and `in` checks. - objects returned from `cookies()`, `headers()`, `useSearchPrams()` throw on `get()` and `has()`. There's a lot of tests for this behavior in `instant-validation-build`. One gap is that iteration/enumeration is currently not instrumented. We're punting on it for now and will revisit in a follow-up. ### Relation to prerender We kick off the validation after the prerender, in `renderToHTMLOrFlightImpl`. Ideally, we should kick this off somewhere higher up, separately from the prerender itself, but we're leaving that for future work. Note that if we do multiple prerenders for the same page (e.g. due to `generateStaticParams`, we'll only run validation for the first prerender for that route. `generateStaticParams` is not currently integrated into the validation at all, i.e. we don't use those params to validate, so this is safe. All params need to be provided via `samples`. This will likely change in the future, but we're not sure how yet. ### Disabling build validation Build-time instant validation is experimental, and has more potential problems to deal with than dev validation. To allow incremental rollout and testing, we allow disabling it per instant config: ```ts export const unstable_instant = { unstable_disableBuildValidation: true // ... } ``` for parity, there's also `unstable_disableBuildValidation`, but we don't expect that to see much use.
Author
Parents
Loading