errors/blocking-prerender-random.mdx
During prerendering, a Server Component called Math.random() outside of <Suspense>. With Cache Components 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> boundary so it runs per-request, or move it to the client.
Other unpredictable APIs (Date.now(), crypto.randomUUID()) have parallel error pages: see Date.now() and crypto APIs. The Client Component case is handled at Math.random() in a Client Component.
Choose this fix when each request genuinely needs a different value. A unique session ID, a single-use nonce, an A/B test bucket: anything that has to be fresh per visitor. Add await 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> boundary on each request.
await connection() before the random callCall connection() before Math.random(). Everything after the await is request-time. Wrap the component in <Suspense> so the surrounding shell stays prerendered and only the dynamic part streams in.
Push the <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.
import { Suspense } from 'react'
export default function Page() {
return (
<DashboardShell>
<Suspense fallback={<TraceSkeleton />}>
<RequestTrace />
</Suspense>
<CachedStats />
</DashboardShell>
)
}
import { connection } from 'next/server'
export async function RequestTrace() {
await connection()
const traceId = Math.random().toString(16).slice(2)
return <small>trace: {traceId}</small>
}
await io()Use io() from next/cache to keep the read out of the static shell. Unlike connection(), io() doesn't block prefetches and works inside "use cache" scopes and Client Components.
import { io } from 'next/cache'
export async function RequestTrace() {
await io()
const traceId = Math.random().toString(16).slice(2)
return <small>trace: {traceId}</small>
}
Learn more: connection, io, Streaming.
The route renders on every request. The shell still ships instantly because of the <Suspense> boundary, but the dynamic region waits on the server render before it can paint. Make sure the fallback approximates the final layout so it doesn't cause a layout shift when the value arrives. See minimizing layout shift.
<Suspense> fallbacks, loading.js, error.js, not-found.js, and global-error.js. Calling Math.random() or Date.now() in any of them raises this same error.Math.random() is being used as a unique ID for logging or correlation, consider an incrementing integer or AsyncLocalStorage request scope. Those don't trigger the error at all because they aren't unpredictable from Next.js's point of view.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() call into a function with use cache as the first statement. Next.js evaluates the function once per cache key and reuses the result.
Wrap the random generation in its own function with use cache. The returned value is part of the cache entry, so every consumer sees the same random number until the cache is invalidated.
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.
cacheLifeWhen you want the random value to rotate on a schedule (a daily featured item, an hourly shuffle), set a cacheLife profile.
import { cacheLife } from 'next/cache'
async function getDailySeed() {
'use cache'
cacheLife('days')
return Math.random()
}
Learn more: How to configure cache lifetimes.
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.
use cache scope, you can't call cookies() or headers(), which means you can't key the random value by request identity.cacheLife may be too short to prerender. See Short-lived caches.use cache accepts a 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 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 instead.
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 so the value is produced after hydration, not during prerender.
useEffectAdd the use client directive. Initialize state to a deterministic placeholder and assign the real value inside useEffect, which runs only in the browser after hydration.
'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 }} />
}
<Suspense> boundaryIf the random value needs to be part of the server-rendered HTML (not deferred to after hydration), the component can call Math.random() during render as long as a <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 for the full recipe.
Learn more: Client Components, Math.random() in a Client Component.
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 for techniques that eliminate the flash.
Math.random() in a Client Component page for the <Suspense> and effect-based recipes.After applying a fix, reload the route and confirm the page immediately paints meaningful UI, with any <Suspense> fallbacks covering only the regions that stream in. A <Suspense> boundary placed around the whole page body can pass validation with an empty shell, which defeats the point of an instant navigation.
In next dev, the error overlay points at the failing component with file paths and line numbers. When working from a build instead, the default next build output is more abbreviated. Run next build --debug-prerender for full user-frame stack traces and next build --debug-build-paths /dashboard /settings to iterate on specific routes.
instant = false doesn't clear this errorThis error fires from the prerender, not from instant-navigation validation. Math.random() returns a different value on every render, so the prerender can't bake it into a static shell regardless of the segment's instant config or experimental.instantInsights.validationLevel. Use one of the fixes above.
generateMetadata()generateMetadata()generateViewport()generateViewport()Math.random() in a Client ComponentDate.now() while prerenderingDate.now() in a Client Component