Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
b00bc26
[Cache Components] Discriminate static shell validation errors by type
gnoff Oct 21, 2025
afacc0e
update error message snapshots
lubieowoce Nov 3, 2025
41c373f
remove debug logs
lubieowoce Nov 3, 2025
2de6e42
rename interrupt to syncInterrupt
lubieowoce Nov 3, 2025
385c5d6
ignore sync IO errors during getPayload
lubieowoce Nov 3, 2025
ed3336a
add types for createFromNodeStream
lubieowoce Nov 3, 2025
bc36ba8
Update metadata to await the generation so we get a proper codeframe
gnoff Nov 4, 2025
13a2a6a
Refine the error wording and udpate tests
gnoff Nov 4, 2025
763f6b9
Use runtime stage end time to ignore I/O debug info from dynamic stage
unstubbable Nov 4, 2025
6ee8b70
Use static stage end time for static shell validation
unstubbable Nov 4, 2025
4c2800c
Fix validation stacks on initial load
unstubbable Nov 4, 2025
c626951
Update more test snapshots
gnoff Nov 4, 2025
baedc04
add types for createFromNodeStream
lubieowoce Nov 3, 2025
b7d7d11
Update performance track snapshots
lubieowoce Nov 3, 2025
3763113
add Before stage
lubieowoce Nov 4, 2025
d43274a
type annotate debugChunks
lubieowoce Nov 4, 2025
f23d409
lazy-import Readable
lubieowoce Nov 4, 2025
9bf1742
add revalidate TODO
lubieowoce Nov 4, 2025
6aa96a9
add comment clarifying the stage behavior in io()
lubieowoce Nov 4, 2025
a18cfb9
cleanup in StagedRenderingController
lubieowoce Nov 4, 2025
57bb437
test environment labels with sync IO
lubieowoce Nov 4, 2025
23fc77a
disable sync IO tests in turbopack due to workUnitAsyncStorage bug
lubieowoce Nov 5, 2025
33f3b97
fix lint: exhaustive switch on RenderStage
lubieowoce Nov 5, 2025
5e71636
fix node:stream imports in edge
lubieowoce Nov 5, 2025
f1b967f
update more snapshots
lubieowoce Nov 5, 2025
29da987
remove duplicate logs in console-patch test
lubieowoce Nov 5, 2025
d269c6f
fix non-exhaustive switch in errors.tsx
lubieowoce Nov 5, 2025
6ca5ff4
update more snapshots
lubieowoce Nov 5, 2025
7099760
fix missing space in message and update snapshots
lubieowoce Nov 6, 2025
dc28cf4
less renders, less errors
lubieowoce Nov 6, 2025
744c39d
disable hanging input tests
lubieowoce Nov 7, 2025
1be8dd4
fix typos in error messages
lubieowoce Nov 7, 2025
486fe96
fix more snapshots again somehow
lubieowoce Nov 7, 2025
6dc529c
how are there still more failing snapshots
lubieowoce Nov 7, 2025
dcdadf1
temporarily disable missing-html-tags test
lubieowoce Nov 7, 2025
7f14322
fix hardcoded hasRuntimePrefetch
lubieowoce Nov 7, 2025
2435776
remove debug console.error
lubieowoce Nov 7, 2025
6460215
fix: no hanging _validation outlet if no validation is performed
lubieowoce Nov 19, 2025
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
12 changes: 8 additions & 4 deletions errors/next-prerender-dynamic-metadata.mdx
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
---
title: Cannot access Request information or uncached data in `generateMetadata()` in an otherwise entirely static route
title: Cannot access Runtime data or uncached data in `generateMetadata()` or file-based Metadata in an otherwise entirely static route
---

## Why This Error Occurred

When `cacheComponents` is enabled, Next.js requires that `generateMetadata()` not depend on uncached data or Request data unless some other part of the page also has similar requirements. The reason for this is that while you normally control your intention for what is allowed to be dynamic by adding or removing Suspense boundaries in your Layout and Page components you are not in control of rendering metadata itself.
When `cacheComponents` is enabled, Next.js requires that Document Metadata not depend on uncached data or Runtime data (`cookies()`, `headers()`, `params`, `searchParams`) unless some other part of the page also has similar requirements.

