Skip to content

[dynamicIO] Disallow sync IO in Server Components #79576

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: canary
Choose a base branch
from
Open
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
147 changes: 147 additions & 0 deletions errors/next-prerender-crypto-client.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
---
title: Cannot access `crypto.getRandomValue()`, `crypto.randomUUID()`, or another web or node crypto API that generates random values synchronously from a Client Component without a fallback UI defined
---

## Why This Error Occurred

A Client Component is accessing a random value synchronously from the Web Crypto API or from Node's `crypto` API.

Client Components primarily run in the browser however on an initial page visit, Next.js will server an HTML page produced by simulating the client environment on the server. This process is called Server Side Rendering or SSR. Next.js will attempt to prerender this HTML ahead of time however if a Client Component accesses a random source during this prerender, it cannot include this component in the prerendered HTML, otherwise the value will be fixed on each user request and not random like expected. Next.js will use the nearest Suspense boundary around this component to prerender a fallback instead however in this instance there was no Suspense boundary.

There are a number of ways you might fix this issue depending on the specifics of your use case.

## Possible Ways to Fix It

### Provide Fallback UI

If your random value is intended to be unique per Request add a Suspense boundary around the component that calls the crypto API that producing this value. This allows Next.js to prerender a fallback UI and fill in an actual random value when the user requests the page.

Before:

```jsx filename="app/blog/new/page.js"
'use client'

export default function Page() {
const newBlogId = crypto.randomUUID()
return <BlogAuthoringView id={newBlogId} />
}
```

After:

```jsx filename="app/blog/new/page.js"
"use client"

import { Suspense } from 'react'

export default function Page() {
return (
<Suspense fallback={<BlogAuthorSkeleton />}>
<DynamicProductsView products={products} />
</Suspense>
)
}

function BlogAuthorSkeleton() {
...
}

function DynamicAuthoringView() {
const newBlogId = crypto.randomUUID()
return <BlogAuthoringView id={newBlogId} />
}
```

### Only access crypto in the browser

If your random value is only intended for use in the browser you can move the call into an effect or event handler. React does not run effects during server rendering and there are no events during server rendering so neither option will require the prerender to exclude this component.

Before:

```jsx filename="app/products.js"
'use client'

function createSecureId() {
const array = new Uint8Array(16)
crypto.getRandomValues(array)
return array[0].toString(16)
}

export default function Workflow({ currentStep, onNext, onPrev }) {
const id = useState(createSecureId)

const next = onNext
? () => {
trackEvent(id, 'forward')
onNext()
}
: null

const previous = onPrev
? () => {
trackEvent(id, 'previous')
onPrev()
}
: null

return (
<>
{currentStep}
{next ? <button onClick={next}>Next</button> : null}
{previous > 0 ? <button onClick={previous}>Previous</button> : null}
</>
)
}
```

After:

```jsx filename="app/products.js"
'use client'

function createSecureId() {
const array = new Uint8Array(16)
crypto.getRandomValues(array)
return array[0].toString(16)
}

function getOrCreateId(ref) {
if (!ref.current) {
ref.current = createSecureId()
}
return ref.current
}

export default function Workflow({ currentStep, onNext, onPrev }) {
const idRef = useRef(null)

const next = onNext
? () => {
trackEvent(getOrCreateId(idRef), 'forward')
onNext()
}
: null

const previous = onPrev
? () => {
trackEvent(getOrCreateId(idRef), 'previous')
onPrev()
}
: null

return (
<>
{currentStep}
{next ? <button onClick={next}>Next</button> : null}
{previous > 0 ? <button onClick={previous}>Previous</button> : null}
</>
)
}
```

## Useful Links

