Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions errors/next-prerender-crypto.mdx
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
---
title: Cannot access `crypto.getRandomValue()`, `crypto.randomUUID()`, or another web or node crypto API that generates random values synchronously in a Server Component
title: Cannot access `crypto.getRandomValue()`, `crypto.randomUUID()`, or another web or node crypto API that generates random values synchronously before other uncached data or Request data in a Server Component
---

## Why This Error Occurred

An API that produces a random value synchronously from the Web Crypto API or from Node's `crypto` API was called outside of a `"use cache"` scope and without first calling `await connection()`. Random values that are produced synchronously must either be inside a `"use cache"` scope or be preceded with `await connection()` to explicitly communicate to Next.js whether the random values produced can be reused across many requests (cached) or if they must be unique per Request (`await connection()`).

If the API used has an async version you can also switch to that instead of using `await connection()`.
An API that produces a random value synchronously from the Web Crypto API or from Node's `crypto` package was used in a Server Component before accessing other uncached data through APIs like `fetch()` and native database drivers, or Request data through Next.js APIs like `cookies()`, `headers()`, `connection()` and `searchParams`. Accessing random values synchronously in this way interferes with the prerendering and prefetching capabilities of Next.js.

## Possible Ways to Fix It

If the random crypto value is appropriate to be prerendered and prefetched consider moving it into a Cache Component or Cache Function with the `"use cache"` directive.

If the random crypto value is intended to be generated on every user request consider whether an async API exists that achieves the same result. If not consider whether you can move the random crypto value generation later, behind other existing uncached data or Request data access. If there is no way to do this you can always precede the random crypto value generation with Request data access by using `await connection()`.

### Cache the token value

If you are generating a token to talk to a database that itself should be cached move the token generation inside the `"use cache"`.
Expand Down
17 changes: 13 additions & 4 deletions errors/next-prerender-current-time.mdx
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
---
title: Cannot infer intended usage of current time with `Date.now()`, `Date()`, or `new Date()` in a Server Component
title: Cannot access `Date.now()`, `Date()`, or `new Date()` before other uncached data or Request data in a Server Component
---

## Why This Error Occurred

Reading the current time in a Server Component can be ambiguous. Sometimes you intend to capture the time when something was cached, other times you intend to capture the time of a user Request. You might also be trying to measure runtime performance to track elapsed time.

Depending on your use case you might use alternative time APIs like `performance.now()`, you might cache the time read with `"use cache"`, or you might communicate that the time must be evaluated on each request by guarding it with `await connection()` or moving it into a Client Component.
`Date.now()`, `Date()`, or `new Date()` was used in a Server Component before accessing other uncached data through APIs like `fetch()` and native database drivers, or Request data through built-in APIs like `cookies()`, `headers()`, `connection()` and `searchParams`. Accessing the current time in this way interferes with the prerendering and prefetching capabilities of Next.js.

## Possible Ways to Fix It

If the current time is being used for diagnostic purposes such as logging or performance tracking consider using `performance.now()` instead.

If the current time is appropriate to be prerendered and prefetched consider moving it into a Cache Component or Cache Function with the `"use cache"` directive.

If the current time is intended to be accessed dynamically on every user request first consider whether it is more appropriate to access it in a Client Component, which can often be the case when reading the time for display purposes. If a Client Component isn't the right choice then consider whether you can move the current time access later, behind other existing uncached data or Request data access. If there is no way to do this you can always precede the current time access with Request data access by using `await connection()`.

> **Note**: Sometimes the place that accesses the current time is inside 3rd party code. While you can't easily convert the time access to `performance.now()` the other strategies can be applied in your own project code regardless of how deeply the time is read.
### Performance use case

