Skip to content

Commit 21936dc

Browse files
committed
[Cache Components] Error for sync IO in prerender in Server Components
Currently we error if you cannot produce a shell unless you have a Suspense boundary above the root. This is fine for normal IO but sync IO like Math.random() and new Date() have much more significant bad consequences for prerendering. Instead of treating these errors as another flavor of "must have a shell" validation we should instead treat them like they must be guarded behind something else dynamic like `await connection()`.
1 parent 15653f3 commit 21936dc

File tree

47 files changed

+1992
-571
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+1992
-571
lines changed

errors/next-prerender-crypto.mdx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
---
2-
title: Cannot access `crypto.getRandomValue()`, `crypto.randomUUID()`, or another web or node crypto API that generates random values synchronously in a Server Component
2+
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
33
---
44

55
## Why This Error Occurred
66

7-
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()`).
8-
9-
If the API used has an async version you can also switch to that instead of using `await connection()`.
7+
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.
108

119
## Possible Ways to Fix It
1210

11+
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.
12+
13+
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()`.
14+
1315
### Cache the token value
1416

1517
If you are generating a token to talk to a database that itself should be cached move the token generation inside the `"use cache"`.

errors/next-prerender-current-time.mdx

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,21 @@
11
---
2-
title: Cannot infer intended usage of current time with `Date.now()`, `Date()`, or `new Date()` in a Server Component
2+
title: Cannot access `Date.now()`, `Date()`, or `new Date()` before other uncached data or Request data in a Server Component
33
---
44

55
## Why This Error Occurred
66

7-
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.
8-
9-
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.
7+
`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.
108

119
## Possible Ways to Fix It
1210

11+
If the current time is being used for diagnostic purposes such as logging or performance tracking consider using `performance.now()` instead.
12+
13+
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.
14+
15+
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()`.
16+
17+
> **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.
18+
1319
### Performance use case
1420

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

4248
> **Note**: If you need report an absolute time to an observability tool you can also use `performance.timeOrigin + performance.now()`.
49+
> **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.
4350
4451
### Cacheable use cases
4552

@@ -214,6 +221,8 @@ async function DynamicBanner() {
214221
}
215222
```
216223

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

219228
- [`Date.now` API](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now)

errors/next-prerender-random.mdx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,20 @@
11
---
2-
title: Cannot infer intended usage of `Math.random()` in a Server Component
2+
title: Cannot access `Math.random()` before other uncached data or Request data in a Server Component
33
---
44

55
## Why This Error Occurred
66

7-
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.
7+
`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.
8+
9+
## Possible Ways to Fix It
10+
11+
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.
12+
13+
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()`.
14+
15+
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.
16+
17+
> **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.
818
919
## Possible Ways to Fix It
1020

packages/next/src/server/app-render/dynamic-rendering.ts

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -778,6 +778,14 @@ export function throwIfDisallowedDynamic(
778778
dynamicValidation: DynamicValidationState,
779779
serverDynamic: DynamicTrackingState
780780
): void {
781+
if (serverDynamic.syncDynamicErrorWithStack) {
782+
logDisallowedDynamicError(
783+
workStore,
784+
serverDynamic.syncDynamicErrorWithStack
785+
)
786+
throw new StaticGenBailoutError()
787+
}
788+
781789
if (prelude !== PreludeState.Full) {
782790
if (dynamicValidation.hasSuspenseAboveBody) {
783791
// This route has opted into allowing fully dynamic rendering
@@ -786,17 +794,6 @@ export function throwIfDisallowedDynamic(
786794
return
787795
}
788796

789-
if (serverDynamic.syncDynamicErrorWithStack) {
790-
// There is no shell and the server did something sync dynamic likely
791-
// leading to an early termination of the prerender before the shell
792-
// could be completed. We terminate the build/validating render.
793-
logDisallowedDynamicError(
794-
workStore,
795-
serverDynamic.syncDynamicErrorWithStack
796-
)
797-
throw new StaticGenBailoutError()
798-
}
799-
800797
// We didn't have any sync bailouts but there may be user code which
801798
// blocked the root. We would have captured these during the prerender
802799
// and can log them here and then terminate the build/validating render

packages/next/src/server/node-environment-extensions/utils.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,13 @@ export function io(expression: string, type: ApiType) {
3030
let message: string
3131
switch (type) {
3232
case 'time':
33-
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`
33+
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`
3434
break
3535
case 'random':
36-
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`
36+
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`
3737
break
3838
case 'crypto':
39-
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`
39+
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`
4040
break
4141
default:
4242
throw new InvariantError(

0 commit comments

Comments
 (0)