docs: move insight error pages from vercel/front to canary (#94564)

### What?

Moves 14 insight-kind error pages from
`vercel/front/apps/next-site/content/errors-extra/` into this repo's
`errors/` directory.

### Why?

`nextjs.org`'s sync pipeline already clones `errors/` from canary on
every deploy. `errors-extra/` is meant for in-flight drafts. These pages
have stabilized, so they belong with the framework code they describe.

This unblocks Docs Link Validation in #94496: cross-links from this
repo's API docs (`cookies.mdx`, `headers.mdx`, `use-params.mdx`,
`use-pathname.mdx`, `generate-metadata.mdx`, `generate-viewport.mdx`,
etc.) to `/docs/messages/blocking-prerender-*` now resolve.

### How?

Copied each `.mdx` verbatim. No content changes. Frontmatter (`kind:
insight`) routes them through the FixOption renderer. Follow-up PR in
`vercel/front` will remove the `errors-extra/` copies; until then the
override wins on slug collision but the content is byte-identical.

<!-- NEXT_JS_LLM_PR -->
This commit is contained in:
Aurora Scharff
2026-06-09 01:01:05 +02:00
committed by GitHub
parent 5c9bc5c8ec
commit 2cc99c73b5
15 changed files with 3254 additions and 0 deletions
+230
View File
@@ -0,0 +1,230 @@
---
name: insight-error-page
description: Write or audit an insight-kind error page for the Next.js dev overlay. Use when creating a new `errors/<slug>.mdx` page, auditing an existing one, or checking that a page matches the framework fix cards. Covers page structure, title alignment, FixOption cards with Copy AI prompt button, code snippets, terminology verification against canonical docs, and Vercel technical writing style.
---
# Insight Error Page — Write & Audit
Write or audit an `errors/<slug>.mdx` insight-kind page that ships from this repo to `nextjs.org` and mirrors the fix-card set in the Next.js dev overlay.
> **Terminology**: the frontmatter uses `kind: insight` but the body text calls these "errors" — never "insights". Write "this error", "error pages", "dismiss the error".
## When to use this skill
- **Write mode**: "create the error page for `next-prerender-random`", "write the sync IO docs"
- **Audit mode**: "audit the blocking-prerender-dynamic page", "check the error pages match the framework"
- Any task involving `errors/*.mdx` insight pages (frontmatter has `kind: insight`)
## Source of truth chain
Every decision traces back to one of these. When in doubt, read the source — don't guess.
| What | Source file | How to read it |
| --------------------------------------------- | --------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| Card titles, IDs, groups, snippets, link URLs | `packages/next/src/next-devtools/dev-overlay/components/instant/instant-guidance-data.ts` | Each `FixCard[]` array is one error family |
| Error headline (literal text user sees) | `packages/next/src/server/app-render/sync-io-messages.ts`, `blocking-route-messages.ts`, etc. | `createSyncIOError`, `createDynamicBodyError`, etc. — the template string is the headline |
| Existing page (content to preserve) | `errors/<slug>.mdx` in this repo | Read the full file; relocate useful content that doesn't fit fix cards into Gotchas or Other options |
| Canonical API docs (terminology) | `docs/01-app/` in this repo | Cross-check every API name, directive name, and concept against the published docs |
| Template structure | This skill file (below) | The canonical shape of the page |
| Vercel writing style | The `vercel-technical-writing` skill in `vercel/front` (not present here) | Apply end-to-end; see "Voice and style" below for the rules condensed |
## Before you start
1. **Read the framework card data** for the error family you're writing. Find the matching `FixCard[]` in `instant-guidance-data.ts`. Note every card's `id`, `title`, `group`, `link`, and `snippets`.
2. **Read the factory message** that produces the dev-overlay headline. Find `createSyncIOError`, `createSyncIOClientError`, `createDynamicBodyError`, etc. The headline template (minus the `Route "..."` prefix) becomes the page `title`.
3. **Read the existing `errors/<slug>.mdx`** if it exists. Note every pattern, code example, and caveat. You must preserve all useful content — relocate it if the new structure doesn't have a 1:1 slot for it.
4. **Read the canonical docs** for every API you'll reference: `use cache`, `cacheLife`, `cacheTag`, `connection`, `Suspense`, `useEffect`, `use client`, `generateStaticParams`, etc. Use the exact terminology from the published docs.
5. **Apply Vercel technical writing style** (active voice, sentence-case headings, no banned words). The full `vercel-technical-writing` skill lives in `vercel/front`; the condensed rules below are the minimum bar.
## Page structure (mandatory)
Every page follows this exact shape. Do not add, remove, or reorder sections.
```
---
title: <literal dev-overlay headline, no period, strip Route "..." prefix>
kind: insight
---
<1-paragraph framing: what triggered the insight, name the APIs, link to Cache Components and instant navigation>
<Cross-link to sibling pages (parallel API families + client/server counterpart)>
> **Good to know**: In [`next dev`](/docs/app/api-reference/cli/next#next-dev-options), the error overlay points at the failing component. Run `next build --debug-prerender` to get the full list of blocking routes with stack traces. When iterating on specific routes, use `next build --debug-build-paths /dashboard /settings` to rebuild only those pages.
## Ways to fix this
<FixOption /> cards — one per framework card, in framework order
## <Card 1 title>
Choose this fix when ...
### Patterns
### Trade-off
### Gotchas
(optional: ### Short-lived caches — only for cache fixes)
## <Card 2 title>
...
## <Card N title>
...
(optional: ## Other options — for useful patterns that don't map to a framework fix card but are still relevant. Examples: bridging to a different error page's fix ("Cache the value in a Server Component" on a client page, linking to the server page), upstream content from `errors/<slug>.mdx` that doesn't fit the card structure, alternative APIs that sidestep the problem entirely. Each option gets its own `###` heading with framing prose, a code snippet, and a "Learn more" link to the page that covers it in full.)
## Don't want this validation?
(canonical opt-out block — see "Don't want this validation?" rule below)
## Useful links
```
## Rules (hard requirements)
### Frontmatter
- `title` = literal dev-overlay headline, no period. Get this from the factory function in the framework (e.g. `sync-io-messages.ts`). Strip the `Route "..."` prefix.
- `kind: insight` — always present.
### Good to Know
- **Always the same canonical block** across all insight pages (the `--debug-prerender` tip). Never put page-specific content here.
- Useful page-specific tips go in Gotchas under the relevant fix section.
### `<FixOption>` cards
- One per framework card, in the same order as the framework `FixCard[]` array.
- `title` = card title from framework, **verbatim**. If it reads awkward as a heading, change the framework first — never the docs.
- `href` = `#` + the auto-slug of the title (e.g. "Generate on every request" → `#generate-on-every-request`). This must match what the heading auto-generates.
- `group` = card group from framework (`dynamic`, `cache`, `client`, `stream`, `defer`, `measure`, `block`, `render`, `silence`).
- `prompt` = AI-agent prompt. Single line, no rendered markdown. The user copies this to the clipboard via the Copy AI prompt button to paste into their agent. It is read by an agent acting on the user's behalf, so guardrail phrasing like `Confirm with the user that ...` is fine here — it tells the agent to verify intent before applying an irreversible or surprising change. Keep prompts directive and specific ("Add X. Do not do Y."). Do not add cross-fix comparisons ("If the user wants X, choose Y instead") — by the time the agent reads the prompt the user has already clicked this card.
- Children = one-sentence plain-prose summary. **No inline code**, no API names in backticks, no snippets. Save technical detail for the section body.
### `## <Fix>` sections
- Heading text = card title, verbatim. Auto-slugs to the `href` above.
- Opens with: "Choose this fix when `<condition>`."
- `### Patterns` — one `####` per meaningfully different shape of the fix. Each has:
- 12 sentences of plain-prose framing
- One short, readable `jsx filename="app/..."` snippet (complete, copy-paste-ready, no `...existing code...`)
- Optional `Learn more:` link below the snippet
- `### Trade-off` — 1 paragraph. Mandatory. Describe the trade-off **in the context of this error**, not the generic API trade-off. If the only honest trade-off is the canonical API behavior (e.g. "GSP requires a rebuild when the list changes"), keep it to one sentence and link out to the API reference. Don't repeat what the API reference page already covers.
- `### Gotchas` — bulleted list. Mandatory (at least 1 bullet).
- Optional `### Short-lived caches` subsection for cache fixes (document the 5-minute threshold).
### Code snippets
- Must be valid React. Do not show unstable APIs (random, time, crypto) inline during render in a Client Component — that causes a hydration mismatch. Defer to `useEffect` + `useState` or an event handler.
- Lazy `useState` initializers (e.g. `useState(() => someUnstableCall())`) run during SSR — warn against this in Gotchas.
- `useRef` lazy-init pattern is valid for stable IDs (initialize in a getter function, not inline). Only applicable when the value should be computed once and frozen — not when it should reflect the current moment.
- Always include `filename="app/..."` on code blocks.
- When a pattern defers rendering to after hydration (e.g. `useEffect`), the Trade-off must link to [Preventing flash before hydration](/docs/app/guides/preventing-flash-before-hydration).
### Cross-links
- Framing paragraph: link to sibling pages (client ↔ server counterpart, parallel API families).
- Gotchas: link to the `-client` page when warning about inline render in Client Components.
- Useful links: keep it short (typically 26 entries). Sibling error pages + the canonical [Ensuring instant navigations](/docs/app/guides/instant-navigation) guide + 13 API references central to the fixes but not already inline-linked throughout the body. Do not re-list every API the body mentions — those are inline-linked at first use and the section is for follow-on navigation, not an index.
- Every API reference and file convention must be inline-linked throughout, not reserved for Useful Links.
- **Cross-page pattern linking**: When a fix on one page is covered in depth on a sibling page, show only the most common pattern inline and link out to the sibling for the full set. For example, a server page's "Render on the client" fix shows one client pattern and links to the `-client` page; a client page's "Other options" section bridges to the server page's cache fix. Don't duplicate entire sections across sibling pages — keep each page lean and let the sibling be the canonical reference.
- **First-party only**: link only to `nextjs.org/docs/*`, `react.dev/*`, `developer.mozilla.org/*`, and other canonical first-party references. **Never** link to personal blogs, community write-ups, conference talks, X/Bluesky posts, GitHub gists, or any third-party source — including the page author's own blog. If a third-party post inspired a pattern, internalize the idea and write it in our own voice without citation. Sibling error pages, our own docs, and primary API specs are the only acceptable destinations.
### Terminology (verify against canonical docs)
- `use cache` directive (not `"use cache"` in prose)
- Cache Components (capitalized)
- static shell (link to `/docs/app/glossary#static-shell`)
- `unstable_instant` (not `instant`)
- `cacheLife` / `cacheTag` / `revalidateTag` / `updateTag` — use published names exactly
- `connection()` from `next/server`
- Client Component / Server Component (capitalized)
- [prerendering](/docs/app/glossary#prerendering) — always linked on first use
### Allow blocking route section (canonical pattern)
When the framework card set includes `unstable_instant = false` (group `block`), use the canonical `## Allow blocking route` section shape. All pages with this fix must match. Cross-page consistency matters — diverging from this shape produces a page that reads like an outlier.
**Intro**: One paragraph explaining what setting `unstable_instant` to `false` does and what the trade-off is. Optional second paragraph noting when this is _rarely_ the right answer (for example, on client-hook or cache fixes where a Suspense boundary is almost always feasible). Phrase the rarity directly. Do not write `Confirm with the user` in body prose — that phrasing belongs in `prompt={...}` agent strings, not in the page the user reads.
**Patterns**: For page-body errors (runtime data, uncached data, client hooks), use both `#### Opt the page out` and `#### Opt the layout out`. For viewport errors, use only `#### Opt the layout out` (viewport always lives on a layout). Each pattern has:
- 12 sentences of framing explaining when to use that scope
- A `jsx filename="app/..."` snippet showing the export
- A `Learn more:` link
After the pattern snippets, include a "Use either pattern when:" bulleted list (2 bullets: layout-shell-not-meaningful + incremental migration; phrase singular for viewport pages with one pattern) and a single-sentence "Don't use this to dismiss the error. Choose [Sibling fix A](#anchor-a) or [Sibling fix B](#anchor-b) when either is feasible." closer.
**Trade-off**: One paragraph. "Navigations to this route are not instant. The user waits for the full server render before any HTML arrives. Use this only when that latency is the deliberate cost of the route's purpose."
**Gotchas** (mandatory bullets, in this order):
- Layout opt-out exempts every route in the subtree, not only the layout itself. Audit child routes before opting a shared layout out.
- This export does not disable prerendering. The route still prerenders if it can. It only silences the instant-navigation validation error.
- Page-specific gotchas (for example, viewport pages add framework-synthesized routes gotcha) come after the two canonical bullets.
**Never** add a gotcha that says `Confirm with the user that ...` in user-facing body prose. That phrasing is agent-prompt voice and belongs in `prompt={...}` strings, not in gotchas or trade-offs the user reads on the page.
### Don't want this validation?
Every insight page ends (just before `## Useful links`) with the canonical opt-out block. It teaches the reader how to silence validation per-segment and app-wide, since instant-navigation validation runs by default in Cache Components apps. Copy verbatim:
```mdx
## Don't want this validation?
Instant-navigation validation runs by default in [Cache Components](/docs/app/api-reference/config/next-config-js/cacheComponents) apps and is what surfaces this error.
- **One segment**: add [`export const unstable_instant = false`](/docs/app/api-reference/file-conventions/route-segment-config/instant) to the page or layout file. The segment is exempted from validation.
- **Entire app**: set [`experimental.instantInsights.validationLevel`](/docs/app/api-reference/file-conventions/route-segment-config/instant#configuring-validation-defaults) to `'manual-warning'` in `next.config`. Validation runs only on segments that explicitly export `unstable_instant`.
See [Ensuring instant navigations](/docs/app/guides/instant-navigation) for the full model.
```
### Writing style
- Lead each section with the answer: "Choose this fix when ..."
- Sentence-case headings, no periods
- No em-dashes for emphasis
- No banned words: `easy`, `quick`, `simple`, `just`, `very`, `basically`, `obviously`, `utilize`, `facilitate`, `leverage`, `robust`, `seamless`, `cutting-edge`, `innovative`
- No filler: `In this guide ...`, `As mentioned above ...`, `Let's take a look at ...`, `It's worth noting ...`
- Active voice + direct address: "You wrap the component" not "the component is wrapped"
- No "Default." labels on patterns (removed during review — patterns don't have a default)
## Audit checklist
When auditing an existing page, check every item:
- [ ] `title` = literal dev-overlay headline (from factory function), no period
- [ ] `kind: insight` in frontmatter
- [ ] Good to Know = the canonical `--debug-prerender` block (no page-specific content)
- [ ] One `<FixOption>` per framework card, in framework order
- [ ] Every `<FixOption>` `title` = card title verbatim
- [ ] Every `<FixOption>` `href` = auto-slug of the heading
- [ ] Every `<FixOption>` `group` matches framework card group
- [ ] Every `<FixOption>` has a `prompt` prop (AI-agent prompt, single line)
- [ ] `<FixOption>` children: plain prose, no backticks, no inline code
- [ ] Every `## <Fix>` heading = card title verbatim
- [ ] Every fix section has `### Patterns`, `### Trade-off`, `### Gotchas`
- [ ] No "Default." labels on patterns
- [ ] No `Confirm with the user ...` phrasing in user-facing body prose (intro paragraphs, Trade-off, Gotchas). It belongs in `prompt={...}` agent strings, not on the page the reader sees.
- [ ] If the page has `## Allow blocking route`, it matches the canonical shape: patterns (page-body errors use both Opt the page out + Opt the layout out; viewport errors use Opt the layout out only), "Use either pattern when" list, "Don't use this to dismiss the error" closer, canonical 2-bullet Gotchas
- [ ] Code snippets are valid React (no inline `Math.random()` during render in Client Components)
- [ ] `useState(() => Math.random())` warned against in Gotchas
- [ ] All API references inline-linked throughout
- [ ] Sibling pages cross-linked in framing paragraph + Useful links
- [ ] Useful links section is short (typically 26 entries). Does not re-list APIs that are already inline-linked in the body.
- [ ] `## Don't want this validation?` section present, verbatim per the canonical block
- [ ] Upstream `errors/<slug>.mdx` content preserved (relocated to Gotchas or Other options if needed)
- [ ] Terminology matches canonical docs (verified, not assumed)
- [ ] Vercel technical writing style applied (no banned words, active voice, sentence-case headings)
- [ ] Framework card `link` URLs point to the correct heading auto-slugs (if not, flag as a framework follow-up)
- [ ] Short-lived caches subsection present under cache fixes (when applicable)
## File locations
- New pages: `errors/<slug>.mdx` (this repo)
- URL: `https://nextjs.org/docs/messages/<slug>`
- `nextjs.org` clones `errors/` from canary on every deploy (sync pipeline lives in `vercel/front`)
- Framework cards: `packages/next/src/next-devtools/dev-overlay/components/instant/instant-guidance-data.ts`
- Factory messages: `packages/next/src/server/app-render/sync-io-messages.ts`, `blocking-route-messages.ts`, `use-cache-messages.ts`
## Reference page
The canonical reference page is `errors/blocking-prerender-random.mdx`. When writing a new page, read it first to match the exact structure, tone, and level of detail.
+292
View File
@@ -0,0 +1,292 @@
---
title: Next.js encountered URL data in a Client Component outside of Suspense
kind: insight
---
During [prerendering](/docs/app/glossary#prerendering), a Client Component called a navigation hook ([`usePathname`](/docs/app/api-reference/functions/use-pathname), [`useParams`](/docs/app/api-reference/functions/use-params), [`useSearchParams`](/docs/app/api-reference/functions/use-search-params#prerendering), [`useSelectedLayoutSegment`](/docs/app/api-reference/functions/use-selected-layout-segment), or [`useSelectedLayoutSegments`](/docs/app/api-reference/functions/use-selected-layout-segments)) outside of a [`<Suspense>`](https://react.dev/reference/react/Suspense) boundary. With [Cache Components](/docs/app/api-reference/config/next-config-js/cacheComponents) enabled, Next.js prerenders as much of a route as possible before a request arrives. These hooks read URL data that is not available during prerendering, so the component needs a fallback to include in the [static shell](/docs/app/glossary#static-shell).
`useSearchParams` triggers this error on any prerendered route because search params come from the request URL. The other four hooks trigger it when the route has dynamic params not covered by [`generateStaticParams`](/docs/app/api-reference/functions/generate-static-params).
Server-side request-bound reads ([`cookies()`](/docs/app/api-reference/functions/cookies), [`headers()`](/docs/app/api-reference/functions/headers)) have different fixes. See [Next.js encountered runtime data during prerendering](/docs/messages/blocking-prerender-runtime). For unstable values like [`Math.random()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random) in Client Components, see [Next.js encountered the unstable value Math.random() in a Client Component](/docs/messages/blocking-prerender-random-client).
> **Good to know**: In [`next dev`](/docs/app/api-reference/cli/next#next-dev-options), the error overlay points at the failing component. Run `next build --debug-prerender` to get the full list of blocking routes with stack traces. When iterating on specific routes, use `next build --debug-build-paths /dashboard /settings` to rebuild only those pages.
## Ways to fix this
<FixOption
group="stream"
href="#wrap-in-or-move-into-suspense"
prompt={`Wrap the component that calls the navigation hook in <Suspense>. The fallback prop must render synchronous, deterministic JSX (no fetch, no awaiting, no Math.random or Date.now) that approximates the final layout. Import Suspense from "react". Do not change the hook call itself. Place the Suspense boundary as close to the hook call as possible so the rest of the route stays in the prerendered static shell.`}
title="Wrap in or move into Suspense"
>
Wrap the component that calls the hook in a Suspense boundary so Next.js can
prerender a fallback while the runtime value loads.
</FixOption>
<FixOption
group="cache"
href="#for-known-params-prerender"
prompt={`Add a generateStaticParams() export to the page or layout that defines the dynamic segment. Return an array of param objects whose keys match the segment's [param] names. On the generated paths, useParams resolves to a build-time constant, and usePathname and useSelectedLayoutSegment(s) (which derive from the URL path) also resolve without needing a Suspense boundary. Does not help useSearchParams, since search params come from the request URL's query string and are not part of segment params. Do not introduce new imports beyond Next.js types. If you can't return at least one known param at build time, use "Wrap in or move into Suspense" instead.`}
title="For known params, prerender"
>
Tell Next.js the full set of valid params ahead of time. Each one becomes a
prerendered route. Does not help useSearchParams.
</FixOption>
<FixOption
group="block"
href="#allow-blocking-route"
prompt={`Add "export const unstable_instant = false" as a top-level export in the page or layout file. This silences the warning for this segment. Confirm with the user that the route is intentionally request-time before applying this change: the export exempts the segment from instant-navigation validation, and the route renders on every request, so navigations to it block until the render completes.`}
title="Allow blocking route"
>
Opt the segment out of instant-navigation validation. The route renders on
every request.
</FixOption>
## Wrap in or move into Suspense
Choose this fix when you want the route to prerender a fallback and replace it with the hook's value at runtime.
### Patterns
#### Wrap the component that calls the hook
Move the hook call into a small Client Component and wrap it in [`<Suspense>`](https://react.dev/reference/react/Suspense). Next.js prerenders the fallback and streams the real value in when it's available.
```jsx filename="app/dashboard/page.js"
import { Suspense } from 'react'
import { Search } from './search'
export default function Page() {
return (
<main>
<h1>Dashboard</h1>
<Suspense fallback={<p>Loading search...</p>}>
<Search />
</Suspense>
</main>
)
}
```
```jsx filename="app/dashboard/search.js"
'use client'
import { useSearchParams } from 'next/navigation'
export function Search() {
const searchParams = useSearchParams()
return <p>Search: {searchParams.get('q')}</p>
}
```
Learn more: [`useSearchParams` prerendering behavior](/docs/app/api-reference/functions/use-search-params#prerendering)
#### Push the hook read down to the leaf
When the hook is read at the top of the tree but only one piece of UI depends on the value, move the read down. The parent stays prerenderable and only the leaf needs a boundary.
```jsx filename="app/layout.js"
import { Nav } from './nav'
export default function Layout({ children }) {
return (
<div>
<Nav />
{children}
</div>
)
}
```
```jsx filename="app/nav.js"
import { Suspense } from 'react'
import Link from 'next/link'
import { ActiveDot } from './active-dot'
export function Nav() {
return (
<nav>
<Link href="/dashboard">
Dashboard
<Suspense>
<ActiveDot href="/dashboard" />
</Suspense>
</Link>
<Link href="/settings">
Settings
<Suspense>
<ActiveDot href="/settings" />
</Suspense>
</Link>
</nav>
)
}
```
```jsx filename="app/active-dot.js"
'use client'
import { usePathname } from 'next/navigation'
export function ActiveDot({ href }) {
const pathname = usePathname()
const isActive = pathname === href || pathname.startsWith(`${href}/`)
return isActive ? <span aria-hidden="true"> •</span> : null
}
```
The nav links prerender into the static shell with their final `href` and label. Only the dot suspends, and its empty fallback doesn't shift the layout.
To style the parent `<Link>` itself instead (for example, bolding the active label), have the leaf set `data-active` and read it from the parent with `has-data-active:font-bold`.
Learn more: [`usePathname`](/docs/app/api-reference/functions/use-pathname), [Creating an active link component with `useSelectedLayoutSegment`](/docs/app/api-reference/functions/use-selected-layout-segment#creating-an-active-link-component)
#### Wrap a sibling that uses the hook value
When the hook value drives a non-visual concern (analytics, attribute on a parent), isolate it in a sibling component and wrap that sibling. The visible UI stays in the static shell.
```jsx filename="app/dashboard/page.js"
import { Suspense } from 'react'
import { Header } from './header'
import { TrackPageView } from './track-page-view'
export default function Page() {
return (
<>
<Header />
<Suspense>
<TrackPageView />
</Suspense>
<DashboardContent />
</>
)
}
```
```jsx filename="app/dashboard/track-page-view.js"
'use client'
import { useEffect } from 'react'
import { usePathname } from 'next/navigation'
export function TrackPageView() {
const pathname = usePathname()
useEffect(() => {
track('pageview', { pathname })
}, [pathname])
return null
}
```
The sibling renders nothing visible, so an empty fallback is correct. There is no UI to approximate, and the rest of the page stays in the static shell.
### Trade-off
The user sees the fallback on the first paint of the suspended region, then it swaps to the real value once the hook resolves after hydration. Choose a fallback shape that matches the final layout so the swap doesn't shift surrounding content.
### Gotchas
- Place the [`<Suspense>`](https://react.dev/reference/react/Suspense) boundary as close to the hook call as possible. Wrapping a large subtree forces the entire subtree into the fallback and loses prerendered content.
- The fallback must be synchronous and deterministic. Do not call [`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), [`crypto.randomUUID()`](https://developer.mozilla.org/en-US/docs/Web/API/Crypto/randomUUID), or [`fetch()`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) inside it. Each one raises a separate [Cache Components](/docs/app/api-reference/config/next-config-js/cacheComponents) error during prerendering.
- Do not pass `{children}` through in the fallback. Child pages may include dynamic reads (for example, `/_not-found` calling [`cookies()`](/docs/app/api-reference/functions/cookies) or [`headers()`](/docs/app/api-reference/functions/headers)) that propagate into what should be a static fallback. Render a placeholder that doesn't include `{children}`.
- A nav rendered in a layout suspends on any page below it that reads URL data the layout's static shell doesn't know. Push the hook read down to the smallest leaf that needs the value so the rest of the nav stays prerendered.
- The static shell does not include the active state. The fallback paints first, then the indicator hydrates and the active style appears. For active-link patterns where the flash is visible, add an [inline script that runs before paint](/docs/app/guides/preventing-flash-before-hydration) to set the active attribute from `location.pathname` so the correct link is styled on the first paint.
- [`usePathname`](/docs/app/api-reference/functions/use-pathname#avoid-hydration-mismatch-with-rewrites) reads the source path on the server when the request was rewritten in `next.config` or middleware, while the browser sees the rewritten path. The active state on a rewritten route resolves to the wrong link on the server and corrects itself on hydration. If your app uses rewrites, defer the read until after mount as the [docs recommend](/docs/app/api-reference/functions/use-pathname#avoid-hydration-mismatch-with-rewrites).
## For known params, prerender
Choose this fix when you know the valid param values at build time. Adding [`generateStaticParams`](/docs/app/api-reference/functions/generate-static-params) tells Next.js to prerender each one. On those prerendered routes, [`useParams`](/docs/app/api-reference/functions/use-params) resolves to a build-time constant, and the [`Suspense` boundary around `usePathname` is optional](/docs/app/api-reference/functions/use-pathname). This fix does not apply to [`useSearchParams`](/docs/app/api-reference/functions/use-search-params), since search params come from the request URL's query string and are not part of the segment params.
### Patterns
#### Export generateStaticParams
Export [`generateStaticParams`](/docs/app/api-reference/functions/generate-static-params) from the page or layout that defines the dynamic segment. Return an array of param objects whose keys match the segment's `[param]` names.
```jsx filename="app/blog/[slug]/page.js"
export function generateStaticParams() {
return [{ slug: 'hello-world' }, { slug: 'release-notes' }]
}
```
Learn more: [`generateStaticParams`](/docs/app/api-reference/functions/generate-static-params).
### Trade-off
The list of params is decided at build time. See [`generateStaticParams`](/docs/app/api-reference/functions/generate-static-params) for rebuild and cache-invalidation patterns when the list changes.
### Gotchas
- [`useSearchParams`](/docs/app/api-reference/functions/use-search-params) is never affected by [`generateStaticParams`](/docs/app/api-reference/functions/generate-static-params). Search params come from the request URL's query string. If a route reads `useSearchParams`, you still need a [`<Suspense>`](https://react.dev/reference/react/Suspense) boundary around that read or [Allow blocking route](#allow-blocking-route) on the segment.
- A request for a param value not in the returned list falls through to runtime rendering. The same client hook will suspend on that request unless the segment is wrapped in `<Suspense>` or marked with [Allow blocking route](#allow-blocking-route).
- The generated paths must cover every dynamic segment in the route. If any segment is missing, the route falls back to runtime rendering for that param.
- [`generateStaticParams`](/docs/app/api-reference/functions/generate-static-params) runs at build time, so it cannot depend on per-request values.
- The [`dynamicParams`](/docs/app/api-reference/file-conventions/route-segment-config/dynamicParams) config isn't available in this model. If you're migrating an existing page, use [`<Suspense>`](https://react.dev/reference/react/Suspense), [`notFound()`](/docs/app/api-reference/functions/not-found), or [Allow blocking route](#allow-blocking-route) instead.
## Allow blocking route
Choose this fix when the route renders per-request and there's no useful static shell. Setting [`unstable_instant`](/docs/app/api-reference/file-conventions/route-segment-config/instant) to `false` exempts the segment from instant-navigation validation. The page renders on every request and the navigation blocks until that render completes.
### Patterns
#### Opt the page out
Add the export to the page that triggered the error. Only that route blocks.
```jsx filename="app/dashboard/page.js"
export const unstable_instant = false
export default function Page() {
return <Dashboard />
}
```
Learn more: [Ensuring instant navigations](/docs/app/guides/instant-navigation).
#### Opt the layout out
When the shared layout itself can't ship instantly (it reads URL data of its own that has no meaningful fallback), set [`unstable_instant`](/docs/app/api-reference/file-conventions/route-segment-config/instant) to `false` on the layout. The layout and every route in the subtree are exempted from instant-navigation validation.
```jsx filename="app/dashboard/layout.js"
export const unstable_instant = false
export default function DashboardLayout({ children }) {
return <DashboardShell>{children}</DashboardShell>
}
```
Learn more: [Route segment `instant` config](/docs/app/api-reference/file-conventions/route-segment-config/instant).
Use either pattern when:
- The route needs request-time data high in the tree to decide what to render, so there is no meaningful [static shell](/docs/app/glossary#static-shell) worth showing first.
- You're migrating a route incrementally and want to defer the lifetime decision without changing how the page renders today.
For a client-hook error this is rarely the right answer. The hook reads a small piece of URL data, and a [`<Suspense>`](https://react.dev/reference/react/Suspense) boundary around that read keeps the rest of the route prerendered. Choose [Wrap in or move into Suspense](#wrap-in-or-move-into-suspense) or [For known params, prerender](#for-known-params-prerender) when either is feasible.
### Trade-off
Navigations to this route are not instant. The user waits for the full server render before any HTML arrives. Use this only when that latency is necessary for the route to function.
### Gotchas
- Setting [`unstable_instant`](/docs/app/api-reference/file-conventions/route-segment-config/instant) to `false` on a layout exempts every route in the subtree, not only the layout itself. Audit child routes before opting a shared layout out.
- This export does not disable [prerendering](/docs/app/glossary#prerendering). The route still prerenders if it can. It only silences the instant-navigation validation error.
## Don't want this validation?
Instant-navigation validation runs by default in [Cache Components](/docs/app/api-reference/config/next-config-js/cacheComponents) apps and is what surfaces this error.
- **One segment**: add [`export const unstable_instant = false`](/docs/app/api-reference/file-conventions/route-segment-config/instant) to the page or layout file. This opts out the segment itself. Child segments are still validated during client navigations.
- **Layout and its children**: add `export const unstable_instant = { unstable_disableValidation: true }` to a layout. This disables validation for that layout and every segment below it.
- **Entire app**: set [`experimental.instantInsights.validationLevel`](/docs/app/api-reference/file-conventions/route-segment-config/instant#configuring-validation-defaults) to `'manual-warning'` in `next.config`. This limits validation to segments that explicitly export `unstable_instant`.
See [Ensuring instant navigations](/docs/app/guides/instant-navigation) for the full model.
## Useful links
- [Ensuring instant navigations](/docs/app/guides/instant-navigation)
- [Preventing flash before hydration](/docs/app/guides/preventing-flash-before-hydration)
- [Next.js encountered runtime data during prerendering](/docs/messages/blocking-prerender-runtime)
- [Next.js encountered the unstable value Math.random() in a Client Component](/docs/messages/blocking-prerender-random-client)
+196
View File
@@ -0,0 +1,196 @@
---
title: Next.js encountered the unstable value crypto.randomUUID() in a Client Component
kind: insight
---
A [Client Component](/docs/app/getting-started/server-and-client-components#using-client-components) called a synchronous [Web Crypto](https://developer.mozilla.org/en-US/docs/Web/API/Crypto) 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)) inline during render, and the surrounding tree had no [`<Suspense>`](https://react.dev/reference/react/Suspense) boundary. Client Components are server-side rendered on first load, so Next.js can't bake an unpredictable value into the prerendered HTML. The SSR value won't match the value the client computes on hydration, so you need to choose: defer the value behind a [`<Suspense>`](https://react.dev/reference/react/Suspense) boundary so SSR can stream it, or move the call into [`useEffect`](https://react.dev/reference/react/useEffect) (or an event handler) so it only runs on the client.
The Server Component case is handled at [Crypto APIs during prerendering](/docs/messages/blocking-prerender-crypto). Other unpredictable client-side 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: [`Math.random()` in a Client Component](/docs/messages/blocking-prerender-random-client) and [`Date.now()` in a Client Component](/docs/messages/blocking-prerender-current-time-client).
> **Good to know**: In [`next dev`](/docs/app/api-reference/cli/next#next-dev-options), the error overlay points at the failing component. Run `next build --debug-prerender` to get the full list of blocking routes with stack traces. When iterating on specific routes, use `next build --debug-build-paths /dashboard /settings` to rebuild only those pages.
## Ways to fix this
<FixOption
group="stream"
href="#wrap-in-or-move-into-suspense"
prompt={`Wrap the Client Component that calls the crypto API in <Suspense> in its parent. The fallback prop must render synchronous, deterministic JSX that approximates the final layout. Import Suspense from "react". Do not change the crypto call.`}
title="Wrap in or move into Suspense"
>
Wrap the component in a Suspense boundary so the shell ships instantly and the
value streams in.
</FixOption>
<FixOption
group="defer"
href="#move-into-effect-or-event-handler"
prompt={`Move the crypto call out of the inline render path and into useEffect (for first-paint values) or an event handler (for interaction values). Initialize state to a deterministic value so SSR and the first hydrated render agree. Do not introduce new imports beyond "react".`}
title="Move into effect or event handler"
>
Defer the crypto call until after hydration so SSR and the browser agree on
the initial render.
</FixOption>
## Wrap in or move into Suspense
Choose this fix when the generated value is part of the rendered output and a brief fallback during SSR is acceptable. Wrap the consuming Client Component in [`<Suspense>`](https://react.dev/reference/react/Suspense) from its parent. The fallback ships in the prerendered HTML, and Next.js fills in the real component when the browser hydrates.
### Patterns
#### Wrap from a Server Component parent
Place the [`<Suspense>`](https://react.dev/reference/react/Suspense) boundary in the Server Component that renders the Client Component.
```jsx filename="app/page.js"
import { Suspense } from 'react'
import { CorrelationId } from './correlation-id'
export default function Page() {
return (
<PageShell>
<Suspense fallback={null}>
<CorrelationId />
</Suspense>
</PageShell>
)
}
```
```jsx filename="app/correlation-id.js"
'use client'
export function CorrelationId() {
return <input type="hidden" name="trace" value={crypto.randomUUID()} />
}
```
Learn more: [Streaming with Suspense](/docs/app/guides/streaming).
### Trade-off
The component shows the fallback during SSR and the first paint. For above-the-fold UI this can be visible.
### Gotchas
- Any UI rendered as part of the prerender shell must be deterministic, including [`<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. Use stable placeholder content.
- The inner Client Component still runs during SSR, behind the boundary. If you need to guarantee the value only runs in the browser, use [Move into effect or event handler](#move-into-effect-or-event-handler) instead.
- A [`<Suspense>`](https://react.dev/reference/react/Suspense) boundary only fixes the prerender/hydration mismatch, not client re-renders. If the component using the crypto API re-renders on the client (a parent state change, a context update), it produces a new value each time. To stabilize the value across re-renders, call the crypto API once in a [`useState`](https://react.dev/reference/react/useState) initializer or [`useRef`](https://react.dev/reference/react/useRef), or compute it on the server and pass it down as a prop.
## Move into effect or event handler
Choose this fix when the generated value isn't needed for the first paint. Move the crypto call into [`useEffect`](https://react.dev/reference/react/useEffect) (for first-paint-after-mount values) or an event handler (for interaction values). The initial render uses a deterministic placeholder, so SSR and hydration agree.
### Patterns
#### Use `useEffect` to initialize after mount
For client-side IDs that should appear shortly after the page loads (a draft key in [`localStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage), a UI correlation ID).
```jsx filename="app/draft.js"
'use client'
import { startTransition, useEffect, useState } from 'react'
export function Draft() {
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: [`useEffect`](https://react.dev/reference/react/useEffect).
#### Generate on user interaction
When the value is in response to a click (a new draft, a fresh nonce on submit), compute it in the event handler.
```jsx filename="app/new-draft.js"
'use client'
import { useState } from 'react'
export function NewDraft() {
const [id, setId] = useState(null)
return (
<button onClick={() => setId(crypto.randomUUID())}>
{id ? `Draft ${id.slice(0, 8)}` : 'Start a draft'}
</button>
)
}
```
#### Lazy-initialize a stable ID with `useRef`
When a component needs a stable secure ID for the lifetime of its mount (a tracking ID, a session correlation key), produce it lazily inside a [`useRef`](https://react.dev/reference/react/useRef) getter. The ref initializer runs after mount, so SSR sees `null` and the browser fills in the value. Subsequent renders read the same ref so the ID stays stable.
```jsx filename="app/workflow.js"
'use client'
import { useRef } from 'react'
function createSecureId() {
const array = new Uint8Array(16)
crypto.getRandomValues(array)
return Array.from(array, (b) => b.toString(16).padStart(2, '0')).join('')
}
function getOrCreateId(ref) {
if (!ref.current) {
ref.current = createSecureId()
}
return ref.current
}
export function Workflow({ onNext }) {
const idRef = useRef(null)
return (
<button
onClick={() => {
trackEvent(getOrCreateId(idRef), 'forward')
onNext()
}}
>
Next
</button>
)
}
```
Learn more: [`useRef`](https://react.dev/reference/react/useRef).
### Trade-off
The user sees the placeholder briefly before the real value. For interactions the wait is invisible, but for `useEffect`-based values there's a flash of the initial state. See [Preventing flash before hydration](/docs/app/guides/preventing-flash-before-hydration) for techniques that eliminate the flash.
### Gotchas
- Don't compute the value inline during render with a lazy initializer like `useState(() => crypto.randomUUID())`. The initializer still runs during SSR and triggers the error.
- If the value needs to be hydration-stable, use [Wrap in or move into Suspense](#wrap-in-or-move-into-suspense) instead.
- Only [Web Crypto](https://developer.mozilla.org/en-US/docs/Web/API/Crypto) ships to the browser. Node-only APIs (`crypto.randomBytes`, `crypto.generateKeyPairSync`) are not available on the client. If you're seeing this error for one of those, the call lives on the server: see [Crypto APIs during prerendering](/docs/messages/blocking-prerender-crypto).
- When you call `setState` from inside [`useEffect`](https://react.dev/reference/react/useEffect), wrap it in [`startTransition`](https://react.dev/reference/react/startTransition). Cascading state updates during hydration can cause an outer [`<Suspense>`](https://react.dev/reference/react/Suspense) boundary's fallback to briefly flash. `startTransition` marks the update as non-blocking so React keeps the existing UI in place while the new value resolves.
## Don't want this validation?
Instant-navigation validation runs by default in [Cache Components](/docs/app/api-reference/config/next-config-js/cacheComponents) apps and is what surfaces this error.
- **One segment**: add [`export const unstable_instant = false`](/docs/app/api-reference/file-conventions/route-segment-config/instant) to the page or layout file. This opts out the segment itself. Child segments are still validated during client navigations.
- **Layout and its children**: add `export const unstable_instant = { unstable_disableValidation: true }` to a layout. This disables validation for that layout and every segment below it.
- **Entire app**: set [`experimental.instantInsights.validationLevel`](/docs/app/api-reference/file-conventions/route-segment-config/instant#configuring-validation-defaults) to `'manual-warning'` in `next.config`. This limits validation to segments that explicitly export `unstable_instant`.
See [Ensuring instant navigations](/docs/app/guides/instant-navigation) for the full model.
## Useful links
- [Crypto APIs during prerendering](/docs/messages/blocking-prerender-crypto)
- [`Math.random()` in a Client Component](/docs/messages/blocking-prerender-random-client)
- [`Date.now()` in a Client Component](/docs/messages/blocking-prerender-current-time-client)
- [`useEffect`](https://react.dev/reference/react/useEffect)
- [Streaming with Suspense](/docs/app/guides/streaming)
- [Ensuring instant navigations](/docs/app/guides/instant-navigation)
+232
View File
@@ -0,0 +1,232 @@
---
title: Next.js encountered the unstable value crypto.randomUUID() while prerendering
kind: insight
---
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).
> **Good to know**: In [`next dev`](/docs/app/api-reference/cli/next#next-dev-options), the error overlay points at the failing component. Run `next build --debug-prerender` to get the full list of blocking routes with stack traces. When iterating on specific routes, use `next build --debug-build-paths /dashboard /settings` to rebuild only those pages.
## Ways to fix this
<FixOption
group="dynamic"
href="#generate-on-every-request"
prompt={`Add "await connection()" from "next/server" immediately before the crypto call. This marks the component as request-time, so Next.js excludes it from the prerendered HTML and streams it in from the nearest <Suspense> boundary on each request. Do not change the crypto call itself. Only change the call site once you've confirmed with the user that a fresh value on every request is the intent.`}
title="Generate on every request"
>
Mark the component as request-time so a fresh token is produced for each
visit.
</FixOption>
<FixOption
group="cache"
href="#cache-the-generated-value"
prompt={`Move the crypto call into its own function and add "use cache" as the first statement. Useful when the same generated value is reused as a key for another cached operation (talking to a database, signing a payload). Do not introduce new imports beyond "next/cache".`}
title="Cache the generated value"
>
Generate one value at build time and reuse it. Useful when the value is a key
into another cached operation.
</FixOption>
<FixOption
group="client"
href="#render-on-the-client"
prompt={`Move the component that calls the crypto API into a Client Component by adding "use client" at the top of the file. The browser produces the value, so the server never has to. If the value needs to be hydration-stable, compute it inside useEffect instead of inline during render.`}
title="Render on the client"
>
Move the call into a Client Component. The browser produces the random value.
</FixOption>
## 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()} />
}
```
Learn more: [`connection`](/docs/app/api-reference/functions/connection), [Streaming patterns and boundary placement](/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.
### 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
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; 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.
## Don't want this validation?
Instant-navigation validation runs by default in [Cache Components](/docs/app/api-reference/config/next-config-js/cacheComponents) apps and is what surfaces this error.
- **One segment**: add [`export const unstable_instant = false`](/docs/app/api-reference/file-conventions/route-segment-config/instant) to the page or layout file. This opts out the segment itself. Child segments are still validated during client navigations.
- **Layout and its children**: add `export const unstable_instant = { unstable_disableValidation: true }` to a layout. This disables validation for that layout and every segment below it.
- **Entire app**: set [`experimental.instantInsights.validationLevel`](/docs/app/api-reference/file-conventions/route-segment-config/instant#configuring-validation-defaults) to `'manual-warning'` in `next.config`. This limits validation to segments that explicitly export `unstable_instant`.
See [Ensuring instant navigations](/docs/app/guides/instant-navigation) for the full model.
## Useful links
- [Crypto APIs in a Client Component](/docs/messages/blocking-prerender-crypto-client)
- [`Math.random()` during prerendering](/docs/messages/blocking-prerender-random)
- [`Date.now()` during prerendering](/docs/messages/blocking-prerender-current-time)
- [`use cache` directive](/docs/app/api-reference/directives/use-cache)
- [`connection` function](/docs/app/api-reference/functions/connection)
- [Ensuring instant navigations](/docs/app/guides/instant-navigation)
@@ -0,0 +1,241 @@
---
title: Next.js encountered the unstable value Date.now() in a Client Component
kind: insight
---
A [Client Component](/docs/app/getting-started/server-and-client-components#using-client-components) called [`Date.now()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now), [`Date()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date), or [`new Date()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) inline during render, and the surrounding tree had no [`<Suspense>`](https://react.dev/reference/react/Suspense) boundary. Client Components are server-side rendered on first load, so Next.js can't bake "now" into the prerendered HTML. The SSR timestamp won't match the value the client computes on hydration, so you need to choose: defer the value behind a [`<Suspense>`](https://react.dev/reference/react/Suspense) boundary so SSR can stream it, or move the call into [`useEffect`](https://react.dev/reference/react/useEffect) (or an event handler) so it only runs on the client.
The Server Component case is handled at [`Date.now()` during prerendering](/docs/messages/blocking-prerender-current-time). Other unpredictable client-side APIs ([`Math.random()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random), [`crypto.randomUUID()`](https://developer.mozilla.org/en-US/docs/Web/API/Crypto/randomUUID)) have parallel error pages: [`Math.random()` in a Client Component](/docs/messages/blocking-prerender-random-client) and [Crypto APIs in a Client Component](/docs/messages/blocking-prerender-crypto-client).
> **Good to know**: In [`next dev`](/docs/app/api-reference/cli/next#next-dev-options), the error overlay points at the failing component. Run `next build --debug-prerender` to get the full list of blocking routes with stack traces. When iterating on specific routes, use `next build --debug-build-paths /dashboard /settings` to rebuild only those pages.
## Ways to fix this
<FixOption
group="stream"
href="#wrap-in-or-move-into-suspense"
prompt={`Wrap the Client Component that calls Date.now() in <Suspense> in its parent. The fallback prop must render synchronous, deterministic JSX (no Date.now or Math.random) that approximates the final layout. Import Suspense from "react". Do not change the Date.now() call.`}
title="Wrap in or move into Suspense"
>
Wrap the component in a Suspense boundary so the shell ships instantly and the
timestamp streams in.
</FixOption>
<FixOption
group="defer"
href="#move-into-effect-or-event-handler"
prompt={`Move the Date.now() call out of the inline render path and into useEffect (for first-paint values) or an event handler (for interaction values). Initialize state to a deterministic value so SSR and the first hydrated render agree. Do not introduce new imports beyond "react".`}
title="Move into effect or event handler"
>
Defer the timestamp read until after hydration so SSR and the browser agree on
the initial render.
</FixOption>
<FixOption
group="measure"
href="#for-telemetry-use-a-timing-api"
prompt={`Replace Date.now() with performance.now() if the value is used for elapsed-time measurement, instrumentation, or telemetry. performance.now() returns a high-resolution monotonic timestamp and does not interfere with prerendering. Do not change the call if the value is rendered into the UI.`}
title="For telemetry, use a timing API"
>
When the timestamp is only used for measuring durations, switch to
performance.now().
</FixOption>
## Wrap in or move into Suspense
Choose this fix when the timestamp is part of the rendered output and a brief fallback during SSR is acceptable. Wrap the consuming Client Component in [`<Suspense>`](https://react.dev/reference/react/Suspense) from its parent. The fallback ships in the prerendered HTML, and Next.js fills in the real component when the browser hydrates.
### Patterns
#### Wrap from a Server Component parent
Place the [`<Suspense>`](https://react.dev/reference/react/Suspense) boundary in the Server Component that renders the Client Component. The fallback prerenders; the inner Client Component runs in the browser.
```jsx filename="app/article.js"
import { Suspense } from 'react'
import { RelativeTime } from './relative-time'
export default function Article({ timestamp }) {
return (
<article>
<Suspense fallback={<time>…</time>}>
<RelativeTime timestamp={timestamp} />
</Suspense>
</article>
)
}
```
```jsx filename="app/relative-time.js"
'use client'
export function RelativeTime({ timestamp }) {
const now = Date.now()
return (
<time suppressHydrationWarning>{computeTimeAgo({ timestamp, now })}</time>
)
}
```
Learn more: [Streaming with Suspense](/docs/app/guides/streaming).
### Trade-off
The component shows the fallback during SSR and the first paint. For above-the-fold UI this can be visible. Pick a fallback that matches the final layout to minimize visual jump.
### Gotchas
- Any UI rendered as part of the prerender shell must be deterministic, including [`<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 [`Date.now()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now) in any of them raises this same error. Use stable placeholder content.
- The inner Client Component still runs during SSR, behind the boundary. If you need to guarantee the timestamp only runs in the browser, use [Move into effect or event handler](#move-into-effect-or-event-handler) instead.
- A [`<Suspense>`](https://react.dev/reference/react/Suspense) boundary only fixes the prerender/hydration mismatch, not client re-renders. If the component using [`Date.now()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now) re-renders on the client (a parent state change, a context update), it reads a fresh timestamp each time. To stabilize the value across re-renders, capture [`Date.now()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now) once in a [`useState`](https://react.dev/reference/react/useState) initializer or [`useRef`](https://react.dev/reference/react/useRef), or compute it on the server and pass it down as a prop.
## Move into effect or event handler
Choose this fix when the timestamp isn't needed for the first paint. Move the [`Date.now()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now) call into [`useEffect`](https://react.dev/reference/react/useEffect) (for first-paint-after-mount values) or an event handler (for interaction values). The initial render uses a deterministic placeholder, so SSR and hydration agree.
### Patterns
#### Use `useEffect` to initialize after mount
For displays that should update over time (a relative-time label, a stopwatch). Initialize state to a deterministic placeholder, then assign the real value in [`useEffect`](https://react.dev/reference/react/useEffect).
```jsx filename="app/clock.js"
'use client'
import { startTransition, useEffect, useState } from 'react'
export function Clock() {
const [now, setNow] = 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(() => {
setNow(Date.now())
})
const id = setInterval(() => {
startTransition(() => {
setNow(Date.now())
})
}, 1000)
return () => clearInterval(id)
}, [])
return <time>{now ? new Date(now).toLocaleTimeString() : '…'}</time>
}
```
Learn more: [`useEffect`](https://react.dev/reference/react/useEffect).
#### Compute on user interaction
When the timestamp is in response to a click ("mark as read", "snapshot now"), compute it in the event handler.
```jsx filename="app/snapshot.js"
'use client'
import { useState } from 'react'
export function Snapshot() {
const [taken, setTaken] = useState(null)
return (
<button onClick={() => setTaken(Date.now())}>
{taken ? `Snapshot at ${new Date(taken).toLocaleString()}` : 'Snapshot'}
</button>
)
}
```
### Trade-off
The user sees the placeholder briefly before the real timestamp. For interactions the wait is invisible, but for `useEffect`-based values there's a flash of the initial state. See [Preventing flash before hydration](/docs/app/guides/preventing-flash-before-hydration) for techniques that eliminate the flash.
### Gotchas
- Don't compute the timestamp inline during render with a lazy initializer like `useState(() => Date.now())`. The initializer still runs during SSR and triggers the error.
- If the value needs to be hydration-stable, use [Wrap in or move into Suspense](#wrap-in-or-move-into-suspense) instead.
- When you call `setState` from inside [`useEffect`](https://react.dev/reference/react/useEffect), wrap it in [`startTransition`](https://react.dev/reference/react/startTransition). Cascading state updates during hydration can cause an outer [`<Suspense>`](https://react.dev/reference/react/Suspense) boundary's fallback to briefly flash. `startTransition` marks the update as non-blocking so React keeps the existing UI in place while the new value resolves.
## For telemetry, use a timing API
Choose this fix when the timestamp isn't user-visible at all. Logging, performance instrumentation, span correlation: all measurements that need a clock but don't render anything. Switch to [`performance.now()`](https://developer.mozilla.org/en-US/docs/Web/API/Performance/now), a high-resolution monotonic timer that doesn't carry the same semantic ("the current wall-clock time") that prevents prerendering.
### Patterns
#### Replace `Date.now()` with `performance.now()`
Drop-in replacement for any elapsed-time calculation.
```jsx filename="app/timed.js"
'use client'
import { useEffect } from 'react'
export function Timed() {
useEffect(() => {
const start = performance.now()
doWork()
const elapsedMs = performance.now() - start
console.log(`doWork took ${elapsedMs}ms`)
}, [])
return null
}
```
Learn more: [`performance.now()`](https://developer.mozilla.org/en-US/docs/Web/API/Performance/now).
### Trade-off
[`performance.now()`](https://developer.mozilla.org/en-US/docs/Web/API/Performance/now) returns a high-resolution timestamp relative to time origin, not a wall-clock time. Use it only for durations.
### Gotchas
- [`performance.now()`](https://developer.mozilla.org/en-US/docs/Web/API/Performance/now) values from the server and browser can't be compared. Each environment has its own time origin.
- Don't pass a [`performance.now()`](https://developer.mozilla.org/en-US/docs/Web/API/Performance/now) value into the rendered output. It's non-deterministic between SSR and the browser.
- For absolute time in an observability tool, use `performance.timeOrigin + performance.now()` to get a wall-clock timestamp without tripping this error.
## Other options
### Cache the value in a Server Component
When the timestamp doesn't need to reflect the user's current visit and lives inside a Client Component only because of where it's rendered, lift the read into a Server Component above with [`use cache`](/docs/app/api-reference/directives/use-cache). The canonical case is a copyright year in a footer.
```jsx filename="app/layout.js"
import { cacheLife } from 'next/cache'
async function getCurrentYear() {
'use cache'
cacheLife('max')
return new Date().getFullYear()
}
export default async function Layout({ children }) {
return (
<>
<main>{children}</main>
<footer>Copyright {await getCurrentYear()}</footer>
</>
)
}
```
Learn more: [`Date.now()` during prerendering](/docs/messages/blocking-prerender-current-time).
## Don't want this validation?
Instant-navigation validation runs by default in [Cache Components](/docs/app/api-reference/config/next-config-js/cacheComponents) apps and is what surfaces this error.
- **One segment**: add [`export const unstable_instant = false`](/docs/app/api-reference/file-conventions/route-segment-config/instant) to the page or layout file. This opts out the segment itself. Child segments are still validated during client navigations.
- **Layout and its children**: add `export const unstable_instant = { unstable_disableValidation: true }` to a layout. This disables validation for that layout and every segment below it.
- **Entire app**: set [`experimental.instantInsights.validationLevel`](/docs/app/api-reference/file-conventions/route-segment-config/instant#configuring-validation-defaults) to `'manual-warning'` in `next.config`. This limits validation to segments that explicitly export `unstable_instant`.
See [Ensuring instant navigations](/docs/app/guides/instant-navigation) for the full model.
## Useful links
- [`Date.now()` during prerendering](/docs/messages/blocking-prerender-current-time)
- [`Math.random()` in a Client Component](/docs/messages/blocking-prerender-random-client)
- [Crypto APIs in a Client Component](/docs/messages/blocking-prerender-crypto-client)
- [`useEffect`](https://react.dev/reference/react/useEffect)
- [`performance.now()`](https://developer.mozilla.org/en-US/docs/Web/API/Performance/now)
- [Streaming with Suspense](/docs/app/guides/streaming)
- [Ensuring instant navigations](/docs/app/guides/instant-navigation)
+291
View File
@@ -0,0 +1,291 @@
---
title: Next.js encountered the unstable value Date.now() while prerendering
kind: insight
---
During [prerendering](/docs/app/glossary#prerendering), a Server Component called [`Date.now()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now), [`Date()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date), or [`new Date()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) 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 "now" into the prerendered HTML. The timestamp at build time will be stale at runtime, so you need to choose: cache the value (treat it as "now-ish" with a tolerated drift), defer the read 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), [`crypto.randomUUID()`](https://developer.mozilla.org/en-US/docs/Web/API/Crypto/randomUUID)) have parallel error pages: see [`Math.random()`](/docs/messages/blocking-prerender-random) and [crypto APIs](/docs/messages/blocking-prerender-crypto). The Client Component case is handled at [`Date.now()` in a Client Component](/docs/messages/blocking-prerender-current-time-client).
> **Good to know**: In [`next dev`](/docs/app/api-reference/cli/next#next-dev-options), the error overlay points at the failing component. Run `next build --debug-prerender` to get the full list of blocking routes with stack traces. When iterating on specific routes, use `next build --debug-build-paths /dashboard /settings` to rebuild only those pages.
## Ways to fix this
<FixOption
group="dynamic"
href="#generate-on-every-request"
prompt={`Add "await connection()" from "next/server" immediately before the Date.now() call. This marks the component as request-time, so Next.js excludes it from the prerendered HTML and streams it in from the nearest <Suspense> boundary on each request. Do not change the call site of Date.now() itself. Only change the call site once you've confirmed with the user that a fresh value on every request is the intent.`}
title="Generate on every request"
>
Mark the component as request-time so the timestamp is recomputed each time
the user visits.
</FixOption>
<FixOption
group="cache"
href="#cache-the-timestamp"
prompt={`Move the Date.now() call into its own function and add "use cache" as the first statement. Optionally call cacheLife(profile) to control how often the timestamp is regenerated. Do not introduce new imports beyond "next/cache".`}
title="Cache the timestamp"
>
Capture one timestamp per cache window and reuse it. The route stays
prerendered.
</FixOption>
<FixOption
group="client"
href="#render-on-the-client"
prompt={`Move the component that calls Date.now() into a Client Component by adding "use client" at the top of the file. If the value needs to be hydration-stable, compute it inside useEffect instead of inline during render.`}
title="Render on the client"
>
Move the call into a Client Component. The browser produces the timestamp on
every visit.
</FixOption>
<FixOption
group="measure"
href="#for-telemetry-use-a-timing-api"
prompt={`Replace Date.now() with performance.now() if the value is used for elapsed-time measurement, instrumentation, or telemetry. performance.now() returns a high-resolution monotonic timestamp and does not interfere with prerendering. Do not change the call if the value is rendered into the UI.`}
title="For telemetry, use a timing API"
>
When the timestamp is only used for measuring durations, switch to
performance.now().
</FixOption>
## Generate on every request
Choose this fix when the user needs to see the current time. A "last updated at" banner, a server-issued timestamp on a transaction, a "happy new year" banner that flips at midnight. 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 timestamp read
Call [`connection()`](/docs/app/api-reference/functions/connection) before the [`Date.now()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now) or [`new Date()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) call. 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 timestamp read as possible. If the parent has cached content (metrics, headers, navigation), isolate the timestamp 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={<UpdatedAtSkeleton />}>
<UpdatedAt />
</Suspense>
<CachedMetrics />
</DashboardShell>
)
}
```
```jsx filename="app/dashboard/updated-at.js"
import { connection } from 'next/server'
export async function UpdatedAt() {
await connection()
return <small>Updated at {new Date().toLocaleString()}</small>
}
```
Learn more: [Streaming patterns and boundary placement](/docs/app/guides/streaming).
### 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.
### 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 [`Date.now()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now) in any of them raises this same error.
- If you're showing a relative time ("3 minutes ago"), the relative formatting belongs on the client so it can update without a re-render. See [Render on the client](#render-on-the-client).
## Cache the timestamp
Choose this fix when a stale timestamp is acceptable for the cache window. A "last refreshed" footer on a daily report, an "as of" banner on an hourly chart. Move the [`Date.now()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now) call into a function with [`use cache`](/docs/app/api-reference/directives/use-cache). Next.js evaluates the function once per cache key and reuses the result.
### Patterns
#### Cache the producer function
Wrap the timestamp read in a function with [`use cache`](/docs/app/api-reference/directives/use-cache). The returned value is part of the cache entry, so every consumer sees the same timestamp until the cache is invalidated.
```jsx filename="app/page.js"
async function getRenderedAt() {
'use cache'
return Date.now()
}
export default async function Page() {
const renderedAt = await getRenderedAt()
return <ReportFooter renderedAt={renderedAt} />
}
```
Learn more: [Caching with `use cache`](/docs/app/api-reference/directives/use-cache).
#### Cache the timestamp alongside the data it relates to
When the timestamp captures "when this cached content was produced" (a "last refresh" footer, a generation timestamp), include the [`Date.now()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now) read inside the same cached function that produces the data. The timestamp and the data are part of the same cache entry, so they refresh together.
```jsx filename="app/dashboard.js"
async function InformationTable() {
'use cache'
const data = await fetch('https://api.example.com/info')
return (
<>
<table>{renderData(await data.json())}</table>
<small>Last refresh: {new Date().toString()}</small>
</>
)
}
```
#### Cache stable annotations like the copyright year
For values like the current year used in a copyright footer, the cached read effectively never changes (until the next year), so it can live in a `cacheLife('max')` entry.
```jsx filename="app/layout.js"
import { cacheLife } from 'next/cache'
async function getCurrentYear() {
'use cache'
cacheLife('max')
return new Date().getFullYear()
}
export default async function Layout({ children }) {
return (
<>
<main>{children}</main>
<footer>Copyright {await getCurrentYear()}</footer>
</>
)
}
```
#### Set the rotation window with `cacheLife`
When you want the timestamp to refresh on a schedule, set a [`cacheLife`](/docs/app/api-reference/functions/cacheLife) profile.
```jsx filename="app/page.js"
import { cacheLife } from 'next/cache'
async function getHourlyTimestamp() {
'use cache'
cacheLife('hours')
return Date.now()
}
```
Learn more: [How to configure cache lifetimes](/docs/app/api-reference/functions/cacheLife).
### Trade-off
Every visitor in the cache window sees the same timestamp. That's fine for "as of" labels on cached data, wrong for "right now" labels.
### Gotchas
- A cached timestamp is the time of the most recent cache miss, not the time of the current visit. If users expect the displayed time to match their visit, use [Generate on every request](#generate-on-every-request) instead.
- 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 timestamp belongs to the user's local clock or needs to update while they're on the page. A relative time label that ticks ("3 seconds ago"), a localized time display, a stopwatch. 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 compute the timestamp inside the component.
```jsx filename="app/now.js"
'use client'
import { startTransition, useEffect, useState } from 'react'
export function Now() {
const [now, setNow] = 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(() => {
setNow(Date.now())
})
const id = setInterval(() => {
startTransition(() => {
setNow(Date.now())
})
}, 1000)
return () => clearInterval(id)
}, [])
return <time>{now ? new Date(now).toLocaleString() : '…'}</time>
}
```
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` or an em-dash), and the timestamp appears only after the browser hydrates the component. That's fine for time-of-day displays but wrong for timestamps 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 [`Date.now()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now) inline during render still trips this error during SSR. See the dedicated [`Date.now()` in a Client Component](/docs/messages/blocking-prerender-current-time-client) page for the [`<Suspense>`](https://react.dev/reference/react/Suspense) and effect-based recipes.
## For telemetry, use a timing API
Choose this fix when the timestamp isn't user-visible at all. Logging, performance instrumentation, span correlation: all measurements that need a clock but don't render anything. Switch to [`performance.now()`](https://developer.mozilla.org/en-US/docs/Web/API/Performance/now), a high-resolution monotonic timer that doesn't carry the same semantic ("the current wall-clock time") that prevents prerendering.
### Patterns
#### Replace `Date.now()` with `performance.now()`
Drop-in replacement for any elapsed-time calculation.
```jsx filename="app/page.js"
export default async function Page() {
const start = performance.now()
const data = await computeReport()
const elapsedMs = performance.now() - start
console.log(`computeReport took ${elapsedMs}ms`)
return <Report data={data} />
}
```
Learn more: [`performance.now()`](https://developer.mozilla.org/en-US/docs/Web/API/Performance/now).
### Trade-off
[`performance.now()`](https://developer.mozilla.org/en-US/docs/Web/API/Performance/now) returns a high-resolution timestamp relative to time origin, not a wall-clock time. It's the wrong tool if you actually need to know "what is today's date". Use it only for durations.
### Gotchas
- [`performance.now()`](https://developer.mozilla.org/en-US/docs/Web/API/Performance/now) values from the server can't be compared to values from the browser. Each environment has its own time origin.
- Don't pass a [`performance.now()`](https://developer.mozilla.org/en-US/docs/Web/API/Performance/now) value into the rendered output or into a cached function. The high-resolution timestamp is non-deterministic and shouldn't influence what's prerendered.
- For absolute time in an observability tool, use `performance.timeOrigin + performance.now()` to get a wall-clock timestamp without tripping this error.
## Don't want this validation?
Instant-navigation validation runs by default in [Cache Components](/docs/app/api-reference/config/next-config-js/cacheComponents) apps and is what surfaces this error.
- **One segment**: add [`export const unstable_instant = false`](/docs/app/api-reference/file-conventions/route-segment-config/instant) to the page or layout file. This opts out the segment itself. Child segments are still validated during client navigations.
- **Layout and its children**: add `export const unstable_instant = { unstable_disableValidation: true }` to a layout. This disables validation for that layout and every segment below it.
- **Entire app**: set [`experimental.instantInsights.validationLevel`](/docs/app/api-reference/file-conventions/route-segment-config/instant#configuring-validation-defaults) to `'manual-warning'` in `next.config`. This limits validation to segments that explicitly export `unstable_instant`.
See [Ensuring instant navigations](/docs/app/guides/instant-navigation) for the full model.
## Useful links
- [`Date.now()` in a Client Component](/docs/messages/blocking-prerender-current-time-client)
- [`Math.random()` during prerendering](/docs/messages/blocking-prerender-random)
- [Crypto APIs during prerendering](/docs/messages/blocking-prerender-crypto)
- [`use cache` directive](/docs/app/api-reference/directives/use-cache)
- [`connection` function](/docs/app/api-reference/functions/connection)
- [`performance.now()`](https://developer.mozilla.org/en-US/docs/Web/API/Performance/now)
- [Ensuring instant navigations](/docs/app/guides/instant-navigation)
+316
View File
@@ -0,0 +1,316 @@
---
title: Next.js encountered uncached data during prerendering or a navigation
kind: insight
---
During [prerendering](/docs/app/glossary#prerendering), a [`fetch()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/fetch) request, database call, [`await connection()`](/docs/app/api-reference/functions/connection), or other asynchronous IO ran 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 prerender the part of the tree that depends on this data, so navigations to this route block instead of being [instant](/docs/app/guides/instant-navigation).
Request-bound reads ([`cookies()`](/docs/app/api-reference/functions/cookies), [`headers()`](/docs/app/api-reference/functions/headers), [`params`](/docs/app/api-reference/file-conventions/page#params-optional), [`searchParams`](/docs/app/api-reference/file-conventions/page#searchparams-optional)) have different fixes. See [Next.js encountered runtime data during prerendering](/docs/messages/blocking-prerender-runtime).
This error can also appear during a client-side navigation when the data access sits inside a [`<Suspense>`](https://react.dev/reference/react/Suspense) boundary from a parent layout but that boundary is too high. It wraps the entire segment instead of only the dynamic part, so the navigation still blocks. Push the boundary closer to the data access so the rest of the segment stays in the [static shell](/docs/app/glossary#static-shell). See [Choosing where to place the boundary](#choosing-where-to-place-the-boundary).
> **Good to know**: In [`next dev`](/docs/app/api-reference/cli/next#next-dev-options), the error overlay points at the failing component. Run `next build --debug-prerender` to get the full list of blocking routes with stack traces. When iterating on specific routes, use `next build --debug-build-paths /dashboard /settings` to rebuild only those pages.
## Ways to fix this
<FixOption
group="cache"
href="#cache-the-component-or-data"
prompt={`Convert the highlighted data access into a cached function. Put "use cache" as the first statement of the function body. If the value depends on input that changes between calls, accept the input as a function argument so it becomes part of the cache key. Optionally call cacheTag(tag) so the entry can be invalidated on-demand from a Server Action via updateTag(tag), or from a Route Handler via revalidateTag(tag, "max") for stale-while-revalidate semantics. Optionally call cacheLife(profile) to control how long the cache lives before background revalidation or full expiration. Do not move the call site. Do not introduce new imports beyond "next/cache".`}
title="Cache the component or data"
>
Move the data access into a cached function so the result is reused and the
route stays prerenderable.
</FixOption>
<FixOption
group="stream"
href="#wrap-in-or-move-into-suspense"
prompt={`Wrap the component that performs the failing data access in <Suspense>. The fallback prop must render synchronous, deterministic JSX (no fetch, no awaiting, no Math.random or Date.now) that approximates the final layout (skeleton, spinner, or stable placeholder text). Import Suspense from "react". Do not change the data fetching logic. If the surrounding parent component already has cached content, place the Suspense boundary as close to the data access as possible so the cached content remains in the static shell.`}
title="Wrap in or move into Suspense"
>
Wrap the data-accessing component in a Suspense boundary. The shell ships
instantly and the data streams in.
</FixOption>
<FixOption
group="block"
href="#allow-blocking-route"
prompt={`Add "export const unstable_instant = false" as a top-level export in the page or layout file. This silences the warning for this segment. Confirm with the user that the route is intentionally request-time before applying this change: the export exempts the segment from instant-navigation validation, and the route renders on every request, so navigations to it block until the render completes.`}
title="Allow blocking route"
>
Opt this segment out of instant navigation. The route has no static shell and
every navigation blocks until the render completes.
</FixOption>
## Cache the component or data
Choose this fix when the data does not need to be regenerated on every request. Move the call into a function and add the [`use cache`](/docs/app/api-reference/directives/use-cache) directive as the first statement of the function body. The function still runs the underlying query, but Next.js caches the result for the configured lifetime and the surrounding route becomes prerenderable.
### Patterns
#### Cache the data-access function
Move the [`fetch()`](/docs/app/getting-started/fetching-data) or database call into its own function and mark that function with [`use cache`](/docs/app/api-reference/directives/use-cache). Arguments to the function and closed-over variables become part of the cache key, so prefer passing the values you depend on as arguments to make the contract explicit.
```jsx filename="app/dashboard/page.js"
async function getRecentTransactions(limit) {
'use cache'
return db.transactions.findMany({
orderBy: { createdAt: 'desc' },
take: limit,
})
}
export default async function Page() {
const transactions = await getRecentTransactions(10)
return <TransactionList transactions={transactions} />
}
```
Learn more: [Fetching data in the App Router](/docs/app/getting-started/fetching-data).
#### Cache the whole component
When the component does nothing but read data and render it, mark the [component itself](/docs/app/api-reference/directives/use-cache#caching-a-components-output-with-use-cache) with `use cache`. Next.js caches the rendered JSX, which is cheaper to reuse than recomputing it from the cached data.
```jsx filename="app/dashboard/transaction-list.js"
export async function TransactionList({ limit }) {
'use cache'
const transactions = await db.transactions.findMany({ take: limit })
return (
<ul>
{transactions.map((transaction) => (
<li key={transaction.id}>{transaction.description}</li>
))}
</ul>
)
}
```
Learn more: [Caching with `use cache`](/docs/app/api-reference/directives/use-cache#caching-a-components-output-with-use-cache).
#### Tag the cache for targeted invalidation
Choose this when you want control over when the cached value is refreshed. Tag the entry with [`cacheTag`](/docs/app/api-reference/functions/cacheTag) and invalidate it on demand: call [`updateTag`](/docs/app/api-reference/functions/updateTag) from a [Server Action](/docs/app/getting-started/mutating-data) when the user performed the mutation and should see fresh data on the next request, or [`revalidateTag`](/docs/app/api-reference/functions/revalidateTag) from a route handler, cron, admin tool, or incoming webhook for stale-while-revalidate refreshes. Tags add an on-demand invalidation path on top of the [`cacheLife`](/docs/app/api-reference/functions/cacheLife) expiration window; the two are independent.
```jsx filename="app/dashboard/page.js"
import { cacheTag } from 'next/cache'
async function getRecentTransactions() {
'use cache'
cacheTag('dashboard-transactions')
return db.transactions.findMany({ take: 10 })
}
```
Learn more: [How revalidation works](/docs/app/guides/how-revalidation-works).
#### Set an explicit `cacheLife` profile
When the data has a natural shelf-life (hourly metrics, daily aggregates), pick a [`cacheLife`](/docs/app/api-reference/functions/cacheLife) profile that matches. Without a profile, Next.js uses the project default.
```jsx filename="app/dashboard/page.js"
import { cacheLife } from 'next/cache'
async function getDashboard() {
'use cache'
cacheLife('hours')
return db.metrics.summary()
}
```
Learn more: [How to configure cache lifetimes](/docs/app/api-reference/functions/cacheLife).
### Trade-off
Freshness becomes a property of the cache configuration, not the data source. The cached response is reused until [`cacheLife`](/docs/app/api-reference/functions/cacheLife) revalidates or expires, or until [`cacheTag`](/docs/app/api-reference/functions/cacheTag) is invalidated. Plan invalidations alongside the code that mutates the data. Call [`updateTag`](/docs/app/api-reference/functions/updateTag) from a [Server Action](/docs/app/getting-started/mutating-data) when the user performed the mutation and should see fresh data on the next request, or [`revalidateTag`](/docs/app/api-reference/functions/revalidateTag) from a route handler, cron, or webhook for stale-while-revalidate refreshes.
### Gotchas
- Variables captured from the surrounding scope are automatically bound as part of the cache key. That keeps cached entries per-value, but it also means a wide closure can balloon the key surface. Prefer passing the dependencies you care about as function arguments so the contract is explicit.
- The `"use cache"` directive runs on the server. It can't wrap a function that uses runtime APIs such as [`cookies()`](/docs/app/api-reference/functions/cookies) or [`headers()`](/docs/app/api-reference/functions/headers). Read those outside the cached scope and pass the values as arguments, or use [`"use cache: private"`](/docs/app/api-reference/directives/use-cache-private).
- If you cache a function and still see this error, the `cacheLife` may be too short to prerender. See [Short-lived caches](#short-lived-caches).
- The default in-memory cache is per-server-instance. If the upstream call is expensive and you want a shared cache across instances, use [`"use cache: remote"`](/docs/app/api-reference/directives/use-cache-remote) instead. It trades a network roundtrip for a single cache shared by all servers.
### 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 [Wrap in or move into Suspense](#wrap-in-or-move-into-suspense) instead.
## Wrap in or move into Suspense
Choose this fix when the data must be fresh on every request. A [`<Suspense>`](https://react.dev/reference/react/Suspense) boundary lets the static shell ship instantly while the dynamic region [streams](/docs/app/glossary#streaming) in once the data resolves.
### Patterns
#### Wrap the existing component in place
Keep the component that performs the data access intact and add a [`<Suspense>`](https://react.dev/reference/react/Suspense) boundary around its usage in the parent.
```jsx filename="app/dashboard/page.js"
import { Suspense } from 'react'
import { TransactionList } from './transaction-list'
import { TransactionSkeleton } from './transaction-skeleton'
export default function Page() {
return (
<Suspense fallback={<TransactionSkeleton />}>
<TransactionList />
</Suspense>
)
}
```
Learn more: [Streaming with Suspense](/docs/app/guides/streaming).
#### Push the data access down to the leaf
When the page reads data at the top and forwards it down, move the read into the component that consumes it. The parent stays static and the boundary wraps only the part that needs the data.
```jsx filename="app/dashboard/page.js"
import { Suspense } from 'react'
export default function Page() {
return (
<main>
<DashboardHeader />
<Suspense fallback={<TransactionSkeleton />}>
<LatestTransactions />
</Suspense>
</main>
)
}
```
```jsx filename="app/dashboard/latest-transactions.js"
export async function LatestTransactions() {
const transactions = await db.transactions.findMany({ take: 20 })
return <TransactionList transactions={transactions} />
}
```
Learn more: [Streaming patterns and boundary placement](/docs/app/guides/streaming).
#### Add a boundary per leaf so they stream in parallel
When several siblings each fetch independently, give each one its own boundary so the streamed regions arrive in parallel instead of waiting on the slowest one.
```jsx filename="app/dashboard/page.js"
import { Suspense } from 'react'
export default function Page() {
return (
<main>
<Suspense fallback={<MetricsSkeleton />}>
<Metrics />
</Suspense>
<Suspense fallback={<TransactionSkeleton />}>
<LatestTransactions />
</Suspense>
</main>
)
}
```
Learn more: [Parallel streaming with multiple Suspense boundaries](/docs/app/guides/streaming).
#### Use `loading.js` for the whole segment
When the entire page depends on the failing data access and there's nothing static to render above it, a [`loading.js`](/docs/app/api-reference/file-conventions/loading) file in the segment is the shorthand. Next.js wraps `{children}` of the layout in `<Suspense>` automatically.
```jsx filename="app/dashboard/loading.js"
export default function Loading() {
return <DashboardSkeleton />
}
```
> **Good to know**: A `loading.js` file wraps the segment's `{children}` in one Suspense boundary. Parent layouts above it still prerender, but everything inside the segment sits behind the fallback. If page-level content could be prerendered (a static intro, a known title), use explicit `<Suspense>` boundaries inside `page.js` around only the dynamic parts.
Learn more: [`loading.js` and instant loading states](/docs/app/api-reference/file-conventions/loading).
### Trade-off
The shell ships immediately, but the user sees a loading state for the streamed region on every request. Design the fallback so it approximates the final layout. A generic spinner causes the page to visibly jump when content arrives. See [CLS-safe skeleton fallback guidance](/docs/app/guides/streaming#cls-cumulative-layout-shift).
### Choosing where to place the boundary
The location of the boundary controls what the user sees during the navigation:
- A high boundary (around the whole page) gives one loading state for everything. Less work to set up, but the user loses context about where they were going.
- A low boundary (around the specific component that fetches) keeps surrounding content visible and only shows a fallback for the part that is in flight. Preferred when the surrounding shell has cached content.
A useful rule: **push the boundary as low as possible** while keeping the fallback meaningful. The cached content above the boundary becomes part of the [static shell](/docs/app/glossary#static-shell) on navigation. Wrapping individual pieces or wrapping the whole page in one boundary stream the same way, but a lower boundary keeps more prerendered content visible during the navigation. See [Maximizing the static shell](/docs/app/getting-started/caching#streaming-uncached-data) for the canonical pattern.
### Gotchas
- The fallback must be deterministic. Calling [`Math.random()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random) or [`Date.now()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now) inside the fallback raises a separate [Cache Components](/docs/app/api-reference/config/next-config-js/cacheComponents) error during prerendering.
- Do not pass `{children}` through in the fallback. Child pages may include dynamic reads (for example, `/_not-found` calling [`cookies()`](/docs/app/api-reference/functions/cookies) or an uncached [`fetch()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/fetch)) that propagate into what should be a static fallback. Render a placeholder that doesn't include `{children}`.
- Boundary placement affects client navigations between sibling routes differently than initial page loads. Validation surfaces this in the dev server and at build time. See [Ensuring instant navigations](/docs/app/guides/instant-navigation) for the full model.
## Allow blocking route
Choose this fix when the route renders per-request and there's no useful static shell. Setting [`unstable_instant`](/docs/app/api-reference/file-conventions/route-segment-config/instant) to `false` exempts the segment from instant-navigation validation. The page renders on every request and the navigation blocks until that render completes.
### Patterns
#### Opt the page out
Add the export to the page that triggered the error. Only that route blocks.
```jsx filename="app/dashboard/page.js"
export const unstable_instant = false
export default async function Page() {
const data = await getDashboard()
return <Dashboard data={data} />
}
```
Learn more: [Ensuring instant navigations](/docs/app/guides/instant-navigation).
#### Opt the layout out
When the shared layout itself can't ship instantly (it reads runtime data or uncached data of its own), set [`unstable_instant`](/docs/app/api-reference/file-conventions/route-segment-config/instant) to `false` on the layout. The layout and every route in the subtree are exempted from instant-navigation validation.
```jsx filename="app/dashboard/layout.js"
export const unstable_instant = false
export default function DashboardLayout({ children }) {
return <DashboardShell>{children}</DashboardShell>
}
```
Learn more: [Route segment `instant` config](/docs/app/api-reference/file-conventions/route-segment-config/instant).
Use either pattern when:
- The route needs request-time data high in the tree to decide what to render (for example auth, tenant, or other gating in a layout), so there is no meaningful [static shell](/docs/app/glossary#static-shell) worth showing first.
- You're migrating a route incrementally and want to defer the lifetime decision without changing how the page renders today.
Don't use this to dismiss the error. Choose [Cache the component or data](#cache-the-component-or-data) or [Wrap in or move into Suspense](#wrap-in-or-move-into-suspense) when either is feasible.
### Trade-off
Navigations to this route are not instant. The user waits for the full server render before any HTML arrives. Use this only when that latency is necessary for the route to function.
### Gotchas
- Setting [`unstable_instant`](/docs/app/api-reference/file-conventions/route-segment-config/instant) to `false` on a layout exempts every route in the subtree, not only the layout itself. Audit child routes before opting a shared layout out.
- This export does not disable [prerendering](/docs/app/glossary#prerendering). The route still prerenders if it can. It only silences the instant-navigation validation error.
## Don't want this validation?
Instant-navigation validation runs by default in [Cache Components](/docs/app/api-reference/config/next-config-js/cacheComponents) apps and is what surfaces this error.
- **One segment**: add [`export const unstable_instant = false`](/docs/app/api-reference/file-conventions/route-segment-config/instant) to the page or layout file. This opts out the segment itself. Child segments are still validated during client navigations.
- **Layout and its children**: add `export const unstable_instant = { unstable_disableValidation: true }` to a layout. This disables validation for that layout and every segment below it.
- **Entire app**: set [`experimental.instantInsights.validationLevel`](/docs/app/api-reference/file-conventions/route-segment-config/instant#configuring-validation-defaults) to `'manual-warning'` in `next.config`. This limits validation to segments that explicitly export `unstable_instant`.
See [Ensuring instant navigations](/docs/app/guides/instant-navigation) for the full model.
## Useful links
- [Ensuring instant navigations](/docs/app/guides/instant-navigation)
- [Next.js encountered runtime data during prerendering](/docs/messages/blocking-prerender-runtime)
@@ -0,0 +1,175 @@
---
title: Next.js encountered uncached data in generateMetadata()
kind: insight
---
During [prerendering](/docs/app/glossary#prerendering), [`generateMetadata()`](/docs/app/api-reference/functions/generate-metadata) performed an uncached data access ([`fetch()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/fetch), database call, [`await connection()`](/docs/app/api-reference/functions/connection)). With [Cache Components](/docs/app/api-reference/config/next-config-js/cacheComponents) enabled, Next.js expects metadata to be prerenderable when the rest of the route is. This route's metadata is blocked, but the rest of its content can be prerendered.
Request-bound reads ([`cookies()`](/docs/app/api-reference/functions/cookies), [`headers()`](/docs/app/api-reference/functions/headers), [`params`](/docs/app/api-reference/file-conventions/page#params-optional), [`searchParams`](/docs/app/api-reference/file-conventions/page#searchparams-optional)) in `generateMetadata()` have different fixes. See [Next.js encountered runtime data in `generateMetadata()`](/docs/messages/blocking-prerender-metadata-runtime).
The viewport equivalent is handled at [Uncached data in `generateViewport()`](/docs/messages/blocking-prerender-viewport-dynamic).
For errors in the page body rather than metadata, see [Next.js encountered uncached data during prerendering](/docs/messages/blocking-prerender-dynamic).
> **Good to know**: In [`next dev`](/docs/app/api-reference/cli/next#next-dev-options), the error overlay points at the failing component. Run `next build --debug-prerender` to get the full list of blocking routes with stack traces. When iterating on specific routes, use `next build --debug-build-paths /dashboard /settings` to rebuild only those pages.
## Ways to fix this
<FixOption
group="cache"
href="#cache-the-metadata"
prompt={`Add "use cache" as the first statement inside generateMetadata(). This caches the metadata so it can be included in the prerender. Optionally call cacheTag(tag) so the entry can be invalidated on-demand from a Server Action via updateTag(tag), or from a Route Handler via revalidateTag(tag, "max") for stale-while-revalidate semantics. Optionally call cacheLife(profile) to control how long the cache lives before background revalidation or full expiration. Do not introduce new imports beyond "next/cache".`}
title="Cache the metadata"
>
Cache the metadata function so the result is reused and the route stays
prerenderable.
</FixOption>
<FixOption
group="dynamic"
href="#mark-the-route-as-dynamic"
prompt={`Add "await connection()" from "next/server" inside a component rendered by the page, wrapped in <Suspense>. The component can render null. This creates a dynamic hole inside Suspense so the rest of the page can still prerender, while signalling to Next.js that the dynamic metadata is intentional. Use this fix when the page would otherwise have no dynamic content other than the metadata.`}
title="Mark the route as dynamic"
>
Tell Next.js the page itself has dynamic content, so the dynamic metadata is
allowed.
</FixOption>
## Cache the metadata
Choose this fix when the metadata comes from an external source (CMS, database) but doesn't need to change on every request. Add the [`use cache`](/docs/app/api-reference/directives/use-cache) directive as the first statement inside [`generateMetadata()`](/docs/app/api-reference/functions/generate-metadata). Next.js caches the returned metadata object and includes it in the prerender.
### Patterns
#### Add `use cache` to `generateMetadata`
Mark the function as cacheable. The metadata is evaluated once per cache window and reused.
```jsx filename="app/blog/[slug]/page.js"
import { cms } from './cms'
export async function generateMetadata({ params }) {
'use cache'
const { slug } = await params
const { title } = await cms.getPageData(slug)
return { title }
}
async function getPageText(slug) {
'use cache'
const { text } = await cms.getPageData(slug)
return text
}
export default async function Page({ params }) {
const { slug } = await params
const text = await getPageText(slug)
return <article>{text}</article>
}
```
Learn more: [Caching with `use cache`](/docs/app/api-reference/directives/use-cache).
#### Tag the metadata for invalidation
When you publish new content and want the metadata to refresh, 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).
```jsx filename="app/blog/[slug]/page.js"
import { cacheTag } from 'next/cache'
import { cms } from './cms'
export async function generateMetadata({ params }) {
'use cache'
const { slug } = await params
cacheTag(`meta-${slug}`)
const { title } = await cms.getPageData(slug)
return { title }
}
```
Learn more: [How revalidation works](/docs/app/guides/how-revalidation-works).
### Trade-off
Freshness depends on the cache configuration. The metadata stays the same until [`cacheLife`](/docs/app/api-reference/functions/cacheLife) revalidates or expires, or until [`cacheTag`](/docs/app/api-reference/functions/cacheTag) is invalidated. Plan invalidations alongside the code that mutates the content.
### Gotchas
- [`use cache`](/docs/app/api-reference/directives/use-cache) can't be combined with [`cookies()`](/docs/app/api-reference/functions/cookies) or [`headers()`](/docs/app/api-reference/functions/headers) in the same scope. Inside a cached function, you can't call request-bound APIs. If the metadata needs a request-bound value (a session token to call a protected API), read it outside the cached scope and pass it as an argument, or use [Mark the route as dynamic](#mark-the-route-as-dynamic) instead.
- If the metadata function reads `params`, the params become part of the cache key automatically. Each unique param set gets its own cached metadata entry.
- A short [`cacheLife`](/docs/app/api-reference/functions/cacheLife) (a profile whose `revalidate` is shorter than the prerender's effective lifetime) prevents the metadata from being included in the prerender. The route becomes partially dynamic. Use a longer profile if you want the metadata included in the static shell.
## Mark the route as dynamic
Choose this fix when the rest of the page is fully static and you want the metadata to remain dynamic. Add a small component that calls [`await connection()`](/docs/app/api-reference/functions/connection), render `null` from it, and wrap it in [`<Suspense>`](https://react.dev/reference/react/Suspense).
This error fires specifically because the metadata is the only dynamic part of an otherwise fully prerenderable route. Adding a dynamic marker is an explicit signal to Next.js that the page has intentional dynamic content streamed alongside the static shell, so the dynamic metadata is allowed.
### Patterns
#### Add a dynamic marker component
Create a small component that calls [`connection()`](/docs/app/api-reference/functions/connection) and renders nothing, wrapped in [`<Suspense>`](https://react.dev/reference/react/Suspense). The page content remains prerenderable and only the marker is excluded from the prerender.
```jsx filename="app/page.js"
import { Suspense } from 'react'
import { connection } from 'next/server'
export async function generateMetadata() {
const response = await fetch('https://api.example.com/meta')
const { title } = await response.json()
return { title }
}
async function DynamicMarker() {
await connection()
return null
}
export default function Page() {
return (
<>
<article>This article is completely static</article>
<Suspense>
<DynamicMarker />
</Suspense>
</>
)
}
```
Learn more: [`connection`](/docs/app/api-reference/functions/connection).
### Trade-off
The metadata and the dynamic marker run on every request, so the route cannot be fully static. The rest of the page content still prerenders, and only the metadata blocks the initial paint.
### Gotchas
- The `DynamicMarker` must be wrapped in [`<Suspense>`](https://react.dev/reference/react/Suspense). Without the boundary, the dynamic marker propagates up and the entire page is treated as blocking, surfacing the same blocking-route error this fix is meant to address.
- This pattern is intentionally verbose. If you find yourself adding a dynamic marker, reconsider whether the metadata can be cached instead. Most metadata doesn't need to be per-request.
- If the page already has a genuinely dynamic component (one that reads [`cookies()`](/docs/app/api-reference/functions/cookies) or uncached data inside a [`<Suspense>`](https://react.dev/reference/react/Suspense) boundary), you won't see this error. The page is already partially dynamic.
- Framework-synthesized routes (`/_not-found`, `/_global-error`) inherit the root layout's `generateMetadata` and must be statically prerendered. The dynamic marker doesn't help here, because these routes don't have a page body where you can place a Suspense'd marker. If your root layout's `generateMetadata` depends on uncached data, [Cache the metadata](#cache-the-metadata) instead, or move to [`global-not-found.js`](/docs/app/api-reference/file-conventions/not-found#global-not-foundjs-experimental), which bypasses the root layout entirely and avoids inheriting its `generateMetadata`.
## Don't want this validation?
Instant-navigation validation runs by default in [Cache Components](/docs/app/api-reference/config/next-config-js/cacheComponents) apps and is what surfaces this error.
- **One segment**: add [`export const unstable_instant = false`](/docs/app/api-reference/file-conventions/route-segment-config/instant) to the page or layout file. This opts out the segment itself. Child segments are still validated during client navigations.
- **Layout and its children**: add `export const unstable_instant = { unstable_disableValidation: true }` to a layout. This disables validation for that layout and every segment below it.
- **Entire app**: set [`experimental.instantInsights.validationLevel`](/docs/app/api-reference/file-conventions/route-segment-config/instant#configuring-validation-defaults) to `'manual-warning'` in `next.config`. This limits validation to segments that explicitly export `unstable_instant`.
See [Ensuring instant navigations](/docs/app/guides/instant-navigation) for the full model.
## Useful links
- [`generateMetadata()`](/docs/app/api-reference/functions/generate-metadata)
- [Runtime data in `generateMetadata()`](/docs/messages/blocking-prerender-metadata-runtime)
- [Uncached data in `generateViewport()`](/docs/messages/blocking-prerender-viewport-dynamic)
- [`use cache` directive](/docs/app/api-reference/directives/use-cache)
- [`cacheLife`](/docs/app/api-reference/functions/cacheLife)
- [`cacheTag`](/docs/app/api-reference/functions/cacheTag)
- [`updateTag`](/docs/app/api-reference/functions/updateTag)
- [`revalidateTag`](/docs/app/api-reference/functions/revalidateTag)
- [`connection` function](/docs/app/api-reference/functions/connection)
- [Ensuring instant navigations](/docs/app/guides/instant-navigation)
@@ -0,0 +1,162 @@
---
title: Next.js encountered runtime data in generateMetadata()
kind: insight
---
During [prerendering](/docs/app/glossary#prerendering), [`generateMetadata()`](/docs/app/api-reference/functions/generate-metadata) or file-based metadata read a per-request value ([`cookies()`](/docs/app/api-reference/functions/cookies), [`headers()`](/docs/app/api-reference/functions/headers), [`params`](/docs/app/api-reference/file-conventions/page#params-optional), [`searchParams`](/docs/app/api-reference/file-conventions/page#searchparams-optional)). With [Cache Components](/docs/app/api-reference/config/next-config-js/cacheComponents) enabled, Next.js expects metadata to be prerenderable when the rest of the route is. This route's metadata is blocked, but the rest of its content can be prerendered.
Uncached data accesses ([`fetch()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/fetch), database calls, [`await connection()`](/docs/app/api-reference/functions/connection)) in `generateMetadata()` have different fixes. See [Next.js encountered uncached data in `generateMetadata()`](/docs/messages/blocking-prerender-metadata-dynamic).
The viewport equivalent is handled at [Runtime data in `generateViewport()`](/docs/messages/blocking-prerender-viewport-runtime).
For errors in the page body rather than metadata, see [Next.js encountered runtime data during prerendering](/docs/messages/blocking-prerender-runtime).
> **Good to know**: In [`next dev`](/docs/app/api-reference/cli/next#next-dev-options), the error overlay points at the failing component. Run `next build --debug-prerender` to get the full list of blocking routes with stack traces. When iterating on specific routes, use `next build --debug-build-paths /dashboard /settings` to rebuild only those pages.
## Ways to fix this
<FixOption
group="static"
href="#use-static-metadata"
prompt={`Replace the generateMetadata() function with a static metadata export. Convert all dynamic values to static strings. If the metadata depends on params, use generateStaticParams instead to prerender each variant. Do not introduce new imports.`}
title="Use static metadata"
>
Replace the dynamic function with a static export so the metadata is known at
build time.
</FixOption>
<FixOption
group="dynamic"
href="#mark-the-route-as-dynamic"
prompt={`Add "await connection()" from "next/server" inside a component rendered by the page, wrapped in <Suspense>. The component can render null. This creates a dynamic hole inside Suspense so the rest of the page can still prerender, while signalling to Next.js that the dynamic metadata is intentional. Use this fix when the page would otherwise have no dynamic content other than the metadata.`}
title="Mark the route as dynamic"
>
Tell Next.js the page itself has dynamic content, so the dynamic metadata is
allowed.
</FixOption>
## Use static metadata
Choose this fix when the metadata values are known at build time and don't change per request. Replace [`generateMetadata()`](/docs/app/api-reference/functions/generate-metadata) with a static [`metadata`](/docs/app/api-reference/functions/generate-metadata#metadata-object) export. The metadata is evaluated once during the build and included in every prerender.
### Patterns
#### Export a static object
Replace the function with a plain object export. Use this when all values are hard-coded strings.
```jsx filename="app/about/page.js"
export const metadata = {
title: 'About Us',
description: 'Learn more about our team and mission.',
}
export default function Page() {
return <AboutContent />
}
```
Learn more: [Static metadata](/docs/app/api-reference/functions/generate-metadata#metadata-object).
#### Use `generateStaticParams` for per-param metadata
When metadata varies by route param (a blog post title, a product name), pair [`generateStaticParams`](/docs/app/api-reference/functions/generate-static-params) with [`generateMetadata`](/docs/app/api-reference/functions/generate-metadata). Each param set is prerendered with its own metadata at build time.
```jsx filename="app/blog/[slug]/page.js"
export function generateStaticParams() {
return [{ slug: 'hello-world' }, { slug: 'nextjs-16' }]
}
export async function generateMetadata({ params }) {
'use cache'
const { slug } = await params
const post = await getPost(slug)
return { title: post.title }
}
```
Learn more: [`generateStaticParams`](/docs/app/api-reference/functions/generate-static-params).
### Trade-off
Static metadata can't reflect per-request values like the visitor's locale, A/B bucket, or personalized title. If you need request-time metadata, use [Mark the route as dynamic](#mark-the-route-as-dynamic).
### Gotchas
- File-based metadata (e.g. an [`icon.js`](/docs/app/api-reference/file-conventions/metadata/app-icons) or [`opengraph-image.js`](/docs/app/api-reference/file-conventions/metadata/opengraph-image) inside a dynamic segment) implicitly depends on `params`. If the segment is dynamic, Next.js treats the metadata function as dynamic too. Pair with [`generateStaticParams`](/docs/app/api-reference/functions/generate-static-params) or switch to a static file (e.g. `icon.png`).
- A [`template`](/docs/app/api-reference/functions/generate-metadata#template) in a parent layout's metadata applies at build time. It doesn't introduce a dynamic dependency.
## Mark the route as dynamic
Choose this fix when the metadata genuinely requires per-request data (a personalized title from a protected API, a theme color from a cookie) and a static export isn't feasible. Add a small component that calls [`await connection()`](/docs/app/api-reference/functions/connection), render `null` from it, and wrap it in [`<Suspense>`](https://react.dev/reference/react/Suspense).
This error fires specifically because the metadata is the only dynamic part of an otherwise fully prerenderable route. Adding a dynamic marker is an explicit signal to Next.js that the page has intentional dynamic content streamed alongside the static shell, so the dynamic metadata is allowed.
### Patterns
#### Add a dynamic marker component
Create a small component that calls [`connection()`](/docs/app/api-reference/functions/connection) and renders nothing, wrapped in [`<Suspense>`](https://react.dev/reference/react/Suspense). The page content remains prerenderable and only the marker is excluded from the prerender.
```jsx filename="app/page.js"
import { Suspense } from 'react'
import { cookies } from 'next/headers'
import { connection } from 'next/server'
export async function generateMetadata() {
const token = (await cookies()).get('token')
const response = await fetch('https://api.example.com/meta', {
headers: { Authorization: token?.value },
})
const { title } = await response.json()
return { title }
}
async function DynamicMarker() {
await connection()
return null
}
export default function Page() {
return (
<>
<article>This article is completely static</article>
<Suspense>
<DynamicMarker />
</Suspense>
</>
)
}
```
Learn more: [`connection`](/docs/app/api-reference/functions/connection).
### Trade-off
The metadata and the dynamic marker run on every request, so the route cannot be fully static. The rest of the page content still prerenders, and only the metadata blocks the initial paint.
### Gotchas
- The `DynamicMarker` must be wrapped in [`<Suspense>`](https://react.dev/reference/react/Suspense). Without the boundary, the dynamic marker propagates up and the entire page is treated as blocking, surfacing the same blocking-route error this fix is meant to address.
- This pattern is intentionally verbose. If you find yourself adding a dynamic marker, reconsider whether the metadata can be cached instead. Most metadata doesn't need to be per-request.
- If the page already has a genuinely dynamic component (one that reads [`cookies()`](/docs/app/api-reference/functions/cookies) or [`headers()`](/docs/app/api-reference/functions/headers) inside a [`<Suspense>`](https://react.dev/reference/react/Suspense) boundary), you won't see this error. The page is already partially dynamic.
- Framework-synthesized routes (`/_not-found`, `/_global-error`) inherit the root layout's `generateMetadata` and must be statically prerendered. The dynamic marker doesn't help here, because these routes don't have a page body where you can place a Suspense'd marker. If your root layout's `generateMetadata` depends on request data, [Use static metadata](#use-static-metadata) instead, or move to [`global-not-found.js`](/docs/app/api-reference/file-conventions/not-found#global-not-foundjs-experimental), which bypasses the root layout entirely and avoids inheriting its `generateMetadata`.
## Don't want this validation?
Instant-navigation validation runs by default in [Cache Components](/docs/app/api-reference/config/next-config-js/cacheComponents) apps and is what surfaces this error.
- **One segment**: add [`export const unstable_instant = false`](/docs/app/api-reference/file-conventions/route-segment-config/instant) to the page or layout file. This opts out the segment itself. Child segments are still validated during client navigations.
- **Layout and its children**: add `export const unstable_instant = { unstable_disableValidation: true }` to a layout. This disables validation for that layout and every segment below it.
- **Entire app**: set [`experimental.instantInsights.validationLevel`](/docs/app/api-reference/file-conventions/route-segment-config/instant#configuring-validation-defaults) to `'manual-warning'` in `next.config`. This limits validation to segments that explicitly export `unstable_instant`.
See [Ensuring instant navigations](/docs/app/guides/instant-navigation) for the full model.
## Useful links
- [`generateMetadata()`](/docs/app/api-reference/functions/generate-metadata)
- [Uncached data in `generateMetadata()`](/docs/messages/blocking-prerender-metadata-dynamic)
- [Runtime data in `generateViewport()`](/docs/messages/blocking-prerender-viewport-runtime)
- [`generateStaticParams`](/docs/app/api-reference/functions/generate-static-params)
- [`connection` function](/docs/app/api-reference/functions/connection)
- [Ensuring instant navigations](/docs/app/guides/instant-navigation)
+189
View File
@@ -0,0 +1,189 @@
---
title: Next.js encountered the unstable value Math.random() in a Client Component
kind: insight
---
A [Client Component](/docs/app/getting-started/server-and-client-components#using-client-components) called [`Math.random()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random) inline during render, and the surrounding tree had no [`<Suspense>`](https://react.dev/reference/react/Suspense) boundary. Client Components are server-side rendered on first load, so Next.js can't bake an unpredictable value into the prerendered HTML. The SSR value won't match the value the client computes on hydration, so you need to choose: defer the value behind a [`<Suspense>`](https://react.dev/reference/react/Suspense) boundary so SSR can stream it, or move the call into [`useEffect`](https://react.dev/reference/react/useEffect) (or an event handler) so it only runs on the client.
The Server Component case is handled at [`Math.random()` during prerendering](/docs/messages/blocking-prerender-random). Other unpredictable client-side APIs ([`Date.now()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now), [`crypto.randomUUID()`](https://developer.mozilla.org/en-US/docs/Web/API/Crypto/randomUUID)) have parallel error pages: [`Date.now()` in a Client Component](/docs/messages/blocking-prerender-current-time-client) and [Crypto APIs in a Client Component](/docs/messages/blocking-prerender-crypto-client).
> **Good to know**: In [`next dev`](/docs/app/api-reference/cli/next#next-dev-options), the error overlay points at the failing component. Run `next build --debug-prerender` to get the full list of blocking routes with stack traces. When iterating on specific routes, use `next build --debug-build-paths /dashboard /settings` to rebuild only those pages.
## Ways to fix this
<FixOption
group="stream"
href="#wrap-in-or-move-into-suspense"
prompt={`Wrap the Client Component that calls Math.random() in <Suspense> in its parent. The fallback prop must render synchronous, deterministic JSX (no Math.random or Date.now) that approximates the final layout (skeleton, spinner, or stable placeholder text). Import Suspense from "react". Do not change the Math.random() call.`}
title="Wrap in or move into Suspense"
>
Wrap the component in a Suspense boundary so the shell ships instantly and the
random value streams in.
</FixOption>
<FixOption
group="defer"
href="#move-into-effect-or-event-handler"
prompt={`Move the Math.random() call out of the inline render path and into useEffect (for first-paint values) or an event handler (for interaction values). Initialize state to a deterministic value so SSR and the first hydrated render agree. Do not introduce new imports beyond "react".`}
title="Move into effect or event handler"
>
Defer the random read until after hydration so SSR and the browser agree on
the initial render.
</FixOption>
## Wrap in or move into Suspense
Choose this fix when the random value is part of the rendered output and a brief fallback during SSR is acceptable. Wrap the consuming Client Component in [`<Suspense>`](https://react.dev/reference/react/Suspense) from its parent. The fallback ships in the prerendered HTML, and Next.js fills in the real component when the browser hydrates.
### Patterns
#### Wrap from a Server Component parent
Place the [`<Suspense>`](https://react.dev/reference/react/Suspense) boundary in the Server Component that renders the Client Component. The fallback prerenders, the inner Client Component runs in the browser, and you only handle the random value once.
```jsx filename="app/page.js"
import { Suspense } from 'react'
import { Avatar } from './avatar'
export default function Page() {
return (
<Profile>
<Suspense fallback={<div className="avatar-skeleton" />}>
<Avatar />
</Suspense>
</Profile>
)
}
```
```jsx filename="app/avatar.js"
'use client'
export function Avatar() {
const color = `#${Math.random().toString(16).slice(2, 8)}`
return <div style={{ background: color }} />
}
```
Learn more: [Streaming with Suspense](/docs/app/guides/streaming).
### Trade-off
The component shows the fallback during SSR and the first paint. For above-the-fold UI this can be visible. Use [`loading.js`](/docs/app/api-reference/file-conventions/loading) for full-segment fallback or pick a fallback that matches the final layout to minimize visual jump.
### Gotchas
- Any UI rendered as part of the prerender shell must be deterministic, including [`<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 [`Math.random()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random) in any of them raises this same error. Use stable placeholder content.
- The inner Client Component still runs during SSR, behind the boundary. If you need to guarantee the random value only runs in the browser, use [Move into effect or event handler](#move-into-effect-or-event-handler) instead.
- A [`<Suspense>`](https://react.dev/reference/react/Suspense) boundary only fixes the prerender/hydration mismatch, not client re-renders. If the component using [`Math.random()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random) re-renders on the client (a parent state change, a context update), it produces a new value each time. To stabilize the value across re-renders, call [`Math.random()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random) once in a [`useState`](https://react.dev/reference/react/useState) initializer or [`useRef`](https://react.dev/reference/react/useRef), or compute it on the server and pass it down as a prop.
## Move into effect or event handler
Choose this fix when the random value isn't needed for the first paint. Move the [`Math.random()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random) call into [`useEffect`](https://react.dev/reference/react/useEffect) (for first-paint-after-mount values) or an event handler (for interaction values). The initial render uses a deterministic placeholder, so SSR and hydration agree.
### Patterns
#### Use `useEffect` for an initial value after mount
For values that should appear shortly after the page loads. Initialize state to `null` (or another deterministic stand-in) and assign the random value inside [`useEffect`](https://react.dev/reference/react/useEffect).
```jsx filename="app/avatar.js"
'use client'
import { startTransition, useEffect, useState } from 'react'
export function Avatar() {
const [color, setColor] = useState('#eaeaea')
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(() => {
setColor(`#${Math.random().toString(16).slice(2, 8)}`)
})
}, [])
return <div style={{ background: color }} />
}
```
Learn more: [`useEffect`](https://react.dev/reference/react/useEffect).
#### Compute on user interaction
When the random value is in response to a click ("reshuffle", "new card"), compute it in the event handler. No SSR concern at all.
```jsx filename="app/shuffle.js"
'use client'
import { useState } from 'react'
export function Shuffle() {
const [seed, setSeed] = useState(0)
return (
<button onClick={() => setSeed(Math.random())}>Shuffle ({seed})</button>
)
}
```
#### Lazy-initialize a stable ID with `useRef`
When a component needs a stable ID for the lifetime of its mount (a tracking ID, a correlation key), produce it lazily inside a [`useRef`](https://react.dev/reference/react/useRef) getter. The ref initializer runs after mount, so SSR sees `null` and the browser fills in the value. Subsequent renders read the same ref so the ID stays stable.
```jsx filename="app/workflow.js"
'use client'
import { useRef } from 'react'
function getOrCreateId(ref) {
if (!ref.current) {
ref.current = Math.random().toString(36).slice(2)
}
return ref.current
}
export function Workflow({ onNext }) {
const idRef = useRef(null)
return (
<button
onClick={() => {
trackEvent(getOrCreateId(idRef), 'forward')
onNext()
}}
>
Next
</button>
)
}
```
Learn more: [`useRef`](https://react.dev/reference/react/useRef).
### Trade-off
The user sees the placeholder briefly before the real value. For interactions the wait is invisible, but for `useEffect`-based values there's a flash of the initial state. See [Preventing flash before hydration](/docs/app/guides/preventing-flash-before-hydration) for techniques that eliminate the flash.
### Gotchas
- Don't compute the random value inline during render even with `useState((/* ... */) => Math.random())`. The lazy initializer still runs during SSR and triggers the error.
- Calling [`Math.random()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random) inline during render in a server-rendered Client Component also causes a hydration mismatch (the SSR HTML uses one value, the browser uses another). The `useEffect` and event handler patterns above avoid both the error and the mismatch.
- If the value needs to be hydration-stable (the SSR HTML and the hydrated render must match exactly), use [Wrap in or move into Suspense](#wrap-in-or-move-into-suspense) instead.
- When you call `setState` from inside [`useEffect`](https://react.dev/reference/react/useEffect), wrap it in [`startTransition`](https://react.dev/reference/react/startTransition). Cascading state updates during hydration can cause an outer [`<Suspense>`](https://react.dev/reference/react/Suspense) boundary's fallback to briefly flash. `startTransition` marks the update as non-blocking so React keeps the existing UI in place while the new value resolves.
## Don't want this validation?
Instant-navigation validation runs by default in [Cache Components](/docs/app/api-reference/config/next-config-js/cacheComponents) apps and is what surfaces this error.
- **One segment**: add [`export const unstable_instant = false`](/docs/app/api-reference/file-conventions/route-segment-config/instant) to the page or layout file. This opts out the segment itself. Child segments are still validated during client navigations.
- **Layout and its children**: add `export const unstable_instant = { unstable_disableValidation: true }` to a layout. This disables validation for that layout and every segment below it.
- **Entire app**: set [`experimental.instantInsights.validationLevel`](/docs/app/api-reference/file-conventions/route-segment-config/instant#configuring-validation-defaults) to `'manual-warning'` in `next.config`. This limits validation to segments that explicitly export `unstable_instant`.
See [Ensuring instant navigations](/docs/app/guides/instant-navigation) for the full model.
## Useful links
- [`Math.random()` during prerendering](/docs/messages/blocking-prerender-random)
- [`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)
- [`useEffect`](https://react.dev/reference/react/useEffect)
- [Streaming with Suspense](/docs/app/guides/streaming)
- [Ensuring instant navigations](/docs/app/guides/instant-navigation)
+210
View File
@@ -0,0 +1,210 @@
---
title: Next.js encountered the unstable value Math.random() while prerendering
kind: insight
---
During [prerendering](/docs/app/glossary#prerendering), a Server Component called [`Math.random()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random) 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 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 ([`Date.now()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now), [`crypto.randomUUID()`](https://developer.mozilla.org/en-US/docs/Web/API/Crypto/randomUUID)) have parallel error pages: see [`Date.now()`](/docs/messages/blocking-prerender-current-time) and [crypto APIs](/docs/messages/blocking-prerender-crypto). The Client Component case is handled at [`Math.random()` in a Client Component](/docs/messages/blocking-prerender-random-client).
> **Good to know**: In [`next dev`](/docs/app/api-reference/cli/next#next-dev-options), the error overlay points at the failing component. Run `next build --debug-prerender` to get the full list of blocking routes with stack traces. When iterating on specific routes, use `next build --debug-build-paths /dashboard /settings` to rebuild only those pages.
## Ways to fix this
<FixOption
group="dynamic"
href="#generate-on-every-request"
prompt={`Add "await connection()" from "next/server" immediately before the Math.random() call. This marks the component as request-time, so Next.js excludes it from the prerendered HTML and streams it in from the nearest <Suspense> boundary on each request. Do not change the call site of Math.random() itself. Only change the call site once you've confirmed with the user that a fresh value on every request is the intent.`}
title="Generate on every request"
>
Mark the component as request-time so the random value is generated each time
the user visits.
</FixOption>
<FixOption
group="cache"
href="#cache-the-random-value"
prompt={`Move the Math.random() call into its own function or component and add "use cache" as the first statement of the body. Optionally call cacheLife(profile) to control how long the same random value is reused before regeneration. Do not introduce new imports beyond "next/cache".`}
title="Cache the random value"
>
Generate one random value at build time and reuse it. The route stays
prerendered.
</FixOption>
<FixOption
group="client"
href="#render-on-the-client"
prompt={`Move the component that calls Math.random() into a Client Component by adding "use client" at the top of the file. The browser produces a fresh value on each visit. If the value needs to be hydration-stable, compute it inside a useEffect or event handler instead of inline during render.`}
title="Render on the client"
>
Move the call into a Client Component. The browser produces the random value,
so the server never has to.
</FixOption>
## Generate on every request
Choose this fix when each request genuinely needs a different value. A [unique session ID](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy), a single-use nonce, an A/B test bucket: anything that has to be fresh per visitor. 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 random call
Call [`connection()`](/docs/app/api-reference/functions/connection) before [`Math.random()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random). 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 random read as possible. If the parent has cached content (a header, stats, navigation), isolate the random 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={<TraceSkeleton />}>
<RequestTrace />
</Suspense>
<CachedStats />
</DashboardShell>
)
}
```
```jsx filename="app/dashboard/request-trace.js"
import { connection } from 'next/server'
export async function RequestTrace() {
await connection()
const traceId = Math.random().toString(16).slice(2)
return <small>trace: {traceId}</small>
}
```
Learn more: [`connection`](/docs/app/api-reference/functions/connection), [Streaming patterns and boundary placement](/docs/app/guides/streaming).
### 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 can paint. Make sure the fallback approximates the final layout so the page doesn't visibly jump when the value arrives.
### 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 [`Math.random()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random) or [`Date.now()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now) in any of them raises this same error.
- If [`Math.random()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random) is being used as a unique ID for logging or correlation, consider an incrementing integer or [`AsyncLocalStorage`](https://nodejs.org/api/async_context.html#class-asynclocalstorage) request scope. Those don't trigger the error at all because they aren't unpredictable from Next.js's point of view.
- Random values produced inside third-party packages will surface this error in your project code. The same fixes apply at the call site that consumes the value.
## Cache the random value
Choose this fix when one stable random value per build, deployment, or `cacheLife` window is acceptable. The classic case is a daily shuffle of items where the same shuffle is fine for every visitor that day. Move the [`Math.random()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random) call into a function with [`use cache`](/docs/app/api-reference/directives/use-cache) as the first statement. Next.js evaluates the function once per cache key and reuses the result.
### Patterns
#### Cache the producer function
Wrap the random generation in its own function with [`use cache`](/docs/app/api-reference/directives/use-cache). The returned value is part of the cache entry, so every consumer sees the same random number until the cache is invalidated.
```jsx filename="app/page.js"
async function getRandomSeed() {
'use cache'
return Math.random()
}
export default async function Page() {
const products = await getCachedProducts()
const seed = await getRandomSeed()
return <ProductsView products={randomize(products, seed)} />
}
```
Learn more: [Caching with `use cache`](/docs/app/api-reference/directives/use-cache).
#### Control the rotation window with `cacheLife`
When you want the random value to rotate on a schedule (a daily featured item, an hourly shuffle), set a [`cacheLife`](/docs/app/api-reference/functions/cacheLife) profile.
```jsx filename="app/page.js"
import { cacheLife } from 'next/cache'
async function getDailySeed() {
'use cache'
cacheLife('days')
return Math.random()
}
```
Learn more: [How to configure cache lifetimes](/docs/app/api-reference/functions/cacheLife).
### Trade-off
Every visitor in the cache window sees the same "random" value. That's the right answer for global ordering and feature rotation, but the wrong answer for per-user uniqueness or anything security-sensitive (session IDs, CSRF tokens, nonces). For unique-per-request values use [Generate on every request](#generate-on-every-request).
### Gotchas
- Inside a [`use cache`](/docs/app/api-reference/directives/use-cache) scope, you can't call [`cookies()`](/docs/app/api-reference/functions/cookies) or [`headers()`](/docs/app/api-reference/functions/headers), which means you can't easily key the random value by request identity.
- If the same random value is used in many places, hoist the cached function up so all consumers share the cache entry instead of producing a different cached value at each call site.
- 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 random value belongs to the client experience. A canvas seed for a confetti animation, a random color for an avatar placeholder, a UI nonce that only matters in the browser. Move the component into a [Client Component](/docs/app/getting-started/server-and-client-components) so the value is produced after hydration, not during prerender.
### Patterns
#### Compute the value inside `useEffect`
Add the [`use client`](/docs/app/api-reference/directives/use-client) directive. Initialize state to a deterministic placeholder and assign the real value inside [`useEffect`](https://react.dev/reference/react/useEffect), which runs only in the browser after hydration.
```jsx filename="app/avatar.js"
'use client'
import { startTransition, useEffect, useState } from 'react'
export function Avatar() {
const [color, setColor] = useState('#888')
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(() => {
setColor(`#${Math.random().toString(16).slice(2, 8)}`)
})
}, [])
return <div style={{ background: color }} />
}
```
#### Inline render with a parent `<Suspense>` boundary
If the random value needs to be part of the server-rendered HTML (not deferred to after hydration), the component can call [`Math.random()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random) during render as long as a [`<Suspense>`](https://react.dev/reference/react/Suspense) boundary wraps it from the parent. Next.js prerenders the fallback and fills in the real component at request time. See [`Math.random()` in a Client Component](/docs/messages/blocking-prerender-random-client) for the full recipe.
Learn more: [Client Components](/docs/app/getting-started/server-and-client-components#using-client-components), [`Math.random()` in a Client Component](/docs/messages/blocking-prerender-random-client).
### Trade-off
The first paint shows the SSR fallback or initial state, and the random value appears only after the browser hydrates the component. That's fine for UI flourishes but wrong for content that has 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 produces a random value inline during render still trips this error during SSR. See the dedicated [`Math.random()` in a Client Component](/docs/messages/blocking-prerender-random-client) page for the [`<Suspense>`](https://react.dev/reference/react/Suspense) and effect-based recipes.
## Don't want this validation?
Instant-navigation validation runs by default in [Cache Components](/docs/app/api-reference/config/next-config-js/cacheComponents) apps and is what surfaces this error.
- **One segment**: add [`export const unstable_instant = false`](/docs/app/api-reference/file-conventions/route-segment-config/instant) to the page or layout file. This opts out the segment itself. Child segments are still validated during client navigations.
- **Layout and its children**: add `export const unstable_instant = { unstable_disableValidation: true }` to a layout. This disables validation for that layout and every segment below it.
- **Entire app**: set [`experimental.instantInsights.validationLevel`](/docs/app/api-reference/file-conventions/route-segment-config/instant#configuring-validation-defaults) to `'manual-warning'` in `next.config`. This limits validation to segments that explicitly export `unstable_instant`.
See [Ensuring instant navigations](/docs/app/guides/instant-navigation) for the full model.
## Useful links
- [`Math.random()` in a Client Component](/docs/messages/blocking-prerender-random-client)
- [`Date.now()` during prerendering](/docs/messages/blocking-prerender-current-time)
- [Crypto APIs during prerendering](/docs/messages/blocking-prerender-crypto)
- [`use cache` directive](/docs/app/api-reference/directives/use-cache)
- [`connection` function](/docs/app/api-reference/functions/connection)
- [Ensuring instant navigations](/docs/app/guides/instant-navigation)
+302
View File
@@ -0,0 +1,302 @@
---
title: Next.js encountered runtime data during prerendering or a navigation
kind: insight
---
During [prerendering](/docs/app/glossary#prerendering), [`cookies()`](/docs/app/api-reference/functions/cookies), [`headers()`](/docs/app/api-reference/functions/headers), [`params`](/docs/app/api-reference/file-conventions/page#params-optional), or [`searchParams`](/docs/app/api-reference/file-conventions/page#searchparams-optional) was read 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 prerender any part of the tree that depends on a per-request value, so navigations to this route block instead of being [instant](/docs/app/guides/instant-navigation).
Uncached data accesses ([`fetch()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/fetch), database calls, [`await connection()`](/docs/app/api-reference/functions/connection)) have different fixes. See [Next.js encountered uncached data during prerendering](/docs/messages/blocking-prerender-dynamic).
This error can also appear during a client-side navigation when the data access sits inside a [`<Suspense>`](https://react.dev/reference/react/Suspense) boundary from a parent layout but that boundary is too high. It wraps the entire segment instead of only the dynamic part, so the navigation still blocks. Push the boundary closer to the data access so the rest of the segment stays in the [static shell](/docs/app/glossary#static-shell). See [Choosing where to place the boundary](#choosing-where-to-place-the-boundary).
> **Good to know**: In [`next dev`](/docs/app/api-reference/cli/next#next-dev-options), the error overlay points at the failing component. Run `next build --debug-prerender` to get the full list of blocking routes with stack traces. When iterating on specific routes, use `next build --debug-build-paths /dashboard /settings` to rebuild only those pages.
## Ways to fix this
<FixOption
group="stream"
href="#wrap-in-or-move-into-suspense"
prompt={`Wrap the component that reads cookies(), headers(), params, or searchParams in <Suspense>. The fallback prop must render synchronous, deterministic JSX (no fetch, no awaiting, no Math.random or Date.now) that approximates the final layout (skeleton, spinner, or stable placeholder text). Import Suspense from "react". Do not change the data access call. Place the Suspense boundary as close to the access as possible so the cached content above remains in the static shell. If the access is deep in a tree and used for a small piece of UI, prefer to push the access down to the leaf component that needs it instead of awaiting it at the top and forwarding the value.`}
title="Wrap in or move into Suspense"
>
Wrap the component that reads the request-time value in a Suspense boundary,
or push the read down to the leaf that needs it.
</FixOption>
<FixOption
group="cache"
href="#for-known-params-prerender"
prompt={`Add a generateStaticParams() export to the dynamic segment. Return an array of param objects whose keys match the segment's [param] names. Each entry is prerendered into static HTML at build time. With Cache Components, requests for params not in the list are served a fallback shell and the route is upgraded in the background. Return a subset of known params for common routes (popular categories, top locales, recent slugs); rare or open-ended params will fall back at runtime. Do not introduce new imports beyond Next.js types. If you can't return at least one known param at build time, use "Wrap in or move into Suspense" instead.`}
title="For known params, prerender"
>
Tell Next.js the full set of valid params ahead of time. Each one becomes a
prerendered route.
</FixOption>
<FixOption
group="block"
href="#allow-blocking-route"
prompt={`Add "export const unstable_instant = false" as a top-level export in the page or layout file. This silences the warning for this segment. Confirm with the user that the route is intentionally request-time before applying this change: the export exempts the segment from instant-navigation validation, and the route renders on every request, so navigations to it block until the render completes.`}
title="Allow blocking route"
>
Opt this segment out of instant navigation. The route has no static shell and
every navigation blocks until the render completes.
</FixOption>
## Wrap in or move into Suspense
Choose this fix when the value really is per-request, but the page has parts that don't depend on it. A [`<Suspense>`](https://react.dev/reference/react/Suspense) boundary lets the static shell ship instantly while the dynamic region [streams](/docs/app/glossary#streaming) in once the request value resolves.
### Patterns
#### Wrap the existing component in place
Keep the component that reads the runtime API intact and add a [`<Suspense>`](https://react.dev/reference/react/Suspense) boundary around its usage in the parent.
```jsx filename="app/dashboard/page.js"
import { Suspense } from 'react'
import { UserHeader } from './user-header'
import { HeaderSkeleton } from './header-skeleton'
export default function Page() {
return (
<DashboardShell>
<Suspense fallback={<HeaderSkeleton />}>
<UserHeader />
</Suspense>
<CachedStats />
</DashboardShell>
)
}
```
Learn more: [Streaming with Suspense](/docs/app/guides/streaming).
#### Push the access down to the leaf
When the value is read at the top of the tree but only consumed by a small piece of UI, move the read down. The parent stays prerenderable and only the leaf needs a boundary.
```jsx filename="app/dashboard/page.js"
import { Suspense } from 'react'
export default function Page() {
return (
<DashboardShell>
<Suspense fallback={<HeaderSkeleton />}>
<UserHeader />
</Suspense>
<CachedStats />
</DashboardShell>
)
}
```
```jsx filename="app/dashboard/user-header.js"
import { cookies } from 'next/headers'
export async function UserHeader() {
const session = (await cookies()).get('session')
return <header>Signed in as {session?.value}</header>
}
```
Learn more: [Streaming patterns and boundary placement](/docs/app/guides/streaming).
#### Pass `searchParams` without awaiting
When the consumer is a child component, pass the promise down instead of awaiting it at the top. The child wraps its own consumption in [`<Suspense>`](https://react.dev/reference/react/Suspense), which keeps the parent prerenderable.
```jsx filename="app/dashboard/page.js"
import { Suspense } from 'react'
import { Results } from './results'
export default function Page({ searchParams }) {
return (
<DashboardShell>
<DashboardHeader />
<Suspense fallback={<ResultsSkeleton />}>
<Results searchParams={searchParams} />
</Suspense>
</DashboardShell>
)
}
```
```jsx filename="app/dashboard/results.js"
export async function Results({ searchParams }) {
const { q } = await searchParams
const widgets = await searchWidgets(q)
return <WidgetList widgets={widgets} />
}
```
Learn more: [Using `searchParams` in the App Router](/docs/app/api-reference/file-conventions/page#searchparams-optional).
#### Forward `searchParams` as a promise chain
A variant of the previous pattern when you want to derive a value without awaiting at the top. Treat [`searchParams`](/docs/app/api-reference/file-conventions/page#searchparams-optional) as a promise and `.then()` it to project the shape the child needs.
```jsx filename="app/dashboard/map/page.js"
export default function Page({ searchParams }) {
const coords = searchParams.then((sp) => ({
lat: Number(sp.lat),
lng: Number(sp.lng),
}))
return <Map coords={coords} />
}
```
Learn more: [Using `searchParams` in the App Router](/docs/app/api-reference/file-conventions/page#searchparams-optional).
#### Use `loading.js` for the whole segment
When every component in the segment reads the same request value and there's nothing static to render above it, a [`loading.js`](/docs/app/api-reference/file-conventions/loading) file in the segment is the shorthand. Next.js wraps `{children}` of the layout in `<Suspense>` automatically.
```jsx filename="app/dashboard/loading.js"
export default function Loading() {
return <DashboardSkeleton />
}
```
> **Good to know**: A `loading.js` file wraps the segment's `{children}` in one Suspense boundary. Parent layouts above it still prerender, but everything inside the segment sits behind the fallback. If page-level content could be prerendered (a static intro, a known title), use explicit `<Suspense>` boundaries inside `page.js` around only the dynamic parts.
Learn more: [`loading.js` and instant loading states](/docs/app/api-reference/file-conventions/loading).
### Trade-off
The shell ships immediately, but the user sees a loading state for the streamed region on every request. Design the fallback so it approximates the final layout. A generic spinner causes the page to visibly jump when content arrives. See [CLS-safe skeleton fallback guidance](/docs/app/guides/streaming#cls-cumulative-layout-shift).
### Choosing where to place the boundary
The location of the boundary controls what the user sees during the navigation:
- A high boundary (around the whole page) gives one loading state for everything. Less work to set up, but the user loses context about where they were going.
- A low boundary (around the specific component that reads the runtime API) keeps surrounding content visible and only shows a fallback for the per-request part. Preferred when the surrounding shell has cached content.
A useful rule: **push the boundary as low as possible** while keeping the fallback meaningful. The cached content above the boundary becomes part of the [static shell](/docs/app/glossary#static-shell) on navigation. Wrapping individual pieces or wrapping the whole page in one boundary stream the same way, but a lower boundary keeps more prerendered content visible during the navigation. See [Maximizing the static shell](/docs/app/getting-started/caching#streaming-uncached-data) for the canonical pattern.
### Gotchas
- The fallback must be deterministic. Calling [`Math.random()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random) or [`Date.now()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now) inside the fallback raises a separate [Cache Components](/docs/app/api-reference/config/next-config-js/cacheComponents) error during prerendering.
- Do not pass `{children}` through in the fallback. Child pages may include dynamic reads (for example, `/_not-found` calling [`cookies()`](/docs/app/api-reference/functions/cookies) or [`headers()`](/docs/app/api-reference/functions/headers)) that propagate into what should be a static fallback. Render a placeholder that doesn't include `{children}`.
- Boundary placement affects client navigations between sibling routes differently than initial page loads. Validation surfaces this in the dev server and at build time. See [Ensuring instant navigations](/docs/app/guides/instant-navigation) for the full model.
- The function returned by [`cookies()`](/docs/app/api-reference/functions/cookies) and [`headers()`](/docs/app/api-reference/functions/headers) is async. Make sure the component reading them is async too, and `await` the call.
- The `params` and `searchParams` props are also async promises. Treat them like any other awaited value when deciding where the boundary goes.
## For known params, prerender
Choose this fix when the route has a closed set of valid params and you know them at build time. Adding [`generateStaticParams`](/docs/app/api-reference/functions/generate-static-params) tells Next.js to prerender each one, so the per-request `params` value becomes a build-time constant for those entries.
### Patterns
#### Return a fixed list
Return the full list of params from the export. Use this for routes whose param space is small and known.
```jsx filename="app/dashboard/[team]/page.js"
export function generateStaticParams() {
return [{ team: 'marketing' }, { team: 'sales' }, { team: 'ops' }]
}
export default async function Page({ params }) {
const { team } = await params
return <TeamDashboard team={team} />
}
```
Learn more: [`generateStaticParams` basics](/docs/app/api-reference/functions/generate-static-params).
#### Fetch the list at build time
When the param values come from a content source, fetch them inside `generateStaticParams`. Next.js calls the function once at build time.
```jsx filename="app/dashboard/[team]/page.js"
export async function generateStaticParams() {
const teams = await db.teams.findMany({ select: { slug: true } })
return teams.map((team) => ({ team: team.slug }))
}
export default async function Page({ params }) {
const { team } = await params
const dashboard = await getDashboard(team)
return <TeamDashboard dashboard={dashboard} />
}
```
Learn more: [ISR with Cache Components](/docs/app/guides/incremental-static-regeneration-cache-components).
### Trade-off
The list of params is decided at build time. See [`generateStaticParams`](/docs/app/api-reference/functions/generate-static-params) for rebuild and cache-invalidation patterns when the list changes. A request for a param not in the returned list falls through to runtime rendering, so the runtime data read still needs a [`<Suspense>`](https://react.dev/reference/react/Suspense) boundary or [Allow blocking route](#allow-blocking-route).
### Gotchas
- The [`generateStaticParams`](/docs/app/api-reference/functions/generate-static-params) function only handles `params`. It does not solve [`cookies()`](/docs/app/api-reference/functions/cookies), [`headers()`](/docs/app/api-reference/functions/headers), or [`searchParams`](/docs/app/api-reference/file-conventions/page#searchparams-optional). If the page also reads any of those, you still need a [`<Suspense>`](https://react.dev/reference/react/Suspense) boundary.
- The function runs at build time, so it can't depend on per-request values. Reading [`cookies()`](/docs/app/api-reference/functions/cookies) or [`headers()`](/docs/app/api-reference/functions/headers) inside it throws.
- The [`dynamicParams`](/docs/app/api-reference/file-conventions/route-segment-config/dynamicParams) config isn't available in this model. If you're migrating an existing page, use [`<Suspense>`](https://react.dev/reference/react/Suspense), [`notFound()`](/docs/app/api-reference/functions/not-found), or [Allow blocking route](#allow-blocking-route) instead.
## Allow blocking route
Choose this fix when the route renders per-request and there's no useful static shell. Setting [`unstable_instant`](/docs/app/api-reference/file-conventions/route-segment-config/instant) to `false` exempts the segment from instant-navigation validation. The page renders on every request and the navigation blocks until that render completes.
### Patterns
#### Opt the page out
Add the export to the page that triggered the error. Only that route blocks.
```jsx filename="app/dashboard/page.js"
export const unstable_instant = false
export default async function Page() {
const session = (await cookies()).get('session')
return <Dashboard session={session?.value} />
}
```
Learn more: [Ensuring instant navigations](/docs/app/guides/instant-navigation).
#### Opt the layout out
When the shared layout itself can't ship instantly (it reads [`cookies()`](/docs/app/api-reference/functions/cookies) or [`headers()`](/docs/app/api-reference/functions/headers) of its own), set [`unstable_instant`](/docs/app/api-reference/file-conventions/route-segment-config/instant) to `false` on the layout. The layout and every route in the subtree are exempted from instant-navigation validation.
```jsx filename="app/dashboard/layout.js"
export const unstable_instant = false
export default function DashboardLayout({ children }) {
return <DashboardShell>{children}</DashboardShell>
}
```
Learn more: [Route segment `instant` config](/docs/app/api-reference/file-conventions/route-segment-config/instant).
Use either pattern when:
- The route needs request-time data high in the tree to decide what to render (for example auth, tenant, or other gating in a layout), so there is no meaningful [static shell](/docs/app/glossary#static-shell) worth showing first.
- You're migrating a route incrementally and want to defer the lifetime decision without changing how the page renders today.
Don't use this to dismiss the error. Choose [Wrap in or move into Suspense](#wrap-in-or-move-into-suspense) or [For known params, prerender](#for-known-params-prerender) when either is feasible.
### Trade-off
Navigations to this route are not instant. The user waits for the full server render before any HTML arrives. Use this only when that latency is necessary for the route to function.
### Gotchas
- Setting [`unstable_instant`](/docs/app/api-reference/file-conventions/route-segment-config/instant) to `false` on a layout exempts every route in the subtree, not only the layout itself. Audit child routes before opting a shared layout out.
- This export does not disable [prerendering](/docs/app/glossary#prerendering). The route still prerenders if it can. It only silences the instant-navigation validation error.
## Don't want this validation?
Instant-navigation validation runs by default in [Cache Components](/docs/app/api-reference/config/next-config-js/cacheComponents) apps and is what surfaces this error.
- **One segment**: add [`export const unstable_instant = false`](/docs/app/api-reference/file-conventions/route-segment-config/instant) to the page or layout file. This opts out the segment itself. Child segments are still validated during client navigations.
- **Layout and its children**: add `export const unstable_instant = { unstable_disableValidation: true }` to a layout. This disables validation for that layout and every segment below it.
- **Entire app**: set [`experimental.instantInsights.validationLevel`](/docs/app/api-reference/file-conventions/route-segment-config/instant#configuring-validation-defaults) to `'manual-warning'` in `next.config`. This limits validation to segments that explicitly export `unstable_instant`.
See [Ensuring instant navigations](/docs/app/guides/instant-navigation) for the full model.
## Useful links
- [Ensuring instant navigations](/docs/app/guides/instant-navigation)
- [Next.js encountered uncached data during prerendering](/docs/messages/blocking-prerender-dynamic)
@@ -0,0 +1,133 @@
---
title: Next.js encountered uncached data in generateViewport()
kind: insight
---
During [prerendering](/docs/app/glossary#prerendering), [`generateViewport()`](/docs/app/api-reference/functions/generate-viewport) performed an uncached data access ([`fetch()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/fetch), database call, [`await connection()`](/docs/app/api-reference/functions/connection)). With [Cache Components](/docs/app/api-reference/config/next-config-js/cacheComponents) enabled, viewport metadata can't be deferred behind a [`<Suspense>`](https://react.dev/reference/react/Suspense) boundary because it affects the initial page load. The page can't be prerendered, so navigations block instead of being [instant](/docs/app/guides/instant-navigation).
Request-bound reads ([`cookies()`](/docs/app/api-reference/functions/cookies), [`headers()`](/docs/app/api-reference/functions/headers), [`params`](/docs/app/api-reference/file-conventions/page#params-optional), [`searchParams`](/docs/app/api-reference/file-conventions/page#searchparams-optional)) in `generateViewport()` have different fixes. See [Next.js encountered runtime data in `generateViewport()`](/docs/messages/blocking-prerender-viewport-runtime). The metadata equivalent is handled at [Uncached data in `generateMetadata()`](/docs/messages/blocking-prerender-metadata-dynamic). For errors in the page body rather than viewport, see [Next.js encountered uncached data during prerendering](/docs/messages/blocking-prerender-dynamic).
> **Good to know**: In [`next dev`](/docs/app/api-reference/cli/next#next-dev-options), the error overlay points at the failing component. Run `next build --debug-prerender` to get the full list of blocking routes with stack traces. When iterating on specific routes, use `next build --debug-build-paths /dashboard /settings` to rebuild only those pages.
## Ways to fix this
<FixOption
group="cache"
href="#cache-the-viewport-data"
prompt={`Add "use cache" as the first statement inside generateViewport(). This caches the viewport so Next.js can include it in the prerender. Optionally call cacheLife(profile) to set automatic expiration. Do not introduce new imports beyond "next/cache".`}
title="Cache the viewport data"
>
Cache the viewport function so the result is reused and the route stays
prerenderable.
</FixOption>
<FixOption
group="block"
href="#allow-blocking-route"
prompt={`Add "export const unstable_instant = false" as a top-level export in the page or layout file. This silences the warning for this segment. Confirm with the user that the route is intentionally fully dynamic before applying this change: the export exempts the segment from instant-navigation validation, and the route renders on every request.`}
title="Allow blocking route"
>
Opt this segment out of instant navigation. The route renders on every request
and every navigation blocks.
</FixOption>
## Cache the viewport data
Choose this fix when the viewport values come from an external source (database, CMS) but don't need to change on every request. Add the [`use cache`](/docs/app/api-reference/directives/use-cache) directive as the first statement inside [`generateViewport()`](/docs/app/api-reference/functions/generate-viewport). Next.js caches the returned viewport object and includes it in the prerender.
### Patterns
#### Add `use cache` to `generateViewport`
Mark the function as cacheable. The viewport is evaluated once per cache window and reused.
```jsx filename="app/layout.js"
import { db } from './db'
export async function generateViewport() {
'use cache'
const { width, initialScale } = await db.query('viewport-config')
return { width, initialScale }
}
export default function RootLayout({ children }) {
return (
<html>
<body>{children}</body>
</html>
)
}
```
Learn more: [Caching with `use cache`](/docs/app/api-reference/directives/use-cache).
### Trade-off
Freshness depends on the cache configuration. The viewport stays the same until [`cacheLife`](/docs/app/api-reference/functions/cacheLife) expires or [`cacheTag`](/docs/app/api-reference/functions/cacheTag) is invalidated.
### Gotchas
- Inside a [`use cache`](/docs/app/api-reference/directives/use-cache) scope, you can't call [`cookies()`](/docs/app/api-reference/functions/cookies) or [`headers()`](/docs/app/api-reference/functions/headers). If the viewport needs a request-bound value (a theme color from a cookie), use [Allow blocking route](#allow-blocking-route) instead.
- A short [`cacheLife`](/docs/app/api-reference/functions/cacheLife) (a profile whose `revalidate` is shorter than the prerender's effective lifetime) prevents the viewport from being included in the prerender. Use a longer profile if you want the viewport included in the static shell.
## Allow blocking route
Choose this fix when the viewport data is genuinely uncacheable. Setting [`unstable_instant`](/docs/app/api-reference/file-conventions/route-segment-config/instant) to `false` exempts the segment from instant-navigation validation. The page renders on every request and the navigation blocks until that render completes.
Unlike page body content, viewport metadata can't be deferred behind [`<Suspense>`](https://react.dev/reference/react/Suspense) because it affects the initial HTML `<head>`. Making the viewport dynamic means the entire page navigation blocks.
### Patterns
#### Opt the layout out
Set [`unstable_instant`](/docs/app/api-reference/file-conventions/route-segment-config/instant) to `false` on the layout that defines `generateViewport`. The layout and every route in its subtree are exempted from instant-navigation validation. Apply this to the nested layout that owns the dynamic viewport, not the root layout, so the opt-out is scoped to the affected subtree.
```jsx filename="app/dashboard/layout.js"
import { db } from './db'
export const unstable_instant = false
export async function generateViewport() {
const { width, initialScale } = await db.query('viewport-config')
return { width, initialScale }
}
export default function DashboardLayout({ children }) {
return children
}
```
Learn more: [Route segment `instant` config](/docs/app/api-reference/file-conventions/route-segment-config/instant).
Use this pattern when:
- The viewport data is uncacheable and must be fetched on every request.
- You're migrating a route incrementally and want to defer the lifetime decision without changing how the page renders today.
Don't use this to dismiss the error. Choose [Cache the viewport data](#cache-the-viewport-data) when feasible.
### Trade-off
Navigations to this route are not instant. The user waits for the full server render before any HTML arrives. Use this only when that latency is necessary for the route to function.
### Gotchas
- Setting [`unstable_instant`](/docs/app/api-reference/file-conventions/route-segment-config/instant) to `false` on a layout exempts every route in the subtree, not only the layout itself. Audit child routes before opting a shared layout out.
- This export does not disable [prerendering](/docs/app/glossary#prerendering). The route still prerenders if it can. It only silences the instant-navigation validation error.
- Framework-synthesized routes (`/_not-found`, `/_global-error`) inherit the root layout's `generateViewport` and must be statically prerendered. [`unstable_instant = false`](/docs/app/api-reference/file-conventions/route-segment-config/instant) silences the validation error but does not let those routes through, so the build still fails when they prerender. If your root layout's `generateViewport` depends on request data, [Cache the viewport data](#cache-the-viewport-data) instead, or move to [`global-not-found.js`](/docs/app/api-reference/file-conventions/not-found#global-not-foundjs-experimental), which bypasses the root layout entirely and avoids inheriting its `generateViewport`.
## Don't want this validation?
Instant-navigation validation runs by default in [Cache Components](/docs/app/api-reference/config/next-config-js/cacheComponents) apps and is what surfaces this error.
- **One segment**: add [`export const unstable_instant = false`](/docs/app/api-reference/file-conventions/route-segment-config/instant) to the page or layout file. This opts out the segment itself. Child segments are still validated during client navigations.
- **Layout and its children**: add `export const unstable_instant = { unstable_disableValidation: true }` to a layout. This disables validation for that layout and every segment below it.
- **Entire app**: set [`experimental.instantInsights.validationLevel`](/docs/app/api-reference/file-conventions/route-segment-config/instant#configuring-validation-defaults) to `'manual-warning'` in `next.config`. This limits validation to segments that explicitly export `unstable_instant`.
See [Ensuring instant navigations](/docs/app/guides/instant-navigation) for the full model.
## Useful links
- [Ensuring instant navigations](/docs/app/guides/instant-navigation)
- [Runtime data in `generateViewport()`](/docs/messages/blocking-prerender-viewport-runtime)
- [Uncached data in `generateMetadata()`](/docs/messages/blocking-prerender-metadata-dynamic)
@@ -0,0 +1,134 @@
---
title: Next.js encountered runtime data in generateViewport()
kind: insight
---
During [prerendering](/docs/app/glossary#prerendering), [`generateViewport()`](/docs/app/api-reference/functions/generate-viewport) read a per-request value ([`cookies()`](/docs/app/api-reference/functions/cookies), [`headers()`](/docs/app/api-reference/functions/headers), [`params`](/docs/app/api-reference/file-conventions/page#params-optional), [`searchParams`](/docs/app/api-reference/file-conventions/page#searchparams-optional)). With [Cache Components](/docs/app/api-reference/config/next-config-js/cacheComponents) enabled, viewport metadata can't be deferred behind a [`<Suspense>`](https://react.dev/reference/react/Suspense) boundary because it affects the initial page load. The page can't be prerendered, so navigations block instead of being [instant](/docs/app/guides/instant-navigation).
Uncached data accesses ([`fetch()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/fetch), database calls, [`await connection()`](/docs/app/api-reference/functions/connection)) in `generateViewport()` have different fixes. See [Next.js encountered uncached data in `generateViewport()`](/docs/messages/blocking-prerender-viewport-dynamic). The metadata equivalent is handled at [Runtime data in `generateMetadata()`](/docs/messages/blocking-prerender-metadata-runtime). For errors in the page body rather than viewport, see [Next.js encountered runtime data during prerendering](/docs/messages/blocking-prerender-runtime).
> **Good to know**: In [`next dev`](/docs/app/api-reference/cli/next#next-dev-options), the error overlay points at the failing component. Run `next build --debug-prerender` to get the full list of blocking routes with stack traces. When iterating on specific routes, use `next build --debug-build-paths /dashboard /settings` to rebuild only those pages.
## Ways to fix this
<FixOption
group="static"
href="#use-static-viewport"
prompt={`Replace the generateViewport() function with a static viewport export. Convert all dynamic values to static ones. Do not introduce new imports.`}
title="Use static viewport"
>
Replace the dynamic function with a static export so the viewport is known at
build time.
</FixOption>
<FixOption
group="block"
href="#allow-blocking-route"
prompt={`Add "export const unstable_instant = false" as a top-level export in the page or layout file. This silences the warning for this segment. Confirm with the user that the route is intentionally fully dynamic before applying this change: the export exempts the segment from instant-navigation validation, and the route renders on every request.`}
title="Allow blocking route"
>
Opt this segment out of instant navigation. The route renders on every request
and every navigation blocks.
</FixOption>
## Use static viewport
Choose this fix when the viewport doesn't actually need the per-request data, either because the values are known at build time, or because the dependency on runtime data is accidental and can be refactored away. Replace [`generateViewport()`](/docs/app/api-reference/functions/generate-viewport) with a static [`viewport`](/docs/app/api-reference/functions/generate-viewport#the-viewport-object) export, or rewrite `generateViewport()` so it no longer reads [`cookies()`](/docs/app/api-reference/functions/cookies), [`headers()`](/docs/app/api-reference/functions/headers), or other request-bound APIs. The values are evaluated once during the build.
### Patterns
#### Export a static object
Replace the function with a plain object export.
```jsx filename="app/layout.js"
export const viewport = {
themeColor: '#000000',
width: 'device-width',
initialScale: 1,
}
export default function RootLayout({ children }) {
return (
<html>
<body>{children}</body>
</html>
)
}
```
Learn more: [Static viewport](/docs/app/api-reference/functions/generate-viewport#the-viewport-object).
### Trade-off
Static viewport can't reflect per-request values like a user's preferred theme color stored in a cookie. If the viewport must be personalized, use [Allow blocking route](#allow-blocking-route).
### Gotchas
- The `viewport` export is typically set in the root layout. Changing a layout's viewport affects every route in its subtree.
- [`themeColor`](/docs/app/api-reference/functions/generate-viewport#themecolor) is the most common reason for a dynamic `generateViewport`. Consider whether a single static value covers all cases before resorting to a blocking route.
## Allow blocking route
Choose this fix when the viewport genuinely requires per-request data (a theme color from a cookie, a user-preferred width) and a static export isn't feasible. Setting [`unstable_instant`](/docs/app/api-reference/file-conventions/route-segment-config/instant) to `false` exempts the segment from instant-navigation validation. The page renders on every request and the navigation blocks until that render completes.
Unlike page body content, viewport metadata can't be deferred behind [`<Suspense>`](https://react.dev/reference/react/Suspense) because it affects the initial HTML `<head>`. Making the viewport dynamic means the entire page navigation blocks.
### Patterns
#### Opt the layout out
Set [`unstable_instant`](/docs/app/api-reference/file-conventions/route-segment-config/instant) to `false` on the layout that defines `generateViewport`. The layout and every route in its subtree are exempted from instant-navigation validation. Apply this to the nested layout that owns the dynamic viewport, not the root layout, so the opt-out is scoped to the affected subtree.
```jsx filename="app/dashboard/layout.js"
import { cookies } from 'next/headers'
export const unstable_instant = false
export async function generateViewport() {
const cookieJar = await cookies()
return {
themeColor: cookieJar.get('theme-color')?.value ?? '#000',
}
}
export default function DashboardLayout({ children }) {
return children
}
```
Learn more: [Route segment `instant` config](/docs/app/api-reference/file-conventions/route-segment-config/instant).
Use this pattern when:
- The viewport is personalized per user and the value must be correct on the first paint (no flash).
- You're migrating a route incrementally and want to defer the lifetime decision without changing how the page renders today.
Don't use this to dismiss the error. Choose [Use static viewport](#use-static-viewport) when feasible.
### Trade-off
Navigations to this route are not instant. The user waits for the full server render before any HTML arrives. Use this only when that latency is necessary for the route to function.
### Gotchas
- Setting [`unstable_instant`](/docs/app/api-reference/file-conventions/route-segment-config/instant) to `false` on a layout exempts every route in the subtree, not only the layout itself. Audit child routes before opting a shared layout out.
- This export does not disable [prerendering](/docs/app/glossary#prerendering). The route still prerenders if it can. It only silences the instant-navigation validation error.
- If the dynamic viewport is the only reason the route blocks, consider whether a static default covers most users. A static `themeColor` with a client-side correction after hydration may give a better experience than blocking the entire navigation.
- Framework-synthesized routes (`/_not-found`, `/_global-error`) inherit the root layout's `generateViewport` and must be statically prerendered. [`unstable_instant = false`](/docs/app/api-reference/file-conventions/route-segment-config/instant) silences the validation error but does not let those routes through, so the build still fails when they prerender. If your root layout's `generateViewport` depends on request data, [Use static viewport](#use-static-viewport) instead, or move to [`global-not-found.js`](/docs/app/api-reference/file-conventions/not-found#global-not-foundjs-experimental), which bypasses the root layout entirely and avoids inheriting its `generateViewport`.
## Don't want this validation?
Instant-navigation validation runs by default in [Cache Components](/docs/app/api-reference/config/next-config-js/cacheComponents) apps and is what surfaces this error.
- **One segment**: add [`export const unstable_instant = false`](/docs/app/api-reference/file-conventions/route-segment-config/instant) to the page or layout file. This opts out the segment itself. Child segments are still validated during client navigations.
- **Layout and its children**: add `export const unstable_instant = { unstable_disableValidation: true }` to a layout. This disables validation for that layout and every segment below it.
- **Entire app**: set [`experimental.instantInsights.validationLevel`](/docs/app/api-reference/file-conventions/route-segment-config/instant#configuring-validation-defaults) to `'manual-warning'` in `next.config`. This limits validation to segments that explicitly export `unstable_instant`.
See [Ensuring instant navigations](/docs/app/guides/instant-navigation) for the full model.
## Useful links
- [Ensuring instant navigations](/docs/app/guides/instant-navigation)
- [Uncached data in `generateViewport()`](/docs/messages/blocking-prerender-viewport-dynamic)
- [Runtime data in `generateMetadata()`](/docs/messages/blocking-prerender-metadata-runtime)
+151
View File
@@ -0,0 +1,151 @@
---
title: Next.js could not validate that a segment in your UI has instant navigation
kind: insight
---
During [prerendering](/docs/app/glossary#prerendering), a segment in the route tree was dropped from rendering. With [Cache Components](/docs/app/api-reference/config/next-config-js/cacheComponents) enabled, Next.js validates that every segment can produce an [instant navigation](/docs/app/guides/instant-navigation). When a segment is not rendered, that validation can't run and issues that would prevent instant navigation go undetected.
This typically happens when a layout conditionally omits `{children}`, a parallel route slot is not rendered, or a Client Component opts out of rendering during SSR.
> **Good to know**: In [`next dev`](/docs/app/api-reference/cli/next#next-dev-options), the error overlay points at the failing component. Run `next build --debug-prerender` to get the full list of blocking routes with stack traces. When iterating on specific routes, use `next build --debug-build-paths /dashboard /settings` to rebuild only those pages.
## Ways to fix this
<FixOption
group="render"
href="#render-the-dropped-segment"
prompt={`Ensure the layout renders {children} so the dropped segment is included in the render tree. If the layout conditionally omits {children} (e.g. showing a login page instead), restructure so both branches render {children} and use a Suspense boundary or conditional content inside the child segment instead. If the segment is a parallel route slot, ensure the layout renders the slot prop.`}
title="Render the dropped segment"
>
Include the segment in the render tree so Next.js can validate it for instant
navigation.
</FixOption>
<FixOption
group="ignore"
href="#skip-validation-on-the-segment"
prompt={`Add "export const unstable_instant = false" as a top-level export in the dropped segment's page or layout file. This silences the warning for the dropped segment and tells Next.js the segment does not need instant-navigation validation. Confirm with the user that skipping validation is intentional before applying this change.`}
title="Skip validation on the segment"
>
Opt the dropped segment out of instant-navigation validation.
</FixOption>
## Render the dropped segment
Choose this fix when the segment should be part of the render tree. The layout that owns the segment needs to render `{children}` (or the parallel route slot prop) so Next.js can validate the subtree for instant navigation.
### Patterns
#### Render `{children}` in the layout
Make sure the layout always includes `{children}` in its output. If the layout conditionally shows different content (a login page when unauthenticated, a dashboard when authenticated), render `{children}` in both branches and handle the conditional inside the child segment.
```jsx filename="app/dashboard/layout.js"
export default function DashboardLayout({ children }) {
return (
<>
<Nav />
{children}
</>
)
}
```
#### Render the parallel route slot
When the dropped segment is a parallel route (e.g. `@modal`), the layout must render the slot prop. If the slot should be hidden in certain states, render it conditionally inside the slot's own page rather than omitting the prop from the layout.
```jsx filename="app/dashboard/layout.js"
export default function DashboardLayout({ children, modal }) {
return (
<>
{children}
{modal}
</>
)
}
```
Learn more: [Parallel Routes](/docs/app/api-reference/file-conventions/parallel-routes).
#### Move auth or guard checks into the page
A common cause is a layout that conditionally returns a sign-in screen (or redirects) instead of rendering `{children}`. Layouts and pages render separately, so put the guard at the page (or slot) level rather than in the layout. The layout always renders `{children}`; each page decides whether to render its content or redirect.
```jsx filename="app/dashboard/layout.js"
export default function DashboardLayout({ children }) {
return (
<>
<Nav />
{children}
</>
)
}
```
```jsx filename="app/dashboard/page.js"
import { redirect } from 'next/navigation'
import { getSession } from '@/lib/session'
export default async function DashboardPage() {
const session = await getSession()
if (!session) redirect('/login')
return <Dashboard session={session} />
}
```
Learn more: [Authentication](/docs/app/guides/authentication).
### Trade-off
The segment is always in the render tree, which means Next.js validates it on every dev render. If the segment has dynamic data, it needs its own [`<Suspense>`](https://react.dev/reference/react/Suspense) boundary or caching strategy to stay prerenderable.
### Gotchas
- A layout that conditionally returns early without rendering `{children}` (e.g. a redirect guard) drops every segment in the subtree. Move the guard into a wrapper component inside `{children}` instead.
- A Client Component that returns `null` during SSR also drops its children from the render tree, triggering this error. Use a [`<Suspense>`](https://react.dev/reference/react/Suspense) boundary above the Client Component so the fallback renders in place of the skipped subtree.
## Skip validation on the segment
Choose this fix when the segment is intentionally not rendered in certain states (a modal that only appears on interaction, a slot gated by authentication). Setting [`unstable_instant`](/docs/app/api-reference/file-conventions/route-segment-config/instant) to `false` on the dropped segment tells Next.js to skip validation for it.
### Patterns
#### Opt the dropped segment out
Add the export to the page or layout file of the segment that was dropped from rendering.
```jsx filename="app/dashboard/@modal/page.js"
export const unstable_instant = false
export default function ModalPage() {
return <Modal />
}
```
Learn more: [Route segment `instant` config](/docs/app/api-reference/file-conventions/route-segment-config/instant).
### Trade-off
The segment is exempt from instant-navigation validation. If it has issues that would block navigation (uncached data outside Suspense, runtime APIs), those issues won't be caught during development.
### Gotchas
- The export must be on the **dropped segment's own file** (page or layout), not on a parent. The framework walks top-down and the first explicit config wins.
- Setting [`unstable_instant`](/docs/app/api-reference/file-conventions/route-segment-config/instant) to `false` does not disable [prerendering](/docs/app/glossary#prerendering). The segment still prerenders if it can. It only silences the validation error.
## Don't want this validation?
Instant-navigation validation runs by default in [Cache Components](/docs/app/api-reference/config/next-config-js/cacheComponents) apps and is what surfaces this error.
- **One segment**: add [`export const unstable_instant = false`](/docs/app/api-reference/file-conventions/route-segment-config/instant) to the page or layout file. This opts out the segment itself. Child segments are still validated during client navigations.
- **Layout and its children**: add `export const unstable_instant = { unstable_disableValidation: true }` to a layout. This disables validation for that layout and every segment below it.
- **Entire app**: set [`experimental.instantInsights.validationLevel`](/docs/app/api-reference/file-conventions/route-segment-config/instant#configuring-validation-defaults) to `'manual-warning'` in `next.config`. This limits validation to segments that explicitly export `unstable_instant`.
See [Ensuring instant navigations](/docs/app/guides/instant-navigation) for the full model.
## Useful links
- [Parallel Routes](/docs/app/api-reference/file-conventions/parallel-routes)
- [Ensuring instant navigations](/docs/app/guides/instant-navigation)
- [Route segment `instant` config](/docs/app/api-reference/file-conventions/route-segment-config/instant)