If you are using the current time for performance tracking with elapsed time use `performance.now()`.
Expand Down Expand Up @@ -40,6 +46,7 @@ export default async function Page() {
```

> **Note**: If you need report an absolute time to an observability tool you can also use `performance.timeOrigin + performance.now()`.
> **Note**: It is essential that the values provided by `performance.now()` do not influence the rendered output of your Component and should never be passed into Cache Functions as arguments or props.
### Cacheable use cases

Expand Down Expand Up @@ -214,6 +221,8 @@ async function DynamicBanner() {
}
```

> **Note**: This example illustrates using `await connection()`, but you could alternatively move where a uncached fetch happens or read cookies before as well.
## Useful Links

- [`Date.now` API](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now)
Expand Down
12 changes: 10 additions & 2 deletions errors/next-prerender-random.mdx
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
---
title: Cannot infer intended usage of `Math.random()` in a Server Component
title: Cannot access `Math.random()` before other uncached data or Request data in a Server Component
---

## Why This Error Occurred

A Server Component is calling `Math.random()` without specifying whether it should be cached or whether it should be evaluated on each user Request. If you want this random value to be included in the prerendered HTML for this page you must cache it using `"use cache"`. If you want this random value to be unique per Request you must precede it with `await connection()` so Next.js knows to exclude it from the prerendered HTML.
`Math.random()` was called in a Server Component before accessing other uncached data through APIs like `fetch()` and native database drivers, or Request data through built-in APIs like `cookies()`, `headers()`, `connection()` and `searchParams`. Accessing random values in this way interferes with the prerendering and prefetching capabilities of Next.js.

## Possible Ways to Fix It

If the random value is appropriate to be prerendered and prefetched consider moving it into a Cache Component or Cache Function with the `"use cache"` directive.

If the random value is intended to be generated on every user request consider whether you can move the random value generation later, behind other existing uncached data or Request data access. If there is no way to do this you can always precede the random value generation with Request data access by using `await connection()`.

If the random value is being used as a unique identifier for diagnostic purposes such as logging or tracking consider using an alternate method of id generation that does not rely on random number generation such as incrementing an integer.

> **Note**: Sometimes the place that generates a random value synchronously is inside 3rd party code. While you can't easily replace the `Math.random()` call directly, the other strategies can be applied in your own project code regardless of how deep random generation is.

### Cache the random value

If your random value is cacheable, move the `Math.random()` call to a `"use cache"` function. For instance, imagine you have a product page and you want to randomize the product order periodically but you are fine with the random order being re-used for different users.
Expand Down
3 changes: 0 additions & 3 deletions packages/next/src/server/app-render/app-render.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,6 @@ import {
consumeDynamicAccess,
type DynamicAccess,
logDisallowedDynamicError,
warnOnSyncDynamicError,
} from './dynamic-rendering'
import {
getClientComponentLoaderMetrics,
Expand Down Expand Up @@ -909,8 +908,6 @@ async function finalRuntimeServerPrerender(
}
)

warnOnSyncDynamicError(serverDynamicTracking)

return {
result,
// TODO(runtime-ppr): do we need to produce a digest map here?
Expand Down
34 changes: 8 additions & 26 deletions packages/next/src/server/app-render/dynamic-rendering.ts
Original file line number Diff line number Diff line change
Expand Up @@ -341,21 +341,6 @@ export function abortAndThrowOnSynchronousRequestDataAccess(
)
}

/**
* Use this function when dynamically prerendering with dynamicIO.
* We don't want to error, because it's better to return something
* (and we've already aborted the render at the point where the sync dynamic error occured),
* but we should log an error server-side.
* @internal
*/
export function warnOnSyncDynamicError(dynamicTracking: DynamicTrackingState) {
if (dynamicTracking.syncDynamicErrorWithStack) {
// the server did something sync dynamic, likely
// leading to an early termination of the prerender.
console.error(dynamicTracking.syncDynamicErrorWithStack)
}
}

// For now these implementations are the same so we just reexport
export const trackSynchronousRequestDataAccessInDev =
trackSynchronousPlatformIOAccessInDev
Expand Down Expand Up @@ -778,6 +763,14 @@ export function throwIfDisallowedDynamic(
dynamicValidation: DynamicValidationState,
serverDynamic: DynamicTrackingState
): void {
if (serverDynamic.syncDynamicErrorWithStack) {
logDisallowedDynamicError(
workStore,
serverDynamic.syncDynamicErrorWithStack
)
throw new StaticGenBailoutError()
}
Comment on lines +766 to +772
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Critical behavioral change: sync dynamic errors now fail immediately even when properly wrapped in Suspense boundaries, breaking previously valid patterns.

View Details
📝 Patch Details
diff --git a/packages/next/src/server/app-render/dynamic-rendering.ts b/packages/next/src/server/app-render/dynamic-rendering.ts
index 2c88cd8e0b..492512254d 100644
--- a/packages/next/src/server/app-render/dynamic-rendering.ts
+++ b/packages/next/src/server/app-render/dynamic-rendering.ts
@@ -763,14 +763,6 @@ export function throwIfDisallowedDynamic(
   dynamicValidation: DynamicValidationState,
   serverDynamic: DynamicTrackingState
 ): void {
-  if (serverDynamic.syncDynamicErrorWithStack) {
-    logDisallowedDynamicError(
-      workStore,
-      serverDynamic.syncDynamicErrorWithStack
-    )
-    throw new StaticGenBailoutError()
-  }
-
   if (prelude !== PreludeState.Full) {
     if (dynamicValidation.hasSuspenseAboveBody) {
       // This route has opted into allowing fully dynamic rendering
@@ -779,6 +771,14 @@ export function throwIfDisallowedDynamic(
       return
     }
 
+    if (serverDynamic.syncDynamicErrorWithStack) {
+      logDisallowedDynamicError(
+        workStore,
+        serverDynamic.syncDynamicErrorWithStack
+      )
+      throw new StaticGenBailoutError()
+    }
+
     // We didn't have any sync bailouts but there may be user code which
     // blocked the root. We would have captured these during the prerender
     // and can log them here and then terminate the build/validating render

Analysis

The logic in throwIfDisallowedDynamic was changed to check serverDynamic.syncDynamicErrorWithStack before checking the prelude state and Suspense boundaries. This is a breaking change that contradicts the intended behavior.

Previous behavior (correct): Sync dynamic errors (like Math.random()) were only treated as failures when there wasn't proper Suspense boundary coverage (prelude !== PreludeState.Full). This allowed pages with proper Suspense boundaries to use these APIs safely during prerendering.

New behavior (incorrect): Sync dynamic errors now always cause immediate failures regardless of Suspense boundary coverage, breaking pages that were previously working correctly.

The test case /sync-random-with-fallback demonstrates this - its comment explicitly states "This is fine and doesn't need to error the build" because it has proper Suspense boundaries, but the behavior change makes it fail. The test expectations were updated to match the new broken behavior instead of fixing the logic bug.

This affects real applications where developers have correctly wrapped dynamic operations in Suspense boundaries as recommended by Next.js documentation, but those patterns will now start failing after this change.


Recommendation

Move the syncDynamicErrorWithStack check back inside the if (prelude !== PreludeState.Full) block, before the hasSuspenseAboveBody check, to restore the correct behavior:

export function throwIfDisallowedDynamic(
  workStore: WorkStore,
  prelude: PreludeState,
  dynamicValidation: DynamicValidationState,
  serverDynamic: DynamicTrackingState
): void {
  if (prelude !== PreludeState.Full) {
    if (dynamicValidation.hasSuspenseAboveBody) {
      // This route has opted into allowing fully dynamic rendering
      // by including a Suspense boundary above the body. In this case
      // a lack of a shell is not considered disallowed so we simply return
      return
    }

    if (serverDynamic.syncDynamicErrorWithStack) {
      logDisallowedDynamicError(
        workStore,
        serverDynamic.syncDynamicErrorWithStack
      )
      throw new StaticGenBailoutError()
    }
    
    // ... rest of the logic
  }
}

Also revert the corresponding test expectations that were changed to accommodate this incorrect behavior.


if (prelude !== PreludeState.Full) {
if (dynamicValidation.hasSuspenseAboveBody) {
// This route has opted into allowing fully dynamic rendering
Expand All @@ -786,17 +779,6 @@ export function throwIfDisallowedDynamic(
return
}

if (serverDynamic.syncDynamicErrorWithStack) {
// There is no shell and the server did something sync dynamic likely
// leading to an early termination of the prerender before the shell
// could be completed. We terminate the build/validating render.
logDisallowedDynamicError(
workStore,
serverDynamic.syncDynamicErrorWithStack
)
throw new StaticGenBailoutError()
}

// We didn't have any sync bailouts but there may be user code which
// blocked the root. We would have captured these during the prerender
// and can log them here and then terminate the build/validating render
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,13 @@ export function io(expression: string, type: ApiType) {
let message: string
switch (type) {
case 'time':
message = `Route "${workStore.route}" used ${expression} instead of using \`performance\` or without explicitly calling \`await connection()\` beforehand. See more info here: https://nextjs.org/docs/messages/next-prerender-current-time`
message = `Route "${workStore.route}" used ${expression} before accessing either uncached data (e.g. \`fetch()\`) or Request data (e.g. \`cookies()\`, \`headers()\`, \`connection()\`, and \`searchParams\`). Accessing the current time in a Server Component requires reading one of these data sources first. Alternatively, consider moving this expression into a Client Component or Cache Component. See more info here: https://nextjs.org/docs/messages/next-prerender-current-time`
break
case 'random':
message = `Route "${workStore.route}" used ${expression} outside of \`"use cache"\` and without explicitly calling \`await connection()\` beforehand. See more info here: https://nextjs.org/docs/messages/next-prerender-random`
message = `Route "${workStore.route}" used ${expression} before accessing either uncached data (e.g. \`fetch()\`) or Request data (e.g. \`cookies()\`, \`headers()\`, \`connection()\`, and \`searchParams\`). Accessing random values synchronously in a Server Component requires reading one of these data sources first. Alternatively, consider moving this expression into a Client Component or Cache Component. See more info here: https://nextjs.org/docs/messages/next-prerender-random`
break
case 'crypto':
message = `Route "${workStore.route}" used ${expression} outside of \`"use cache"\` and without explicitly calling \`await connection()\` beforehand. See more info here: https://nextjs.org/docs/messages/next-prerender-crypto`
message = `Route "${workStore.route}" used ${expression} before accessing either uncached data (e.g. \`fetch()\`) or Request data (e.g. \`cookies()\`, \`headers()\`, \`connection()\`, and \`searchParams\`). Accessing random cryptographic values synchronously in a Server Component requires reading one of these data sources first. Alternatively, consider moving this expression into a Client Component or Cache Component. See more info here: https://nextjs.org/docs/messages/next-prerender-crypto`
break
default:
throw new InvariantError(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ describe('Cache Components Dev Errors', () => {
// soft-navigating to the page (see test below).
await expect(browser).toDisplayCollapsedRedbox(`
{
"description": "Route "/error" used \`Math.random()\` outside of \`"use cache"\` and without explicitly calling \`await connection()\` beforehand. See more info here: https://nextjs.org/docs/messages/next-prerender-random",
"description": "Route "/error" used \`Math.random()\` before accessing either uncached data (e.g. \`fetch()\`) or Request data (e.g. \`cookies()\`, \`headers()\`, \`connection()\`, and \`searchParams\`). Accessing random values synchronously in a Server Component requires reading one of these data sources first. Alternatively, consider moving this expression into a Client Component or Cache Component. See more info here: https://nextjs.org/docs/messages/next-prerender-random",
"environmentLabel": "Server",
"label": "Console Error",
"source": "app/error/page.tsx (2:23) @ Page
Expand Down Expand Up @@ -52,7 +52,7 @@ describe('Cache Components Dev Errors', () => {
// TODO: React should not include the anon stack in the Owner Stack.
await expect(browser).toDisplayCollapsedRedbox(`
{
"description": "Route "/error" used \`Math.random()\` outside of \`"use cache"\` and without explicitly calling \`await connection()\` beforehand. See more info here: https://nextjs.org/docs/messages/next-prerender-random",
"description": "Route "/error" used \`Math.random()\` before accessing either uncached data (e.g. \`fetch()\`) or Request data (e.g. \`cookies()\`, \`headers()\`, \`connection()\`, and \`searchParams\`). Accessing random values synchronously in a Server Component requires reading one of these data sources first. Alternatively, consider moving this expression into a Client Component or Cache Component. See more info here: https://nextjs.org/docs/messages/next-prerender-random",
"environmentLabel": "Server",
"label": "Console Error",
"source": "app/error/page.tsx (2:23) @ Page
Expand Down
Loading
Loading