The heuristic Next.js uses to understand your intent with `generateMetadata()` is to look at the data requirements of the rest of the route. If other components depend on Request data or uncached data, then we allow `generateMetadata()` to have similar data requirements. If the rest of your page has no dependence on this type of data, we require that `generateMetadata()` also not have this type of data dependence.
Next.js determines if a page is entirely static or partially static by looking at whether any part of the page cannot be prerendered.

Typically you control the which parts of a page can be prerendered by adding `"use cache"` to Components or data functions and by avoiding Runtime data like `cookies()` or `searchParams`. However, Metadata can be defined in functions (`generateMetadata()`) defined far from your Page content. Additionally Metadata can implicitly depend on Runtime data when using file-based Metadata such as an icon inside a route with a dynamic param. It would be easy for Metadata to accidentally make an otherwise entirely static page have a dynamic component.

To prevent anwanted partially dynamic pages, Next.js expects pages that are otherwise entirely prerenderable to also have prerenderable Metadata.

## Possible Ways to Fix It

Expand Down Expand Up @@ -141,7 +145,7 @@ export default function Page() {
}
```

Note: The reason to structure this `DynamicMarker` as a self-contained Suspense boundary is to avoid blocking the actual content of the page from being prerendered. When Partial Prerendering is enabled alongside `cacheComponents`, the static shell will still contain all of the prerenderable content, and only the metadata will stream in dynamically.
Note: The reason to structure this `DynamicMarker` as a self-contained Suspense boundary is to avoid blocking the actual content of the page from being prerendered.

## Useful Links

Expand Down
92 changes: 48 additions & 44 deletions errors/next-prerender-dynamic-viewport.mdx
Original file line number Diff line number Diff line change
@@ -1,16 +1,61 @@
---
title: Cannot access Request information or uncached data in `generateViewport()`
title: Cannot access Runtime data or uncached data in `generateViewport()`
---

## Why This Error Occurred

When `cacheComponents` is enabled, Next.js requires that `generateViewport()` not depend on uncached data or Request data unless you explicitly opt into having a fully dynamic page. If you encountered this error, it means that `generateViewport` depends on one of these types of data and you have not specifically indicated that the affected route should be entirely dynamic.
When `cacheComponents` is enabled, Next.js requires that `generateViewport()` not depend on uncached data or Runtime data (`cookies()`, `headers()`, `params`, `searchParams`) unless you explicitly opt into having a fully dynamic page. If you encountered this error, it means that `generateViewport` depends on one of these types of data and you have not specifically indicated that blocking navigations are acceptable.

## Possible Ways to Fix It

To fix this issue, you must first determine your goal for the affected route.

Normally, the way you indicate to Next.js that you want to allow reading Request data or uncached external data is by performing this data access inside a component with an ancestor Suspense boundary. With Viewport, however, you aren't directly in control of wrapping the location where this metadata will be rendered, and even if you could wrap it in a Suspense boundary, it would not be correct to render it with a fallback. This is because this metadata is critical to properly loading resources such as images and must be part of the initial App Shell (the initial HTML containing the document head as well as the first paintable UI).
Normally, Next.js ensures every page can produce an initial UI that allows the page to start loading even before uncached data and Runtime data is available. This is accomplished by defining prerenderable UI with Suspense. However viewport metadata is not able to be deferred until after the page loads because it affects initial page load UI.

Ideally, you update `generateViewport` so it does not depend on any uncached data or Runtime data. This allows navigations to appear instant.

However if this is not possibl you can instruct Next.js to allow all navigations to be potentially blocking by wrapping your document `<body>` in a Suspense boundary.

### Caching External Data

When external data is cached, Next.js can prerender with it, which ensures that the App Shell always has the complete viewport metadata available. Consider using `"use cache"` to mark the function producing the external data as cacheable.

Before:

```jsx filename="app/.../layout.tsx"
import { db } from './db'

export async function generateViewport() {
const { width, initialScale } = await db.query('viewport-size')
return {
width,
initialScale,
}
}

export default async function Layout({ children }) {
return ...
}
```

After:

```jsx filename="app/.../layout.tsx"
import { db } from './db'

export async function generateViewport() {
"use cache"
const { width, initialScale } = await db.query('viewport-size')
return {
width,
initialScale,
}
}

export default async function Layout({ children }) {
return ...
}
```

### If you must access Request Data or your external data is uncacheable

Expand Down Expand Up @@ -61,47 +106,6 @@ export default function RootLayout({ children }) {
}
```

