Files
vercel__next.js/errors/blocking-prerender-current-time.mdx
T
Aurora Scharff ab4b1918c7 docs(cache-components): clarify allow-runtime, sync-IO and instant=false, CLS fallback (#94997)
### What?

Docs-only follow-ups for Cache Components and Partial Prefetching, from
agent migration friction logs and Joseph's review of
[#95082](https://github.com/vercel/next.js/pull/95082).

- **`adopting-partial-prefetching.mdx`** — reframe the `allow-runtime`
row in the audit table (it's an enhancement, not the `<Link
prefetch={true}>` warning fix; the per-route opt-in is `prefetch =
'partial'`).
- **`migrating-to-cache-components.mdx`** — split the `## Enable Cache
Components` section into before-flag (remove `dynamic` / `revalidate` /
`fetchCache`) and after-flag (translate `revalidate`, `fetchCache`,
`unstable_cache`, `fetch` cache options) steps. Flag the `revalidateTag`
second-argument requirement, with a Before / After example. Add two
Good-to-know callouts: off-grid `revalidate` values map to the closest
`cacheLife` profile; `instant = false` is allowed-to-block, not
forced-dynamic.
- **`blocking-prerender-current-time` / `-random` / `-crypto`** (and
their `-client` siblings) — drop the misleading "Don't want this
validation?" opt-out section. `instant = false` is an instant-navigation
knob and doesn't suppress sync-IO prerender errors.
- **Remaining `blocking-prerender-*` pages** — standardize the CLS
fallback callout so they all link the canonical section.

### Why?

Friction logs surfaced four recurring misreads:

1. Devs expected `prefetch = 'allow-runtime'` to silence the `<Link
prefetch={true}>` warning. The docs implied it would.
2. The migration guide mixed `dynamic` removal with `unstable_cache`
translation, so it wasn't clear which had to happen before the flag and
which after.
3. `revalidateTag` now requires a cache profile, but the on-demand
revalidation section didn't say so.
4. Sync-IO error pages still carried a "Don't want this validation?"
opt-out that doesn't actually work for those errors.
2026-06-24 10:55:45 +02:00

295 lines
16 KiB
Plaintext

---
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"
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"
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"
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"
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>
}
```
#### Alternative: `await io()`
To avoid tying the read to an HTTP request context, use [`io()`](/docs/app/api-reference/functions/io) from `next/cache`. It marks the read as request-time IO and behaves like `connection()` for streaming, with the added benefit that it also works inside `"use cache"` scopes and Client Components, where `connection()` throws.
```jsx filename="app/dashboard/updated-at.js"
import { io } from 'next/cache'
export async function UpdatedAt() {
await io()
return <small>Updated at {new Date().toLocaleString()}</small>
}
```
Learn more: [`io`](/docs/app/api-reference/functions/io), [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. Design the fallback so it approximates the final layout. A generic spinner or empty box causes the page to visibly jump when the content arrives. See [CLS-safe skeleton fallback guidance](/docs/app/guides/streaming#cls-cumulative-layout-shift).
### Gotchas
- Any UI rendered as part of the prerender shell must be deterministic. That includes [`<Suspense>`](https://react.dev/reference/react/Suspense) fallbacks, [`loading.js`](/docs/app/api-reference/file-conventions/loading), [`error.js`](/docs/app/api-reference/file-conventions/error), [`not-found.js`](/docs/app/api-reference/file-conventions/not-found), and [`global-error.js`](/docs/app/api-reference/file-conventions/error#global-error). Calling [`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.
## Why `instant = false` doesn't clear this error
This error fires from the prerender, not from instant-navigation validation. `new Date()` and `Date.now()` return a different value on every render, so the prerender can't bake them into a static shell regardless of the segment's `instant` config or [`experimental.instantInsights.validationLevel`](/docs/app/api-reference/file-conventions/route-segment-config/instant#configuring-validation-defaults). Use one of the fixes above.
## 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)