mirror of
https://github.com/hanzoai/next.js.git
synced 2026-09-24 17:00:51 +00:00
This API has experimental modes for build validation but the dev validation is going to be stable in the next release.
303 lines
18 KiB
Plaintext
303 lines
18 KiB
Plaintext
---
|
|
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 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 [`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 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 [`instant`](/docs/app/api-reference/file-conventions/route-segment-config/instant) to `false` on the layout. This allows that layout segment to block while descendant segments remain independently validated.
|
|
|
|
```jsx filename="app/dashboard/layout.js"
|
|
export const 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 [`instant`](/docs/app/api-reference/file-conventions/route-segment-config/instant) to `false` opts only the segment that exports it out. Descendant segments are still validated by the global default. See [Don't want this validation?](#dont-want-this-validation) for the subtree-wide opt-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 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 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 `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)
|