Files
Aurora ScharffandGitHub 18d2e2da5f Insights: use a single Learn more link in console errors (#95967)
## Summary

The instant / blocking-prerender insight console errors printed a docs
URL under each fix option (two or three anchors per message). This
collapses them to a single `Learn more:` link at the end of each
message, while keeping the `[stream]`/`[cache]`/`[block]` fix-option
labels. It restores the single-link format these builders originally
shipped with.

**Before:**

```
Ways to fix this:
  - [stream] Provide a placeholder with `<Suspense fallback={...}>` around the data access
    https://nextjs.org/docs/messages/blocking-prerender-runtime#wrap-in-or-move-into-suspense
  - [block] Set `export const instant = false` to allow a blocking route
    https://nextjs.org/docs/messages/blocking-prerender-runtime#allow-blocking-route
```

**After:**

```
Ways to fix this:
  - [stream] Provide a placeholder with `<Suspense fallback={...}>` around the data access
  - [block] Set `export const instant = false` to allow a blocking route

Learn more: https://nextjs.org/docs/messages/blocking-prerender-runtime
```

Covers all 16 insight-kind errors, across `blocking-route-messages.ts`,
`sync-io-messages.ts`, `dynamic-rendering-utils.ts`, and
`instant-messages.ts`.

Because the dev overlay classified these errors by the `#`-anchored docs
URL, `getBlockingRouteErrorDetails` is updated to match the anchor-less
`Learn more:` URL. Console-message snapshots and the guidance-data
extraction test are updated to the new format. The dev-overlay fix-card
data (`instant-guidance-data.ts`) keeps its per-card links, since those
are the overlay UI rather than the console message.

## Verification

- `pnpm --filter=next types`
- Snapshots regenerated with `jest -u` for the affected suites

<!-- NEXT_JS_LLM -->
2026-07-21 12:53:17 +02:00

674 lines
32 KiB
TypeScript

import { isNextDev } from 'e2e-utils'
import { waitForNoErrorToast } from 'next-test-utils'
import { getPrerenderOutput } from './utils'
import type { CacheComponentsErrorsContext } from './shared.util'
export function registerMetadataAndViewportTests(
ctx: CacheComponentsErrorsContext
) {
const { next, isTurbopack, isDebugPrerender, prerender } = ctx
let cliOutputLength: number
beforeEach(() => {
cliOutputLength = next.cliOutput.length
})
describe('Dynamic Metadata - Static Route', () => {
const pathname = '/dynamic-metadata-static-route'
if (isNextDev) {
it('should show a collapsed redbox error', async () => {
const browser = await next.browser(pathname)
await expect(browser).toDisplayCollapsedRedbox(`
{
"code": "E1425",
"description": "Next.js encountered uncached data in generateMetadata().",
"environmentLabel": "Server",
"label": "Blocking Route",
"source": "app/dynamic-metadata-static-route/page.tsx (2:9) @ Module.generateMetadata
> 2 | await new Promise((r) => setTimeout(r, 0))
| ^",
"stack": [
"Module.generateMetadata app/dynamic-metadata-static-route/page.tsx (2:9)",
],
}
`)
})
} else {
it('should error the build if generateMetadata is dynamic when the rest of the route is prerenderable', async () => {
try {
await prerender(pathname)
} catch {
// we expect the build to fail
}
const output = getPrerenderOutput(
next.cliOutput.slice(cliOutputLength),
{ isMinified: !isDebugPrerender }
)
if (isDebugPrerender) {
expect(output).toMatchInlineSnapshot(`
"Route "/dynamic-metadata-static-route": Next.js encountered uncached or runtime data in \`generateMetadata()\`.
This route's metadata is blocked, but the rest of its content can be prerendered.
Ways to fix this:
- [static] Use a static metadata export instead of \`generateMetadata()\`
- [cache] Cache the metadata with \`"use cache"\` in \`generateMetadata()\` (does not apply to \`connection()\`)
- [dynamic] Render a marker component that calls \`await connection()\` inside \`<Suspense>\` on the page
Learn more: https://nextjs.org/docs/messages/blocking-prerender-metadata-runtime
Error occurred prerendering page "/dynamic-metadata-static-route". Read more: https://nextjs.org/docs/messages/prerender-error
> Export encountered errors on 1 path:
/dynamic-metadata-static-route/page: /dynamic-metadata-static-route"
`)
} else {
expect(output).toMatchInlineSnapshot(`
"Route "/dynamic-metadata-static-route": Next.js encountered uncached or runtime data in \`generateMetadata()\`.
This route's metadata is blocked, but the rest of its content can be prerendered.
Ways to fix this:
- [static] Use a static metadata export instead of \`generateMetadata()\`
- [cache] Cache the metadata with \`"use cache"\` in \`generateMetadata()\` (does not apply to \`connection()\`)
- [dynamic] Render a marker component that calls \`await connection()\` inside \`<Suspense>\` on the page
Learn more: https://nextjs.org/docs/messages/blocking-prerender-metadata-runtime
Error occurred prerendering page "/dynamic-metadata-static-route". Read more: https://nextjs.org/docs/messages/prerender-error
Export encountered an error on /dynamic-metadata-static-route/page: /dynamic-metadata-static-route, exiting the build."
`)
}
})
}
})
describe('Dynamic Metadata - Error Route', () => {
const pathname = '/dynamic-metadata-error-route'
if (isNextDev) {
it('should show a collapsed redbox error', async () => {
const browser = await next.browser(pathname)
await expect(browser).toDisplayCollapsedRedbox(`
{
"code": "E1440",
"description": "Next.js encountered uncached data during prerendering.",
"environmentLabel": "Server",
"label": "Blocking Route",
"source": "app/dynamic-metadata-error-route/page.tsx (21:9) @ Dynamic
> 21 | await new Promise((r) => setTimeout(r))
| ^",
"stack": [
"Dynamic app/dynamic-metadata-error-route/page.tsx (21:9)",
"Page app/dynamic-metadata-error-route/page.tsx (15:7)",
],
}
`)
})
} else {
// This test is just here because there was a bug when dynamic metadata was used alongside another cache components violation which caused the validation to be skipped.
it('should error the build for the correct reason when there is a cache components violation alongside dynamic metadata', async () => {
try {
await prerender(pathname)
} catch {
// we expect the build to fail
}
const output = getPrerenderOutput(
next.cliOutput.slice(cliOutputLength),
{ isMinified: !isDebugPrerender }
)
if (isTurbopack) {
if (isDebugPrerender) {
expect(output).toMatchInlineSnapshot(`
"Error: Route "/dynamic-metadata-error-route": Next.js encountered uncached or runtime data during prerendering.
\`fetch(...)\`, \`cookies()\`, \`headers()\`, \`params\`, \`searchParams\`, or \`connection()\` accessed outside of \`<Suspense>\` prevents the route from being prerendered, blocking the page load and leading to a slower user experience.
Ways to fix this:
- [stream] Provide a placeholder with \`<Suspense fallback={...}>\` around the data access
- [cache] For uncached data (\`fetch\`, database calls): cache the access with \`"use cache"\` (does not apply to \`connection()\`)
- [block] Set \`export const instant = false\` to allow a blocking route
Learn more: https://nextjs.org/docs/messages/blocking-prerender-dynamic
at Dynamic (app/dynamic-metadata-error-route/page.tsx:20:16)
at Page (app/dynamic-metadata-error-route/page.tsx:15:7)
18 | }
19 |
> 20 | async function Dynamic() {
| ^
21 | await new Promise((r) => setTimeout(r))
22 | return <p id="dynamic">Dynamic</p>
23 | }
To debug the issue, start the app in development mode by running \`next dev\`, then open "/dynamic-metadata-error-route" in your browser to investigate the error.
Error occurred prerendering page "/dynamic-metadata-error-route". Read more: https://nextjs.org/docs/messages/prerender-error
> Export encountered errors on 1 path:
/dynamic-metadata-error-route/page: /dynamic-metadata-error-route"
`)
} else {
expect(output).toMatchInlineSnapshot(`
"Error: Route "/dynamic-metadata-error-route": Next.js encountered uncached or runtime data during prerendering.
\`fetch(...)\`, \`cookies()\`, \`headers()\`, \`params\`, \`searchParams\`, or \`connection()\` accessed outside of \`<Suspense>\` prevents the route from being prerendered, blocking the page load and leading to a slower user experience.
Ways to fix this:
- [stream] Provide a placeholder with \`<Suspense fallback={...}>\` around the data access
- [cache] For uncached data (\`fetch\`, database calls): cache the access with \`"use cache"\` (does not apply to \`connection()\`)
- [block] Set \`export const instant = false\` to allow a blocking route
Learn more: https://nextjs.org/docs/messages/blocking-prerender-dynamic
at main (<anonymous>)
at body (<anonymous>)
at html (<anonymous>)
To get a more detailed stack trace and pinpoint the issue, try one of the following:
- Start the app in development mode by running \`next dev\`, then open "/dynamic-metadata-error-route" in your browser to investigate the error.
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
Error occurred prerendering page "/dynamic-metadata-error-route". Read more: https://nextjs.org/docs/messages/prerender-error
Export encountered an error on /dynamic-metadata-error-route/page: /dynamic-metadata-error-route, exiting the build."
`)
}
} else {
if (isDebugPrerender) {
expect(output).toMatchInlineSnapshot(`
"Error: Route "/dynamic-metadata-error-route": Next.js encountered uncached or runtime data during prerendering.
\`fetch(...)\`, \`cookies()\`, \`headers()\`, \`params\`, \`searchParams\`, or \`connection()\` accessed outside of \`<Suspense>\` prevents the route from being prerendered, blocking the page load and leading to a slower user experience.
Ways to fix this:
- [stream] Provide a placeholder with \`<Suspense fallback={...}>\` around the data access
- [cache] For uncached data (\`fetch\`, database calls): cache the access with \`"use cache"\` (does not apply to \`connection()\`)
- [block] Set \`export const instant = false\` to allow a blocking route
Learn more: https://nextjs.org/docs/messages/blocking-prerender-dynamic
at Dynamic (webpack:///app/dynamic-metadata-error-route/page.tsx:20:16)
at Page (webpack:///app/dynamic-metadata-error-route/page.tsx:15:7)
18 | }
19 |
> 20 | async function Dynamic() {
| ^
21 | await new Promise((r) => setTimeout(r))
22 | return <p id="dynamic">Dynamic</p>
23 | }
To debug the issue, start the app in development mode by running \`next dev\`, then open "/dynamic-metadata-error-route" in your browser to investigate the error.
Error occurred prerendering page "/dynamic-metadata-error-route". Read more: https://nextjs.org/docs/messages/prerender-error
> Export encountered errors on 1 path:
/dynamic-metadata-error-route/page: /dynamic-metadata-error-route"
`)
} else {
expect(output).toMatchInlineSnapshot(`
"Error: Route "/dynamic-metadata-error-route": Next.js encountered uncached or runtime data during prerendering.
\`fetch(...)\`, \`cookies()\`, \`headers()\`, \`params\`, \`searchParams\`, or \`connection()\` accessed outside of \`<Suspense>\` prevents the route from being prerendered, blocking the page load and leading to a slower user experience.
Ways to fix this:
- [stream] Provide a placeholder with \`<Suspense fallback={...}>\` around the data access
- [cache] For uncached data (\`fetch\`, database calls): cache the access with \`"use cache"\` (does not apply to \`connection()\`)
- [block] Set \`export const instant = false\` to allow a blocking route
Learn more: https://nextjs.org/docs/messages/blocking-prerender-dynamic
at a (<next-dist-dir>)
at b (<next-dist-dir>)
at c (<next-dist-dir>)
at d (<next-dist-dir>)
at e (<next-dist-dir>)
at f (<next-dist-dir>)
at g (<next-dist-dir>)
at h (<next-dist-dir>)
at i (<next-dist-dir>)
at j (<next-dist-dir>)
at main (<anonymous>)
at body (<anonymous>)
at html (<anonymous>)
at k (<next-dist-dir>)
at l (<next-dist-dir>)
at m (<next-dist-dir>)
at n (<next-dist-dir>)
at o (<next-dist-dir>)
at p (<next-dist-dir>)
at q (<next-dist-dir>)
at r (<next-dist-dir>)
at s (<next-dist-dir>)
at t (<next-dist-dir>)
at u (<next-dist-dir>)
To get a more detailed stack trace and pinpoint the issue, try one of the following:
- Start the app in development mode by running \`next dev\`, then open "/dynamic-metadata-error-route" in your browser to investigate the error.
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
Error occurred prerendering page "/dynamic-metadata-error-route". Read more: https://nextjs.org/docs/messages/prerender-error
Export encountered an error on /dynamic-metadata-error-route/page: /dynamic-metadata-error-route, exiting the build."
`)
}
}
})
}
})
describe('Dynamic Metadata - Static Route With Suspense', () => {
const pathname = '/dynamic-metadata-static-with-suspense'
if (isNextDev) {
it('should show a collapsed redbox error', async () => {
const browser = await next.browser(pathname)
await expect(browser).toDisplayCollapsedRedbox(`
{
"code": "E1425",
"description": "Next.js encountered uncached data in generateMetadata().",
"environmentLabel": "Server",
"label": "Blocking Route",
"source": "app/dynamic-metadata-static-with-suspense/page.tsx (2:9) @ Module.generateMetadata
> 2 | await new Promise((r) => setTimeout(r, 0))
| ^",
"stack": [
"Module.generateMetadata app/dynamic-metadata-static-with-suspense/page.tsx (2:9)",
],
}
`)
})
} else {
it('should error the build if generateMetadata is dynamic when the rest of the route is prerenderable', async () => {
try {
await prerender(pathname)
} catch {
// we expect the build to fail
}
const output = getPrerenderOutput(
next.cliOutput.slice(cliOutputLength),
{ isMinified: !isDebugPrerender }
)
if (isDebugPrerender) {
expect(output).toMatchInlineSnapshot(`
"Route "/dynamic-metadata-static-with-suspense": Next.js encountered uncached or runtime data in \`generateMetadata()\`.
This route's metadata is blocked, but the rest of its content can be prerendered.
Ways to fix this:
- [static] Use a static metadata export instead of \`generateMetadata()\`
- [cache] Cache the metadata with \`"use cache"\` in \`generateMetadata()\` (does not apply to \`connection()\`)
- [dynamic] Render a marker component that calls \`await connection()\` inside \`<Suspense>\` on the page
Learn more: https://nextjs.org/docs/messages/blocking-prerender-metadata-runtime
Error occurred prerendering page "/dynamic-metadata-static-with-suspense". Read more: https://nextjs.org/docs/messages/prerender-error
> Export encountered errors on 1 path:
/dynamic-metadata-static-with-suspense/page: /dynamic-metadata-static-with-suspense"
`)
} else {
expect(output).toMatchInlineSnapshot(`
"Route "/dynamic-metadata-static-with-suspense": Next.js encountered uncached or runtime data in \`generateMetadata()\`.
This route's metadata is blocked, but the rest of its content can be prerendered.
Ways to fix this:
- [static] Use a static metadata export instead of \`generateMetadata()\`
- [cache] Cache the metadata with \`"use cache"\` in \`generateMetadata()\` (does not apply to \`connection()\`)
- [dynamic] Render a marker component that calls \`await connection()\` inside \`<Suspense>\` on the page
Learn more: https://nextjs.org/docs/messages/blocking-prerender-metadata-runtime
Error occurred prerendering page "/dynamic-metadata-static-with-suspense". Read more: https://nextjs.org/docs/messages/prerender-error
Export encountered an error on /dynamic-metadata-static-with-suspense/page: /dynamic-metadata-static-with-suspense, exiting the build."
`)
}
})
}
})
describe('Dynamic Metadata - Static Route With Suspense Above Body', () => {
const pathname = '/dynamic-metadata-static-with-suspense-above-body'
if (isNextDev) {
it('should show a collapsed redbox error', async () => {
const browser = await next.browser(pathname)
await expect(browser).toDisplayCollapsedRedbox(`
{
"code": "E1425",
"description": "Next.js encountered uncached data in generateMetadata().",
"environmentLabel": "Server",
"label": "Blocking Route",
"source": "app/dynamic-metadata-static-with-suspense-above-body/page.tsx (2:9) @ Module.generateMetadata
> 2 | await new Promise((r) => setTimeout(r, 0))
| ^",
"stack": [
"Module.generateMetadata app/dynamic-metadata-static-with-suspense-above-body/page.tsx (2:9)",
],
}
`)
})
} else {
it('should error the build because Suspense above body is not a documented mitigation for dynamic generateMetadata', async () => {
try {
await prerender(pathname)
} catch {
// we expect the build to fail
}
const output = getPrerenderOutput(
next.cliOutput.slice(cliOutputLength),
{ isMinified: !isDebugPrerender }
)
if (isDebugPrerender) {
expect(output).toMatchInlineSnapshot(`
"Route "/dynamic-metadata-static-with-suspense-above-body": Next.js encountered uncached or runtime data in \`generateMetadata()\`.
This route's metadata is blocked, but the rest of its content can be prerendered.
Ways to fix this:
- [static] Use a static metadata export instead of \`generateMetadata()\`
- [cache] Cache the metadata with \`"use cache"\` in \`generateMetadata()\` (does not apply to \`connection()\`)
- [dynamic] Render a marker component that calls \`await connection()\` inside \`<Suspense>\` on the page
Learn more: https://nextjs.org/docs/messages/blocking-prerender-metadata-runtime
Error occurred prerendering page "/dynamic-metadata-static-with-suspense-above-body". Read more: https://nextjs.org/docs/messages/prerender-error
> Export encountered errors on 1 path:
/dynamic-metadata-static-with-suspense-above-body/page: /dynamic-metadata-static-with-suspense-above-body"
`)
} else {
expect(output).toMatchInlineSnapshot(`
"Route "/dynamic-metadata-static-with-suspense-above-body": Next.js encountered uncached or runtime data in \`generateMetadata()\`.
This route's metadata is blocked, but the rest of its content can be prerendered.
Ways to fix this:
- [static] Use a static metadata export instead of \`generateMetadata()\`
- [cache] Cache the metadata with \`"use cache"\` in \`generateMetadata()\` (does not apply to \`connection()\`)
- [dynamic] Render a marker component that calls \`await connection()\` inside \`<Suspense>\` on the page
Learn more: https://nextjs.org/docs/messages/blocking-prerender-metadata-runtime
Error occurred prerendering page "/dynamic-metadata-static-with-suspense-above-body". Read more: https://nextjs.org/docs/messages/prerender-error
Export encountered an error on /dynamic-metadata-static-with-suspense-above-body/page: /dynamic-metadata-static-with-suspense-above-body, exiting the build."
`)
}
})
}
})
describe('Dynamic Metadata - Static Route With instant = false', () => {
const pathname = '/dynamic-metadata-static-with-instant-false'
if (isNextDev) {
it('should show a collapsed redbox error', async () => {
const browser = await next.browser(pathname)
await expect(browser).toDisplayCollapsedRedbox(`
{
"code": "E1425",
"description": "Next.js encountered uncached data in generateMetadata().",
"environmentLabel": "Server",
"label": "Blocking Route",
"source": "app/dynamic-metadata-static-with-instant-false/page.tsx (4:9) @ Module.generateMetadata
> 4 | await new Promise((r) => setTimeout(r, 0))
| ^",
"stack": [
"Module.generateMetadata app/dynamic-metadata-static-with-instant-false/page.tsx (4:9)",
],
}
`)
})
} else {
it('should error the build because instant = false is not a documented mitigation for dynamic generateMetadata', async () => {
try {
await prerender(pathname)
} catch {
// we expect the build to fail
}
const output = getPrerenderOutput(
next.cliOutput.slice(cliOutputLength),
{ isMinified: !isDebugPrerender }
)
if (isDebugPrerender) {
expect(output).toMatchInlineSnapshot(`
"Route "/dynamic-metadata-static-with-instant-false": Next.js encountered uncached or runtime data in \`generateMetadata()\`.
This route's metadata is blocked, but the rest of its content can be prerendered.
Ways to fix this:
- [static] Use a static metadata export instead of \`generateMetadata()\`
- [cache] Cache the metadata with \`"use cache"\` in \`generateMetadata()\` (does not apply to \`connection()\`)
- [dynamic] Render a marker component that calls \`await connection()\` inside \`<Suspense>\` on the page
Learn more: https://nextjs.org/docs/messages/blocking-prerender-metadata-runtime
Error occurred prerendering page "/dynamic-metadata-static-with-instant-false". Read more: https://nextjs.org/docs/messages/prerender-error
> Export encountered errors on 1 path:
/dynamic-metadata-static-with-instant-false/page: /dynamic-metadata-static-with-instant-false"
`)
} else {
expect(output).toMatchInlineSnapshot(`
"Route "/dynamic-metadata-static-with-instant-false": Next.js encountered uncached or runtime data in \`generateMetadata()\`.
This route's metadata is blocked, but the rest of its content can be prerendered.
Ways to fix this:
- [static] Use a static metadata export instead of \`generateMetadata()\`
- [cache] Cache the metadata with \`"use cache"\` in \`generateMetadata()\` (does not apply to \`connection()\`)
- [dynamic] Render a marker component that calls \`await connection()\` inside \`<Suspense>\` on the page
Learn more: https://nextjs.org/docs/messages/blocking-prerender-metadata-runtime
Error occurred prerendering page "/dynamic-metadata-static-with-instant-false". Read more: https://nextjs.org/docs/messages/prerender-error
Export encountered an error on /dynamic-metadata-static-with-instant-false/page: /dynamic-metadata-static-with-instant-false, exiting the build."
`)
}
})
}
})
describe('Dynamic Metadata - Dynamic Route', () => {
const pathname = '/dynamic-metadata-dynamic-route'
if (isNextDev) {
it('should not show a collapsed redbox error', async () => {
const browser = await next.browser(pathname)
await waitForNoErrorToast(browser)
})
} else {
it('should partially prerender when all dynamic components are inside a Suspense boundary', async () => {
try {
await prerender(pathname)
} catch (error) {
throw new Error('expected build not to fail', { cause: error })
}
expect(next.cliOutput).toContain(`◐ ${pathname}`)
await next.start({ skipBuild: true })
const $ = await next.render$(pathname)
expect($('#dynamic').text()).toBe('Dynamic')
expect($('[data-fallback]').length).toBe(1)
})
}
})
describe('Dynamic Viewport - Static Route', () => {
const pathname = '/dynamic-viewport-static-route'
if (isNextDev) {
it('should show a collapsed redbox error', async () => {
const browser = await next.browser(pathname)
await expect(browser).toDisplayCollapsedRedbox(`
{
"code": "E1438",
"description": "Next.js encountered uncached data in generateViewport().",
"environmentLabel": "Server",
"label": "Blocking Route",
"source": "app/dynamic-viewport-static-route/page.tsx (2:9) @ Module.generateViewport
> 2 | await new Promise((r) => setTimeout(r, 0))
| ^",
"stack": [
"Module.generateViewport app/dynamic-viewport-static-route/page.tsx (2:9)",
],
}
`)
})
} else {
it('should error the build if generateViewport is dynamic', async () => {
try {
await prerender(pathname)
} catch {
// we expect the build to fail
}
const output = getPrerenderOutput(
next.cliOutput.slice(cliOutputLength),
{ isMinified: !isDebugPrerender }
)
if (isDebugPrerender) {
expect(output).toMatchInlineSnapshot(`
"Route "/dynamic-viewport-static-route": Next.js encountered uncached or runtime data in \`generateViewport()\`.
This prevents the page from being prerendered, leading to a slower user experience. Unlike metadata, viewport cannot be streamed behind \`<Suspense>\` because it affects the initial page load.
Ways to fix this:
- [static] Use a static viewport export instead of \`generateViewport()\`
- [cache] For uncached data (\`fetch\`, database calls): cache the viewport with \`"use cache"\` in \`generateViewport()\` (does not apply to \`connection()\`)
- [block] Set \`export const instant = false\` to allow a blocking route
Learn more: https://nextjs.org/docs/messages/blocking-prerender-viewport-runtime
Error occurred prerendering page "/dynamic-viewport-static-route". Read more: https://nextjs.org/docs/messages/prerender-error
> Export encountered errors on 1 path:
/dynamic-viewport-static-route/page: /dynamic-viewport-static-route"
`)
} else {
expect(output).toMatchInlineSnapshot(`
"Route "/dynamic-viewport-static-route": Next.js encountered uncached or runtime data in \`generateViewport()\`.
This prevents the page from being prerendered, leading to a slower user experience. Unlike metadata, viewport cannot be streamed behind \`<Suspense>\` because it affects the initial page load.
Ways to fix this:
- [static] Use a static viewport export instead of \`generateViewport()\`
- [cache] For uncached data (\`fetch\`, database calls): cache the viewport with \`"use cache"\` in \`generateViewport()\` (does not apply to \`connection()\`)
- [block] Set \`export const instant = false\` to allow a blocking route
Learn more: https://nextjs.org/docs/messages/blocking-prerender-viewport-runtime
Error occurred prerendering page "/dynamic-viewport-static-route". Read more: https://nextjs.org/docs/messages/prerender-error
Export encountered an error on /dynamic-viewport-static-route/page: /dynamic-viewport-static-route, exiting the build."
`)
}
})
}
})
describe('Dynamic Viewport - Static Route With Suspense Above Body', () => {
const pathname = '/dynamic-viewport-static-with-suspense'
if (isNextDev) {
it('should not show a collapsed redbox error', async () => {
const browser = await next.browser(pathname)
await waitForNoErrorToast(browser)
})
} else {
it('should not error the build when generateViewport is dynamic and the root layout wraps body in Suspense', async () => {
try {
await prerender(pathname)
} catch (error) {
throw new Error('expected build not to fail', { cause: error })
}
})
}
})
describe('Dynamic Viewport - Static Route With instant = false', () => {
const pathname = '/dynamic-viewport-static-with-instant-false'
if (isNextDev) {
it('should not show a collapsed redbox error', async () => {
const browser = await next.browser(pathname)
await waitForNoErrorToast(browser)
})
} else {
it('should not error the build when generateViewport is dynamic and the page opts into blocking via instant = false', async () => {
try {
await prerender(pathname)
} catch (error) {
throw new Error('expected build not to fail', { cause: error })
}
})
}
})
describe('Dynamic Viewport - Dynamic Route', () => {
const pathname = '/dynamic-viewport-dynamic-route'
if (isNextDev) {
it('should show a collapsed redbox error', async () => {
const browser = await next.browser(pathname)
await expect(browser).toDisplayCollapsedRedbox(`
{
"code": "E1438",
"description": "Next.js encountered uncached data in generateViewport().",
"environmentLabel": "Server",
"label": "Blocking Route",
"source": "app/dynamic-viewport-dynamic-route/page.tsx (4:9) @ Module.generateViewport
> 4 | await new Promise((r) => setTimeout(r, 0))
| ^",
"stack": [
"Module.generateViewport app/dynamic-viewport-dynamic-route/page.tsx (4:9)",
],
}
`)
})
} else {
it('should error the build if generateViewport is dynamic even if there are other uses of dynamic on the page', async () => {
try {
await prerender(pathname)
} catch {
// we expect the build to fail
}
const output = getPrerenderOutput(
next.cliOutput.slice(cliOutputLength),
{ isMinified: !isDebugPrerender }
)
if (isDebugPrerender) {
expect(output).toMatchInlineSnapshot(`
"Route "/dynamic-viewport-dynamic-route": Next.js encountered uncached or runtime data in \`generateViewport()\`.
This prevents the page from being prerendered, leading to a slower user experience. Unlike metadata, viewport cannot be streamed behind \`<Suspense>\` because it affects the initial page load.
Ways to fix this:
- [static] Use a static viewport export instead of \`generateViewport()\`
- [cache] For uncached data (\`fetch\`, database calls): cache the viewport with \`"use cache"\` in \`generateViewport()\` (does not apply to \`connection()\`)
- [block] Set \`export const instant = false\` to allow a blocking route
Learn more: https://nextjs.org/docs/messages/blocking-prerender-viewport-runtime
Error occurred prerendering page "/dynamic-viewport-dynamic-route". Read more: https://nextjs.org/docs/messages/prerender-error
> Export encountered errors on 1 path:
/dynamic-viewport-dynamic-route/page: /dynamic-viewport-dynamic-route"
`)
} else {
expect(output).toMatchInlineSnapshot(`
"Route "/dynamic-viewport-dynamic-route": Next.js encountered uncached or runtime data in \`generateViewport()\`.
This prevents the page from being prerendered, leading to a slower user experience. Unlike metadata, viewport cannot be streamed behind \`<Suspense>\` because it affects the initial page load.
Ways to fix this:
- [static] Use a static viewport export instead of \`generateViewport()\`
- [cache] For uncached data (\`fetch\`, database calls): cache the viewport with \`"use cache"\` in \`generateViewport()\` (does not apply to \`connection()\`)
- [block] Set \`export const instant = false\` to allow a blocking route
Learn more: https://nextjs.org/docs/messages/blocking-prerender-viewport-runtime
Error occurred prerendering page "/dynamic-viewport-dynamic-route". Read more: https://nextjs.org/docs/messages/prerender-error
Export encountered an error on /dynamic-viewport-dynamic-route/page: /dynamic-viewport-dynamic-route, exiting the build."
`)
}
})
}
})
}