### Caching External Data

When external data is cached, Next.js can prerender with it, which ensures that the App Shell always has the complete viewport metadata available. Consider using `"use cache"` to mark the function producing the external data as cacheable.

Before:

```jsx filename="app/.../layout.tsx"
import { db } from './db'

export async function generateViewport() {
const { width, initialScale } = await db.query('viewport-size')
return {
width,
initialScale,
}
}

export default async function Layout({ children }) {
return ...
}
```

After:

```jsx filename="app/.../layout.tsx"
import { db } from './db'

export async function generateViewport() {
"use cache"
const { width, initialScale } = await db.query('viewport-size')
return {
width,
initialScale,
}
}

export default async function Layout({ children }) {
return ...
}
```

## Useful Links

- [`generateViewport()`](/docs/app/api-reference/functions/generate-viewport)
Expand Down
145 changes: 145 additions & 0 deletions errors/next-prerender-runtime-crypto.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
---
title: Cannot access `crypto.getRandomValue()`, `crypto.randomUUID()`, or another web or node crypto API that generates random values synchronously before other uncached data or `connection()` 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` package was used in a Server Component before accessing other uncached data through APIs like `fetch()` and native database drivers, or the `connection()` API. While typically random crypto values can be guarded behind Runtime data like `cookies()`, `headers()`, `params`, and `searchParams`, this particular route is configured for Runtime Prefetching which makes these APIs available as part of the prefetch request. Accessing random values synchronously without preceding it with uncached data or `await connection()` interferes with the framework's ability to produce a correct prefetch result.

## Possible Ways to Fix It

If the random crypto value is appropriate to be 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 navigation 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"`.

Before:

```jsx filename="app/page.js"
export const unstable_prefetch = {
mode: 'runtime',
samples: [...],
}

async function getCachedData(token: string, userId: string) {
"use cache"
return db.query(token, userId, ...)
}

export default async function Page({ params }) {
const { userId } = await params
const token = crypto.randomUUID()
const data = await getCachedData(token, userId);
return ...
}
```

After:

```jsx filename="app/page.js"
export const unstable_prefetch = {
mode: 'runtime',
samples: [...],
}

async function getCachedData(userId: string) {
"use cache"
const token = crypto.randomUUID()
return db.query(token, userId, ...)
}

export default async function Page({ params }) {
const { userId } = await params
const data = await getCachedData(userId);
return ...
}
```

### Use an async API at request-time

If you require this random value to be unique per Request and an async version of the API exists switch to it instead. Also ensure that there is a parent Suspense boundary that defines a fallback UI Next.js can use while rendering this component on each Request.

Before:

```jsx filename="app/page.js"
export const unstable_prefetch = {
mode: 'runtime',
samples: [...],
}

import { generateKeySync } from 'node:crypto'

export default async function Page({ params }) {
const { dataId } = await params
const data = await fetchData(dataId)
const key = generateKeySync('hmac', { ... })
const digestedData = await digestDataWithKey(data, key);
return ...
}
```

After:

```jsx filename="app/page.js"
export const unstable_prefetch = {
mode: 'runtime',
samples: [...],
}

import { generateKey } from 'node:crypto'

export default async function Page({ params }) {
const { dataId } = await params
const data = await fetchData(dataId)
const key = await new Promise(resolve => generateKey('hmac', { ... }, key => resolve(key)))
const digestedData = await digestDataWithKey(data, key);
return ...
}
```

### Use `await connection()` at request-time

If you require this random value to be unique per Request and an async version of the API does not exist, call `await connection()`. Also ensure that there is a parent Suspense boundary that defines a fallback UI Next.js can use while rendering this component on each Request.

Before:

```jsx filename="app/page.js"
export const unstable_prefetch = {
mode: 'runtime',
samples: [...],
}

export default async function Page({ params }) {
const { sessionId } = await params
const uuid = crypto.randomUUID()
return <RequestId sessionId={sessionId} id={uuid} />
}
```

After:

```jsx filename="app/page.js"
export const unstable_prefetch = {
mode: 'runtime',
samples: [...],
}

import { connection } from 'next/server'

export default async function Page({ params }) {
await connection()
const { sessionId } = await params
const uuid = crypto.randomUUID()
return <RequestId sessionId={sessionId} id={uuid} />
}
```

## 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)
Loading
Loading