Files
Janka UrygaandGitHub 7bbea9d4b1 [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.
2026-03-12 14:57:38 +01:00
..
…

swc-plugin for Next.js Error Code Transformation

Overview

This plugin transforms Next.js source code by:

  • Rewriting new Error or Error to include an additional property: Object.defineProperty(new Error(...), "__NEXT_ERROR_CODE", { value: $code, enumerable: false, configurable: true }). The enumerable: false ensures the error code won't show up in console logs while still being accessible for telemetry. The configurable: true ensures the error code can be overwritten, useful for transforming errors.
  • This enables anonymous error code reporting for user feedback while keeping the message private.

Error Code Mapping

  • File Location: packages/next/errors.json
  • Structure: Append-only, increment-based mapping of error codes to messages.
  • Automation:
    • Running pnpm build automatically updates errors.json if new errors are introduced.
    • Commit Reminder: Always commit the updated errors.json file to avoid CI failures.

Build Process Details

  • Two-Pass Build:

    1. First Pass:
      • Extracts new errors and temporarily stores them in packages/next/.errors/*.json.
      • In CI, a build with new errors will fail.
    2. Second Pass:
      • Updates packages/next/errors.json and reruns the build step to confirm no further new errors.
      • Inspired by React's error mapping mechanism.
  • Concurrency Handling: The two-pass system ensures reliable ordering of inserted error codes during concurrent builds.

Modifying the Plugin

  • WASM Rebuild Required:

    • After modifying the plugin, rebuild the WASM file via pnpm build-error-code-plugin and commit it to the repository.
    • Reason: Pre-built artifacts simplify the build process (pnpm build runs without requiring cargo).
  • Testing: Run cargo test inside the crates/next-error-code-swc-plugin directory to test the plugin.