Files
vercel__next.js/errors/blocking-prerender-crypto.mdx
T

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

276 lines
16 KiB
Plaintext
Raw Normal View History

---
title: Next.js encountered the unstable value crypto.randomUUID() while prerendering
kind: insight
---
docs(insights): restructure error pages and link to preview docs (#95193) ### What? Restructure the 15 Insight error pages (`errors/blocking-prerender-*.mdx`, `errors/instant-*.mdx`) so they orient the reader before diving into fixes, and point at the preview docs for the new 16.3 Instant Navigations guide and APIs. ### Why? These pages are the first thing devs see when they click the doc link from the dev overlay or the build output for a blocking-route / instant-navigation error. Previously they opened straight into the technical explanation with no signal that this error is part of the new 16.3 Instant Navigations feature, and several callouts mixed unrelated concerns (debug flags inside a top-of-page "Good to know", duplicated "Useful links" footers). ### How? - **Intro callout box** added to every page, pointing at the 16.3 launch blog post and the migration guide so newcomers have an anchor before reading the fix. - **`## Verifying the fix` section** added near the bottom, gathering the debug tips (empty-shell warning, `--debug-prerender`, `--debug-build-paths`) that previously sat in a top-of-page "Good to know" callout where they were misplaced. - **Removed** duplicated "Useful links" footers (already cross-linked in body), removed the misplaced top-of-page debug callout. - **Switched all 473 doc links** to `https://preview.nextjs.org/docs/...` so they resolve against the unpublished 16.3 docs (the live nextjs.org docs don't have the Instant Navigations guide or the route-segment-config/instant API reference yet). Split out of #95186 (Copy-as-prompt checklist) which was the same branch but a separate feature. Closes NEXT- <!-- NEXT_JS_LLM_PR -->
2026-06-26 14:12:19 +02:00
<div
style={{
padding: '1.25rem 1.5rem',
border: '1px solid var(--ds-gray-400)',
borderRadius: '12px',
background: 'var(--ds-background-200)',
margin: '1.5rem 0 2rem',
fontSize: '0.95rem',
lineHeight: '1.6',
}}
>
This Insight is part of the [Instant
Navigations](https://nextjs.org/blog/next-16-3-instant-navigations) feature
introduced in Next.js 16.3. If you're new to it, start with the [Ensuring
instant navigations](/docs/app/guides/instant-navigation) guide for an
overview of what instant navigations are and how Next.js validates them, then
come back here for the specific fix.
docs(insights): restructure error pages and link to preview docs (#95193) ### What? Restructure the 15 Insight error pages (`errors/blocking-prerender-*.mdx`, `errors/instant-*.mdx`) so they orient the reader before diving into fixes, and point at the preview docs for the new 16.3 Instant Navigations guide and APIs. ### Why? These pages are the first thing devs see when they click the doc link from the dev overlay or the build output for a blocking-route / instant-navigation error. Previously they opened straight into the technical explanation with no signal that this error is part of the new 16.3 Instant Navigations feature, and several callouts mixed unrelated concerns (debug flags inside a top-of-page "Good to know", duplicated "Useful links" footers). ### How? - **Intro callout box** added to every page, pointing at the 16.3 launch blog post and the migration guide so newcomers have an anchor before reading the fix. - **`## Verifying the fix` section** added near the bottom, gathering the debug tips (empty-shell warning, `--debug-prerender`, `--debug-build-paths`) that previously sat in a top-of-page "Good to know" callout where they were misplaced. - **Removed** duplicated "Useful links" footers (already cross-linked in body), removed the misplaced top-of-page debug callout. - **Switched all 473 doc links** to `https://preview.nextjs.org/docs/...` so they resolve against the unpublished 16.3 docs (the live nextjs.org docs don't have the Instant Navigations guide or the route-segment-config/instant API reference yet). Split out of #95186 (Copy-as-prompt checklist) which was the same branch but a separate feature. Closes NEXT- <!-- NEXT_JS_LLM_PR -->
2026-06-26 14:12:19 +02:00
</div>
During [prerendering](/docs/app/glossary#prerendering), a Server Component called a synchronous [Web Crypto](https://developer.mozilla.org/en-US/docs/Web/API/Crypto) or Node [`crypto`](https://nodejs.org/api/crypto.html) API that produces a random value ([`crypto.randomUUID()`](https://developer.mozilla.org/en-US/docs/Web/API/Crypto/randomUUID), [`crypto.getRandomValues()`](https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues), [`crypto.randomBytes()`](https://nodejs.org/api/crypto.html#cryptorandombytessize-callback), [`crypto.generateKeyPairSync()`](https://nodejs.org/api/crypto.html#cryptogeneratekeypairsynctype-options)) outside of [`<Suspense>`](https://react.dev/reference/react/Suspense). With [Cache Components](/docs/app/api-reference/config/next-config-js/cacheComponents) enabled, Next.js can't bake an unpredictable value into the prerendered HTML. The value at build time will differ from the value at runtime, so you need to choose: cache the generated value so it's stable, defer the call behind a [`<Suspense>`](https://react.dev/reference/react/Suspense) boundary so it runs per-request, or move it to the client.
Other unpredictable APIs ([`Math.random()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random), [`Date.now()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now)) have parallel error pages: see [`Math.random()`](/docs/messages/blocking-prerender-random) and [`Date.now()`](/docs/messages/blocking-prerender-current-time). The Client Component case is handled at [Crypto APIs in a Client Component](/docs/messages/blocking-prerender-crypto-client).
## Ways to fix this
<FixCardGrid>
<FixCard
group="dynamic"
title="Generate on every request"
href="#generate-on-every-request"
snippets={[
{ text: 'await connection()', highlight: true },
{ text: 'const id = crypto.randomUUID()' },
{ text: 'return <Token id={id} />' },
]}
/>
<FixCard
group="cache"
title="Cache the generated value"
href="#cache-the-generated-value"
snippets={[
{ text: 'function TokenId() {' },
{ text: ' "use cache"', highlight: true },
{ text: ' return crypto.randomUUID()' },
]}
/>
<FixCard
group="client"
title="Render on the client"
href="#render-on-the-client"
snippets={[
{ text: '"use client"', highlight: true },
{ text: '// runs in the browser' },
{ text: 'const id = crypto.randomUUID()' },
]}
/>
</FixCardGrid>
## Generate on every request
Choose this fix when each request needs a fresh token: a session ID, an OAuth state, a single-use nonce, a CSRF token. Add [`await connection()`](/docs/app/api-reference/functions/connection) before the call to tell Next.js the surrounding component is request-bound. The component is excluded from the prerender and streamed in from the nearest [`<Suspense>`](https://react.dev/reference/react/Suspense) boundary on each request.
### Patterns
#### Use `await connection()` before the crypto call
Call [`connection()`](/docs/app/api-reference/functions/connection) before the crypto API. Everything after the `await` is request-time. Wrap the component in [`<Suspense>`](https://react.dev/reference/react/Suspense) so the surrounding shell stays prerendered and only the dynamic part streams in.
Push the [`<Suspense>`](https://react.dev/reference/react/Suspense) boundary as close to the crypto call as possible. If the parent has cached content, isolate the crypto read in its own component so only that piece falls behind the boundary.
```jsx filename="app/dashboard/page.js"
import { Suspense } from 'react'
export default function Page() {
return (
<DashboardShell>
<Suspense fallback={null}>
<CsrfToken />
</Suspense>
<CachedStats />
</DashboardShell>
)
}
```
```jsx filename="app/dashboard/csrf-token.js"
import { connection } from 'next/server'
export async function CsrfToken() {
await connection()
return <input type="hidden" name="csrf" value={crypto.randomUUID()} />
}
```
docs(cache-components): clarify allow-runtime, sync-IO and instant=false, CLS fallback (#94997) ### What? Docs-only follow-ups for Cache Components and Partial Prefetching, from agent migration friction logs and Joseph's review of [#95082](https://github.com/vercel/next.js/pull/95082). - **`adopting-partial-prefetching.mdx`** — reframe the `allow-runtime` row in the audit table (it's an enhancement, not the `<Link prefetch={true}>` warning fix; the per-route opt-in is `prefetch = 'partial'`). - **`migrating-to-cache-components.mdx`** — split the `## Enable Cache Components` section into before-flag (remove `dynamic` / `revalidate` / `fetchCache`) and after-flag (translate `revalidate`, `fetchCache`, `unstable_cache`, `fetch` cache options) steps. Flag the `revalidateTag` second-argument requirement, with a Before / After example. Add two Good-to-know callouts: off-grid `revalidate` values map to the closest `cacheLife` profile; `instant = false` is allowed-to-block, not forced-dynamic. - **`blocking-prerender-current-time` / `-random` / `-crypto`** (and their `-client` siblings) — drop the misleading "Don't want this validation?" opt-out section. `instant = false` is an instant-navigation knob and doesn't suppress sync-IO prerender errors. - **Remaining `blocking-prerender-*` pages** — standardize the CLS fallback callout so they all link the canonical section. ### Why? Friction logs surfaced four recurring misreads: 1. Devs expected `prefetch = 'allow-runtime'` to silence the `<Link prefetch={true}>` warning. The docs implied it would. 2. The migration guide mixed `dynamic` removal with `unstable_cache` translation, so it wasn't clear which had to happen before the flag and which after. 3. `revalidateTag` now requires a cache profile, but the on-demand revalidation section didn't say so. 4. Sync-IO error pages still carried a "Don't want this validation?" opt-out that doesn't actually work for those errors.
2026-06-24 10:55:45 +02:00
#### Alternative: `await io()`
Use [`io()`](/docs/app/api-reference/functions/io) from `next/cache` to keep the read out of the static shell. Unlike [`connection()`](/docs/app/api-reference/functions/connection), `io()` doesn't block prefetches and works inside `"use cache"` scopes and Client Components.
docs(cache-components): clarify allow-runtime, sync-IO and instant=false, CLS fallback (#94997) ### What? Docs-only follow-ups for Cache Components and Partial Prefetching, from agent migration friction logs and Joseph's review of [#95082](https://github.com/vercel/next.js/pull/95082). - **`adopting-partial-prefetching.mdx`** — reframe the `allow-runtime` row in the audit table (it's an enhancement, not the `<Link prefetch={true}>` warning fix; the per-route opt-in is `prefetch = 'partial'`). - **`migrating-to-cache-components.mdx`** — split the `## Enable Cache Components` section into before-flag (remove `dynamic` / `revalidate` / `fetchCache`) and after-flag (translate `revalidate`, `fetchCache`, `unstable_cache`, `fetch` cache options) steps. Flag the `revalidateTag` second-argument requirement, with a Before / After example. Add two Good-to-know callouts: off-grid `revalidate` values map to the closest `cacheLife` profile; `instant = false` is allowed-to-block, not forced-dynamic. - **`blocking-prerender-current-time` / `-random` / `-crypto`** (and their `-client` siblings) — drop the misleading "Don't want this validation?" opt-out section. `instant = false` is an instant-navigation knob and doesn't suppress sync-IO prerender errors. - **Remaining `blocking-prerender-*` pages** — standardize the CLS fallback callout so they all link the canonical section. ### Why? Friction logs surfaced four recurring misreads: 1. Devs expected `prefetch = 'allow-runtime'` to silence the `<Link prefetch={true}>` warning. The docs implied it would. 2. The migration guide mixed `dynamic` removal with `unstable_cache` translation, so it wasn't clear which had to happen before the flag and which after. 3. `revalidateTag` now requires a cache profile, but the on-demand revalidation section didn't say so. 4. Sync-IO error pages still carried a "Don't want this validation?" opt-out that doesn't actually work for those errors.
2026-06-24 10:55:45 +02:00
```jsx filename="app/dashboard/csrf-token.js"
import { io } from 'next/cache'
export async function CsrfToken() {
await io()
return <input type="hidden" name="csrf" value={crypto.randomUUID()} />
}
```
Learn more: [`connection`](/docs/app/api-reference/functions/connection), [`io`](/docs/app/api-reference/functions/io), [Streaming](/docs/app/guides/streaming).
#### Switch to an async crypto API
When an async equivalent of the API exists, prefer it. Async crypto operations integrate with [`<Suspense>`](https://react.dev/reference/react/Suspense) naturally and don't need [`await connection()`](/docs/app/api-reference/functions/connection): the [`await`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await) already tells Next.js the surrounding scope is request-time.
```jsx filename="app/page.js"
import { randomBytes } from 'node:crypto'
import { promisify } from 'node:util'
import { Suspense } from 'react'
const randomBytesAsync = promisify(randomBytes)
export default async function Page() {
return (
<Suspense fallback={<TokenSkeleton />}>
<TokenDisplay />
</Suspense>
)
}
async function TokenDisplay() {
const buf = await randomBytesAsync(32)
return <code>{buf.toString('hex')}</code>
}
```
Learn more: [Node `crypto` API](https://nodejs.org/docs/latest/api/crypto.html).
### Trade-off
The route renders on every request. The shell still ships instantly because of the [`<Suspense>`](https://react.dev/reference/react/Suspense) boundary, but the dynamic region waits on the server render before it paints. When the fallback is visible UI, design it to approximate the final layout so it doesn't cause a layout shift when the content arrives. See [minimizing layout shift](/docs/app/guides/streaming#cls-cumulative-layout-shift).
### Gotchas
- Any UI rendered as part of the prerender shell must be deterministic. That includes [`<Suspense>`](https://react.dev/reference/react/Suspense) fallbacks, [`loading.js`](/docs/app/api-reference/file-conventions/loading), [`error.js`](/docs/app/api-reference/file-conventions/error), [`not-found.js`](/docs/app/api-reference/file-conventions/not-found), and [`global-error.js`](/docs/app/api-reference/file-conventions/error#global-error). Calling a crypto API in any of them raises this same error.
- For genuinely security-critical tokens (session IDs, CSRF), [Generate on every request](#generate-on-every-request) is the only correct choice. Caching a CSRF token across visitors defeats its purpose.
- This error only fires for synchronous random-producing APIs. Async crypto operations ([`crypto.subtle.digest()`](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest), [`crypto.generateKeyPair()`](https://nodejs.org/api/crypto.html#cryptogeneratekeypairtype-options-callback)) integrate with [`<Suspense>`](https://react.dev/reference/react/Suspense) naturally and don't trip the error.
## Cache the generated value
Choose this fix when the generated value is a _key into another cached operation_. The classic case is a service that requires a token: generate the token once, cache it, and let it serve as the cache key for downstream lookups. The user-visible value never changes across visitors, which is fine because the user never sees the token directly.
### Patterns
#### Cache the token alongside the query that uses it
Wrap both the token generation and the call that consumes it in the same [`use cache`](/docs/app/api-reference/directives/use-cache) function.
```jsx filename="app/page.js"
async function getCachedData() {
'use cache'
const token = crypto.randomUUID()
return db.query(token /* … */)
}
export default async function Page() {
const data = await getCachedData()
return <View data={data} />
}
```
Learn more: [Caching with `use cache`](/docs/app/api-reference/directives/use-cache).
#### Tag the cache for explicit rotation
When you want to rotate the token on a schedule or in response to an event, tag the entry with [`cacheTag`](/docs/app/api-reference/functions/cacheTag). Invalidate from a Server Action with [`updateTag`](/docs/app/api-reference/functions/updateTag) (read-your-own-writes: the next request waits for fresh data) or from a Route Handler with [`revalidateTag`](/docs/app/api-reference/functions/revalidateTag) (stale-while-revalidate).
```jsx filename="app/page.js"
import { cacheTag } from 'next/cache'
async function getApiToken() {
'use cache'
cacheTag('api-token')
return crypto.randomBytes(32).toString('hex')
}
```
Learn more: [How revalidation works](/docs/app/guides/how-revalidation-works).
### Trade-off
[PP] Surface URL data during prefetching as an Instant insight with rule page (#95365) ## What? Adds the Instant Insight + rule page for the Partial Prefetching shell validation (Janka's #95151): with `partialPrefetching` on, a route that reads `params`/`searchParams` outside `<Suspense>` blocks the extraction of the per-route **App Shell** that every link to the route shares. Surfaced as an Instant insight (not a redbox), with two fix cards: Stream (wrap in `<Suspense>`) · Allow blocking route (`instant = false`). The insight fires both at link-prefetch time and during a client-side navigation, so it covers a `<Link prefetch>` into the route as well as a plain client navigation to it. Demos: - [99-link-params-gsp](https://error-messages-overhaul-ibsl.labs.vercel.dev/scenario/99-link-params-gsp) — `<Link prefetch>` to a `generateStaticParams` route that awaits `params` unguarded. - [117-subnav-params](https://error-messages-overhaul-ibsl.labs.vercel.dev/scenario/117-subnav-params) — client-side navigation into a `[slug]` route that reads `params` outside `<Suspense>`. - [118-subnav-search-params](https://error-messages-overhaul-ibsl.labs.vercel.dev/scenario/118-subnav-search-params) — client-side navigation into a route that reads `searchParams` outside `<Suspense>`. Follow-up: #95389 documents the route-side audit in the Adopting Partial Prefetching guide (draft, to be coordinated). ## How? - Family-wide polish across all 16 insight pages while aligning the new one: consistent "Verifying the fix" wording, normalized `Learn more:` link texts (target title for guides, bare code name for API references), "App Shell" used consistently, CLS guidance reframed as "minimizing layout shift", prose cleanups (no semicolons, no code-initial sentences), and the insight-error-page skill updated to match. - New `errors/instant-shell-url-data.mdx` rule page, structured like the rest of the insight family (`blocking-prerender-client-hook`, `instant-link-prefetch-partial`); added to every page's Related Insights. New **URL data** glossary entry ("varies per link, not per session"), linked from the page. - `createLinkBodyErrorInNavigation` re-pointed to the page and classified as a navigation-time insight (`inNavigation` → Instant tab) using the shared `linkCards` fix cards. - Common insight format for the message: headline "Next.js encountered URL data outside of Suspense", consequence "may prevent the navigation from being instant, leading to a slower user experience" ("may", since boundaries or a runtime prefetch can still keep the navigation instant), and the two fixes phrased as `[stream] Provide a placeholder with <Suspense fallback={...}> around the data access` and `[block] Set export const instant = false to allow a blocking route`. The shell mechanics are explained on the docs page rather than in the headline. - Second fix reframed from "Disable validation" (group `ignore`) to **Allow blocking route** (group `block`), matching the `blocking-prerender-*` family: `instant = false` marks the segment as allowed to block rather than opting the route out of validation. - Metadata/viewport variants: only the headline term is aligned ("link data" → "URL data") plus a small grammar fix; framing, fix cards, and docs URLs stay on the existing `-metadata-runtime`/`-viewport-runtime` pages unchanged. - `errors.json` regenerated append-only (codes 1407-1409), and the docs opt-out wording updated to the new silence-free phrasing. - e2e snapshots updated for all four configs (dev/start × default/partial-prefetching); unit tests for the matcher, cards, classification, and `isInstantNavigationError`. <!-- NEXT_JS_LLM_PR -->
2026-07-08 04:04:01 +02:00
Every visitor in the cache window uses the same generated value. That's the right answer for upstream cache keys and signing keys you control, and the wrong answer for per-user identity (sessions, CSRF, nonces).
### Gotchas
- Don't cache a value that's intended as a security token for visitors. If the same "random" UUID is used as a CSRF token for every user, the protection is gone.
- [`use cache`](/docs/app/api-reference/directives/use-cache) can't combine with [`cookies()`](/docs/app/api-reference/functions/cookies) or [`headers()`](/docs/app/api-reference/functions/headers) in the same scope, so you can't key the cached value by user identity from inside the cached function.
- If you cache a function and still see this error, the [`cacheLife`](/docs/app/api-reference/functions/cacheLife) may be too short to prerender. See [Short-lived caches](#short-lived-caches).
### Short-lived caches
[`use cache`](/docs/app/api-reference/directives/use-cache) accepts a [`cacheLife`](/docs/app/api-reference/functions/cacheLife) profile. A short profile (such as `"seconds"` or `"minutes"`) whose `revalidate` is shorter than the prerender's effective lifetime prevents the value from being included in the prerender. The segment becomes a dynamic hole instead. The cache entry still helps the [Client Cache](/docs/app/glossary#client-cache) and protects upstream APIs, but the page falls back to streaming.
To keep the page prerendered, use a profile with a longer revalidate window such as `"default"` (15 minutes), `"hours"`, or `"days"`. If a short profile is intentional, treat the value as dynamic and use [Generate on every request](#generate-on-every-request) instead.
## Render on the client
Choose this fix when the generated value belongs to the client experience. A client-only correlation ID for telemetry, a draft-state key in [`localStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage), a UI nonce for a confirmation modal. Move the component into a [Client Component](/docs/app/getting-started/server-and-client-components) so the value is produced after hydration.
### Patterns
#### Move the component to the client
Add [`use client`](/docs/app/api-reference/directives/use-client) and call the crypto API inside the component.
```jsx filename="app/draft-key.js"
'use client'
import { startTransition, useEffect, useState } from 'react'
export function DraftKey() {
const [key, setKey] = useState(null)
useEffect(() => {
// Wrap in startTransition so that if any component below suspends
// during this update, React keeps the existing UI visible instead
// of flashing the nearest outer <Suspense> fallback.
startTransition(() => {
setKey(crypto.randomUUID())
})
}, [])
return <input type="hidden" value={key ?? ''} />
}
```
Learn more: [Client Components](/docs/app/getting-started/server-and-client-components#using-client-components).
### Trade-off
The first paint shows the SSR fallback (often `null`), and the value appears only after the browser hydrates the component. That's fine for client-only state but wrong for tokens that have to be in the prerendered HTML. See [Preventing flash before hydration](/docs/app/guides/preventing-flash-before-hydration) for techniques that eliminate the flash.
### Gotchas
- A Client Component that calls a crypto API inline during render still trips this error during SSR. See the dedicated [Crypto APIs in a Client Component](/docs/messages/blocking-prerender-crypto-client) page for the [`<Suspense>`](https://react.dev/reference/react/Suspense) and effect-based recipes.
- The browser only ships [Web Crypto](https://developer.mozilla.org/en-US/docs/Web/API/Crypto). Node-only APIs (`crypto.randomBytes`, `crypto.generateKeyPairSync`) are not available on the client.
docs(insights): restructure error pages and link to preview docs (#95193) ### What? Restructure the 15 Insight error pages (`errors/blocking-prerender-*.mdx`, `errors/instant-*.mdx`) so they orient the reader before diving into fixes, and point at the preview docs for the new 16.3 Instant Navigations guide and APIs. ### Why? These pages are the first thing devs see when they click the doc link from the dev overlay or the build output for a blocking-route / instant-navigation error. Previously they opened straight into the technical explanation with no signal that this error is part of the new 16.3 Instant Navigations feature, and several callouts mixed unrelated concerns (debug flags inside a top-of-page "Good to know", duplicated "Useful links" footers). ### How? - **Intro callout box** added to every page, pointing at the 16.3 launch blog post and the migration guide so newcomers have an anchor before reading the fix. - **`## Verifying the fix` section** added near the bottom, gathering the debug tips (empty-shell warning, `--debug-prerender`, `--debug-build-paths`) that previously sat in a top-of-page "Good to know" callout where they were misplaced. - **Removed** duplicated "Useful links" footers (already cross-linked in body), removed the misplaced top-of-page debug callout. - **Switched all 473 doc links** to `https://preview.nextjs.org/docs/...` so they resolve against the unpublished 16.3 docs (the live nextjs.org docs don't have the Instant Navigations guide or the route-segment-config/instant API reference yet). Split out of #95186 (Copy-as-prompt checklist) which was the same branch but a separate feature. Closes NEXT- <!-- NEXT_JS_LLM_PR -->
2026-06-26 14:12:19 +02:00
## Verifying the fix
[PP] Surface URL data during prefetching as an Instant insight with rule page (#95365) ## What? Adds the Instant Insight + rule page for the Partial Prefetching shell validation (Janka's #95151): with `partialPrefetching` on, a route that reads `params`/`searchParams` outside `<Suspense>` blocks the extraction of the per-route **App Shell** that every link to the route shares. Surfaced as an Instant insight (not a redbox), with two fix cards: Stream (wrap in `<Suspense>`) · Allow blocking route (`instant = false`). The insight fires both at link-prefetch time and during a client-side navigation, so it covers a `<Link prefetch>` into the route as well as a plain client navigation to it. Demos: - [99-link-params-gsp](https://error-messages-overhaul-ibsl.labs.vercel.dev/scenario/99-link-params-gsp) — `<Link prefetch>` to a `generateStaticParams` route that awaits `params` unguarded. - [117-subnav-params](https://error-messages-overhaul-ibsl.labs.vercel.dev/scenario/117-subnav-params) — client-side navigation into a `[slug]` route that reads `params` outside `<Suspense>`. - [118-subnav-search-params](https://error-messages-overhaul-ibsl.labs.vercel.dev/scenario/118-subnav-search-params) — client-side navigation into a route that reads `searchParams` outside `<Suspense>`. Follow-up: #95389 documents the route-side audit in the Adopting Partial Prefetching guide (draft, to be coordinated). ## How? - Family-wide polish across all 16 insight pages while aligning the new one: consistent "Verifying the fix" wording, normalized `Learn more:` link texts (target title for guides, bare code name for API references), "App Shell" used consistently, CLS guidance reframed as "minimizing layout shift", prose cleanups (no semicolons, no code-initial sentences), and the insight-error-page skill updated to match. - New `errors/instant-shell-url-data.mdx` rule page, structured like the rest of the insight family (`blocking-prerender-client-hook`, `instant-link-prefetch-partial`); added to every page's Related Insights. New **URL data** glossary entry ("varies per link, not per session"), linked from the page. - `createLinkBodyErrorInNavigation` re-pointed to the page and classified as a navigation-time insight (`inNavigation` → Instant tab) using the shared `linkCards` fix cards. - Common insight format for the message: headline "Next.js encountered URL data outside of Suspense", consequence "may prevent the navigation from being instant, leading to a slower user experience" ("may", since boundaries or a runtime prefetch can still keep the navigation instant), and the two fixes phrased as `[stream] Provide a placeholder with <Suspense fallback={...}> around the data access` and `[block] Set export const instant = false to allow a blocking route`. The shell mechanics are explained on the docs page rather than in the headline. - Second fix reframed from "Disable validation" (group `ignore`) to **Allow blocking route** (group `block`), matching the `blocking-prerender-*` family: `instant = false` marks the segment as allowed to block rather than opting the route out of validation. - Metadata/viewport variants: only the headline term is aligned ("link data" → "URL data") plus a small grammar fix; framing, fix cards, and docs URLs stay on the existing `-metadata-runtime`/`-viewport-runtime` pages unchanged. - `errors.json` regenerated append-only (codes 1407-1409), and the docs opt-out wording updated to the new silence-free phrasing. - e2e snapshots updated for all four configs (dev/start × default/partial-prefetching); unit tests for the matcher, cards, classification, and `isInstantNavigationError`. <!-- NEXT_JS_LLM_PR -->
2026-07-08 04:04:01 +02:00
After applying a fix, reload the route and confirm the page immediately paints meaningful UI, with any `<Suspense>` fallbacks covering only the regions that stream in. A [`<Suspense>`](https://react.dev/reference/react/Suspense) boundary placed around the whole page body can pass validation with an empty shell, which defeats the point of an instant navigation.
In [`next dev`](/docs/app/api-reference/cli/next#next-dev-options), the error overlay points at the failing component with file paths and line numbers. When working from a build instead, the default [`next build`](/docs/app/api-reference/cli/next#next-build-options) output is more abbreviated. Run `next build --debug-prerender` for full user-frame stack traces and `next build --debug-build-paths /dashboard /settings` to iterate on specific routes.
docs(insights): restructure error pages and link to preview docs (#95193) ### What? Restructure the 15 Insight error pages (`errors/blocking-prerender-*.mdx`, `errors/instant-*.mdx`) so they orient the reader before diving into fixes, and point at the preview docs for the new 16.3 Instant Navigations guide and APIs. ### Why? These pages are the first thing devs see when they click the doc link from the dev overlay or the build output for a blocking-route / instant-navigation error. Previously they opened straight into the technical explanation with no signal that this error is part of the new 16.3 Instant Navigations feature, and several callouts mixed unrelated concerns (debug flags inside a top-of-page "Good to know", duplicated "Useful links" footers). ### How? - **Intro callout box** added to every page, pointing at the 16.3 launch blog post and the migration guide so newcomers have an anchor before reading the fix. - **`## Verifying the fix` section** added near the bottom, gathering the debug tips (empty-shell warning, `--debug-prerender`, `--debug-build-paths`) that previously sat in a top-of-page "Good to know" callout where they were misplaced. - **Removed** duplicated "Useful links" footers (already cross-linked in body), removed the misplaced top-of-page debug callout. - **Switched all 473 doc links** to `https://preview.nextjs.org/docs/...` so they resolve against the unpublished 16.3 docs (the live nextjs.org docs don't have the Instant Navigations guide or the route-segment-config/instant API reference yet). Split out of #95186 (Copy-as-prompt checklist) which was the same branch but a separate feature. Closes NEXT- <!-- NEXT_JS_LLM_PR -->
2026-06-26 14:12:19 +02:00
## Why `instant = false` doesn't clear this error
This error fires from the prerender, not from instant-navigation validation. `crypto.randomUUID()` and related APIs return a different value on every call, so the prerender can't bake them into a static shell regardless of the segment's `instant` config or [`experimental.instantInsights.validationLevel`](/docs/app/api-reference/file-conventions/route-segment-config/instant#configuring-validation-defaults). Use one of the fixes above.
## Related Insights
- [Runtime data during prerendering](/docs/messages/blocking-prerender-runtime)
- [Uncached data during prerendering](/docs/messages/blocking-prerender-dynamic)
- [URL data in a Client Component outside of Suspense](/docs/messages/blocking-prerender-client-hook)
- [Runtime data in `generateMetadata()`](/docs/messages/blocking-prerender-metadata-runtime)
- [Uncached data in `generateMetadata()`](/docs/messages/blocking-prerender-metadata-dynamic)
- [Runtime data in `generateViewport()`](/docs/messages/blocking-prerender-viewport-runtime)
- [Uncached data in `generateViewport()`](/docs/messages/blocking-prerender-viewport-dynamic)
- [`Math.random()` while prerendering](/docs/messages/blocking-prerender-random)
- [`Math.random()` in a Client Component](/docs/messages/blocking-prerender-random-client)
- [`Date.now()` while prerendering](/docs/messages/blocking-prerender-current-time)
- [`Date.now()` in a Client Component](/docs/messages/blocking-prerender-current-time-client)
- [Crypto APIs in a Client Component](/docs/messages/blocking-prerender-crypto-client)
[PP] Surface URL data during prefetching as an Instant insight with rule page (#95365) ## What? Adds the Instant Insight + rule page for the Partial Prefetching shell validation (Janka's #95151): with `partialPrefetching` on, a route that reads `params`/`searchParams` outside `<Suspense>` blocks the extraction of the per-route **App Shell** that every link to the route shares. Surfaced as an Instant insight (not a redbox), with two fix cards: Stream (wrap in `<Suspense>`) · Allow blocking route (`instant = false`). The insight fires both at link-prefetch time and during a client-side navigation, so it covers a `<Link prefetch>` into the route as well as a plain client navigation to it. Demos: - [99-link-params-gsp](https://error-messages-overhaul-ibsl.labs.vercel.dev/scenario/99-link-params-gsp) — `<Link prefetch>` to a `generateStaticParams` route that awaits `params` unguarded. - [117-subnav-params](https://error-messages-overhaul-ibsl.labs.vercel.dev/scenario/117-subnav-params) — client-side navigation into a `[slug]` route that reads `params` outside `<Suspense>`. - [118-subnav-search-params](https://error-messages-overhaul-ibsl.labs.vercel.dev/scenario/118-subnav-search-params) — client-side navigation into a route that reads `searchParams` outside `<Suspense>`. Follow-up: #95389 documents the route-side audit in the Adopting Partial Prefetching guide (draft, to be coordinated). ## How? - Family-wide polish across all 16 insight pages while aligning the new one: consistent "Verifying the fix" wording, normalized `Learn more:` link texts (target title for guides, bare code name for API references), "App Shell" used consistently, CLS guidance reframed as "minimizing layout shift", prose cleanups (no semicolons, no code-initial sentences), and the insight-error-page skill updated to match. - New `errors/instant-shell-url-data.mdx` rule page, structured like the rest of the insight family (`blocking-prerender-client-hook`, `instant-link-prefetch-partial`); added to every page's Related Insights. New **URL data** glossary entry ("varies per link, not per session"), linked from the page. - `createLinkBodyErrorInNavigation` re-pointed to the page and classified as a navigation-time insight (`inNavigation` → Instant tab) using the shared `linkCards` fix cards. - Common insight format for the message: headline "Next.js encountered URL data outside of Suspense", consequence "may prevent the navigation from being instant, leading to a slower user experience" ("may", since boundaries or a runtime prefetch can still keep the navigation instant), and the two fixes phrased as `[stream] Provide a placeholder with <Suspense fallback={...}> around the data access` and `[block] Set export const instant = false to allow a blocking route`. The shell mechanics are explained on the docs page rather than in the headline. - Second fix reframed from "Disable validation" (group `ignore`) to **Allow blocking route** (group `block`), matching the `blocking-prerender-*` family: `instant = false` marks the segment as allowed to block rather than opting the route out of validation. - Metadata/viewport variants: only the headline term is aligned ("link data" → "URL data") plus a small grammar fix; framing, fix cards, and docs URLs stay on the existing `-metadata-runtime`/`-viewport-runtime` pages unchanged. - `errors.json` regenerated append-only (codes 1407-1409), and the docs opt-out wording updated to the new silence-free phrasing. - e2e snapshots updated for all four configs (dev/start × default/partial-prefetching); unit tests for the matcher, cards, classification, and `isInstantNavigationError`. <!-- NEXT_JS_LLM_PR -->
2026-07-08 04:04:01 +02:00
- [Dynamic data during prefetching](/docs/messages/instant-link-prefetch-partial)
- [URL data outside of Suspense](/docs/messages/instant-shell-url-data)
- [Unrendered segment](/docs/messages/instant-unrendered-segment)