- [`connection` function](/docs/app/api-reference/functions/connection)
- [Web Crypto API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API)
- [Node Crypto API](https://nodejs.org/docs/latest/api/crypto.html)
- [`Suspense` React API](https://react.dev/reference/react/Suspense)
2 changes: 1 addition & 1 deletion errors/next-prerender-crypto.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
title: Cannot access `crypto.getRandomValue()`, `crypto.randomUUID()`, or another web or node crypto API that generates random values synchronously while prerendering
title: Cannot access `crypto.getRandomValue()`, `crypto.randomUUID()`, or another web or node crypto API that generates random values synchronously in Server Component
---

## Why This Error Occurred
Expand Down
199 changes: 199 additions & 0 deletions errors/next-prerender-current-time-client.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
---
title: Cannot access current time from a Client Component without a fallback UI defined
---

## Why This Error Occurred

A Client Component is accessing the current time with `Date.now()`, `Date()`, or `new Date()`.

Client Components primarily run in the browser however on an initial page visit, Next.js will server an HTML page produced by simulating the client environment on the server. This process is called Server Side Rendering or SSR. Next.js will attempt to prerender this HTML ahead of time however if a Client Component accesses the current time during this prerender, it cannot include this component in the prerendered HTML, otherwise it might contain a content based on a time very different from when the HTML is sent to a user. Next.js will use the nearest Suspense boundary around this component to prerender a fallback instead however in this instance there was no Suspense boundary.

There are a number of ways you might fix this issue depending on the specifics of your use case.

## Possible Ways to Fix It

### Provide Fallback UI

If you want the time value to be part of the server rendered HTML you can add a Suspense boundary around the component which allows Next.js to prerender a fallback UI ahead of a user's request and fill in the actual content when the user request occurs.

Before:

```jsx filename="app/article.js"
'use client'

import { Suspense } from 'react'

export function RelativeTime({ timestamp }) {
const now = Date.now()
return <span>{computeTimeAgo({ timestamp, now })}</span>
}

export default function Article({ articleData }) {
return (
<article>
<h1>...</h1>
<RelativeTime timestamp={articleData.publishedAt} />
</article>
)
}
```

After:

```jsx filename="app/article.js"
'use client'

import { Suspense } from 'react'

export function RelativeTime({ timestamp }) {
const now = Date.now()
return <span>{computeTimeAgo({ timestamp, now })}</span>
}

export default function Article({ articleData }) {
return (
<article>
<h1>...</h1>
<Suspense fallback={<span>...</span>}>
<RelativeTime timestamp={articleData.publishedAt} />
</Suspense>
</article>
)
}
```

### Only access the time in the browser

If you do not want to provide a fallback UI you may be able to move the time access into an effect. React does not run effects during server rendering so the time access will only occur in the browser.

Before:

```jsx filename="app/article.js"
'use client'

export function RelativeTime({ timestamp }) {
const now = Date.now()
return <span>{computeTimeAgo({ timestamp, now })}</span>
}

export default function Article({ articleData }) {
return (
<article>
<h1>...</h1>
<RelativeTime timestamp={articleData.publishedAt} />
</article>
)
}
```

After:

```jsx filename="app/article.js"
'use client'

import { useState, useEffect } from 'react'

export function RelativeTime({ timestamp }) {
const [timeAgo, setTimeAgo] = useState('')
useEffect(() => {
// The effect won't run while rendering on the server
const now = Date.now()
setTimeAgo(computeTimeAgo({ timestamp, now }))
}, [timestamp])
return <span>{timeAgo}</span>
}

export default function Article({ articleData }) {
return (
<article>
<h1>...</h1>
<RelativeTime timestamp={articleData.publishedAt} />
</article>
)
}
```

### Cache the time in a Server Component

While Next.js will treat reading the current time in a Server Component the same as a Client Component, you have the additional option of caching the time read using `"use cache"`. With this approach the time value will can be included in the prerendered HTML because you are explicitly indicating the value does not need to reflect the time of the user's request.

Before:

```jsx filename="app/page.js"
'use client'

export default function Layout({ children }) {
return (
<>
<main>{children}</main>
<footer>
<span>Copyright {new Date().getFullYear()}</span>
</footer>
</>
)
}
```

After:

```jsx filename="app/layout.js"
async function getCurrentYear() {
'use cache'
return new Date().getFullYear()
}

export default async function Layout({ children }) {
return (
<>
<main>{children}</main>
<footer>
<span>Copyright {await getCurrentYear()}</span>
</footer>
</>
)
}
```

### Performance use case

If you are using the current time for performance tracking with elapsed time use `performance.now()`.

Before:

```jsx filename="app/page.js"
"use client"

export default function Page() {
const start = Date.now();
const data = computeDataSlowly(...);
const end = Date.now();
console.log(`somethingSlow took ${end - start} milliseconds to complete`)

return ...
}
```

After:

```jsx filename="app/page.js"
"use client"

export default async function Page() {
const start = performance.now();
const data = computeDataSlowly(...);
const end = performance.now();
console.log(`somethingSlow took ${end - start} milliseconds to complete`)
return ...
}
```

> **Note**: If you need report an absolute time to an observability tool you can also use `performance.timeOrigin + performance.now()`.

## Useful Links

- [`Date.now` API](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now)
- [`Date constructor` API](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/Date)
- [`"use cache"`](/docs/app/api-reference/directives/use-cache)
- [`performance` Web API](https://developer.mozilla.org/en-US/docs/Web/API/Performance)
- [`Suspense` React API](https://react.dev/reference/react/Suspense)
- [`useEffect` React Hook](https://react.dev/reference/react/useEffect)
Loading
Loading