import { nextTestSetup } from 'e2e-utils'
import {
expectNoBuildValidationErrors,
expectBuildValidationSkipped,
extractBuildValidationError,
waitForValidation,
} from 'e2e-utils/instant-validation'
import {
openRedbox,
retry,
waitForNoErrorToast,
waitForRedbox,
} from '../../../lib/next-test-utils'
import {
createRedboxSnapshot,
ErrorSnapshot,
RedboxSnapshot,
} from '../../../lib/add-redbox-matchers'
import { Playwright } from '../../../lib/next-webdriver'
describe('instant validation', () => {
const { next, skipped, isNextDev, isNextStart, isTurbopack } = nextTestSetup({
files: __dirname,
skipStart: true,
skipDeployment: true,
env: {
NEXT_TEST_LOG_VALIDATION: '1',
},
})
if (skipped) return
if (isNextStart && !isTurbopack) {
// TODO(instant-validation-build): snapshot tests for webpack
it.skip('TODO: snapshot tests for webpack', () => {})
return
}
if (isNextStart) {
beforeAll(async () => {
await next.build({ args: ['--experimental-build-mode', 'compile'] })
})
afterEach(async () => {
await next.stop()
})
} else {
beforeAll(async () => {
await next.start()
})
}
let currentCliOutputIndex = 0
beforeEach(() => {
currentCliOutputIndex = next.cliOutput.length
})
function getCliOutputSinceMark(): string {
if (next.cliOutput.length < currentCliOutputIndex) {
// cliOutput shrank since we started the test, so something (like a `sandbox`) reset the logs
currentCliOutputIndex = 0
}
return next.cliOutput.slice(currentCliOutputIndex)
}
const prerender = async (pathname: string) => {
const args = [
'--experimental-build-mode',
'generate',
'--debug-build-paths',
`app${pathname}/page.tsx`,
]
return await next.build({ args })
}
const NO_VALIDATION_ERRORS_WAIT: Parameters<typeof waitForNoErrorToast>[1] = {
waitInMs: 500,
}
async function expectNoDevValidationErrors(
browser: Playwright,
url: string
): Promise<void> {
await waitForValidation(url, getCliOutputSinceMark)
await waitForNoErrorToast(browser, NO_VALIDATION_ERRORS_WAIT)
}
const cases = isNextDev
? [
{ isClientNav: false, description: 'dev - initial load' },
{ isClientNav: true, description: 'dev - client navigation' },
]
: [{ isClientNav: false, description: 'build' }]
describe.each(cases)('$description', ({ isClientNav }) => {
/**
* Navigate to a page either via initial load or soft navigation.
* For soft nav, navigates to the index page first, then clicks the link.
*/
async function navigateTo(href: string) {
if (!isClientNav) {
// Initial load - navigate directly
const browser = await next.browser(href)
await browser.elementByCss('main')
return browser
}
// Soft nav - go to index page first, then click link
const indexPage = href.startsWith('/default/')
? '/default'
: '/suspense-in-root'
const browser = await next.browser(indexPage)
const initialRootLayoutTimestamp = await browser
.elementById('root-layout-timestamp')
.text()
await browser
.elementByCss(`[data-link-type="soft"][href="${href}"]`)
.click()
await retry(
async () => {
expect(await browser.url()).toContain(href)
},
undefined,
100,
'wait for url to change'
)
// Sanity check: we shouldn't have switched or otherwise refetched the root layout
const finalRootLayoutTimestamp = await browser
.elementById('root-layout-timestamp')
.text()
expect(initialRootLayoutTimestamp).toBe(finalRootLayoutTimestamp)
return browser
}
it('valid - static prefetch - suspense around runtime and dynamic', async () => {
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/static/suspense-around-dynamic'
)
await expectNoDevValidationErrors(browser, await browser.url())
} else {
const result = await prerender(
'/suspense-in-root/static/suspense-around-dynamic'
)
expectNoBuildValidationErrors(result)
}
})
it('valid - runtime prefetch - suspense only around dynamic', async () => {
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/runtime/suspense-around-dynamic'
)
await expectNoDevValidationErrors(browser, await browser.url())
} else {
const result = await prerender(
'/suspense-in-root/runtime/suspense-around-dynamic'
)
expectNoBuildValidationErrors(result)
}
})
it('invalid - static prefetch - missing suspense around runtime', async () => {
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/static/missing-suspense-around-runtime'
)
await expect(browser).toDisplayCollapsedRedbox(`
{
"cause": [
{
"label": "Caused by: Instant Validation",
"source": "app/suspense-in-root/static/missing-suspense-around-runtime/page.tsx (3:33) @ unstable_instant
> 3 | export const unstable_instant = true
| ^",
"stack": [
"unstable_instant app/suspense-in-root/static/missing-suspense-around-runtime/page.tsx (3:33)",
"Set.forEach <anonymous>",
],
},
],
"code": "E1166",
"description": "Next.js encountered runtime data during the initial render.",
"environmentLabel": "Server",
"label": "Instant",
"source": "app/suspense-in-root/static/missing-suspense-around-runtime/page.tsx (6:16) @ Page
> 6 | await cookies()
| ^",
"stack": [
"Page app/suspense-in-root/static/missing-suspense-around-runtime/page.tsx (6:16)",
],
}
`)
} else {
const result = await prerender(
'/suspense-in-root/static/missing-suspense-around-runtime'
)
expect(extractBuildValidationError(result.cliOutput))
.toMatchInlineSnapshot(`
"Error: Route "/suspense-in-root/static/missing-suspense-around-runtime": Next.js encountered runtime data during the initial render.
\`cookies()\`, \`headers()\`, \`params\`, or \`searchParams\` accessed outside of \`<Suspense>\` blocks navigation, leading to a slower user experience.
Ways to fix this:
- Move the data access into a child component within a <Suspense> boundary
- Use \`generateStaticParams\` to make route params static
- Set \`export const instant = false\` to allow a blocking route
Learn more: https://nextjs.org/docs/messages/blocking-route
at body (<anonymous>)
at html (<anonymous>)
at a (<anonymous>)
Build-time instant validation failed for route "/suspense-in-root/static/missing-suspense-around-runtime".
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 "/suspense-in-root/static/missing-suspense-around-runtime" in your browser to investigate the error.
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
Stopping prerender due to instant validation errors."
`)
expect(result.exitCode).toBe(1)
}
})
it('invalid - static prefetch - missing suspense around dynamic', async () => {
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/static/missing-suspense-around-dynamic'
)
await expect(browser).toDisplayCollapsedRedbox(`
{
"cause": [
{
"label": "Caused by: Instant Validation",
"source": "app/suspense-in-root/static/missing-suspense-around-dynamic/page.tsx (3:33) @ unstable_instant
> 3 | export const unstable_instant = true
| ^",
"stack": [
"unstable_instant app/suspense-in-root/static/missing-suspense-around-dynamic/page.tsx (3:33)",
"Set.forEach <anonymous>",
],
},
],
"code": "E1164",
"description": "Next.js encountered uncached data during the initial render.",
"environmentLabel": "Server",
"label": "Instant",
"source": "app/suspense-in-root/static/missing-suspense-around-dynamic/page.tsx (6:19) @ Page
> 6 | await connection()
| ^",
"stack": [
"Page app/suspense-in-root/static/missing-suspense-around-dynamic/page.tsx (6:19)",
],
}
`)
} else {
const result = await prerender(
'/suspense-in-root/static/missing-suspense-around-dynamic'
)
expect(extractBuildValidationError(result.cliOutput))
.toMatchInlineSnapshot(`
"Error: Route "/suspense-in-root/static/missing-suspense-around-dynamic": Next.js encountered uncached data during the initial render.
\`fetch(...)\` or \`connection()\` accessed outside of \`<Suspense>\` blocks navigation, leading to a slower user experience.
Ways to fix this:
- Cache the data access with \`"use cache"\`
- Move the data access into a child component within a <Suspense> boundary
- Set \`export const instant = false\` to allow a blocking route
Learn more: https://nextjs.org/docs/messages/blocking-route
at body (<anonymous>)
at html (<anonymous>)
at a (<anonymous>)
Build-time instant validation failed for route "/suspense-in-root/static/missing-suspense-around-dynamic".
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 "/suspense-in-root/static/missing-suspense-around-dynamic" in your browser to investigate the error.
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
Stopping prerender due to instant validation errors."
`)
expect(result.exitCode).toBe(1)
}
})
it('invalid - runtime prefetch - missing suspense around dynamic', async () => {
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/runtime/missing-suspense-around-dynamic'
)
await expect(browser).toDisplayCollapsedRedbox(`
{
"cause": [
{
"label": "Caused by: Instant Validation",
"source": "app/suspense-in-root/runtime/missing-suspense-around-dynamic/page.tsx (4:33) @ unstable_instant
> 4 | export const unstable_instant = true
| ^",
"stack": [
"unstable_instant app/suspense-in-root/runtime/missing-suspense-around-dynamic/page.tsx (4:33)",
"Set.forEach <anonymous>",
],
},
],
"code": "E1164",
"description": "Next.js encountered uncached data during the initial render.",
"environmentLabel": "Server",
"label": "Instant",
"source": "app/suspense-in-root/runtime/missing-suspense-around-dynamic/page.tsx (23:19) @ Dynamic
> 23 | await connection()
| ^",
"stack": [
"Dynamic app/suspense-in-root/runtime/missing-suspense-around-dynamic/page.tsx (23:19)",
"Page app/suspense-in-root/runtime/missing-suspense-around-dynamic/page.tsx (16:9)",
],
}
`)
} else {
const result = await prerender(
'/suspense-in-root/runtime/missing-suspense-around-dynamic'
)
expect(extractBuildValidationError(result.cliOutput))
.toMatchInlineSnapshot(`
"Error: Route "/suspense-in-root/runtime/missing-suspense-around-dynamic": Next.js encountered uncached data during the initial render.
\`fetch(...)\` or \`connection()\` accessed outside of \`<Suspense>\` blocks navigation, leading to a slower user experience.
Ways to fix this:
- Cache the data access with \`"use cache"\`
- Move the data access into a child component within a <Suspense> boundary
- Set \`export const instant = false\` to allow a blocking route
Learn more: https://nextjs.org/docs/messages/blocking-route
at div (<anonymous>)
at main (<anonymous>)
at body (<anonymous>)
at html (<anonymous>)
at a (<anonymous>)
Build-time instant validation failed for route "/suspense-in-root/runtime/missing-suspense-around-dynamic".
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 "/suspense-in-root/runtime/missing-suspense-around-dynamic" in your browser to investigate the error.
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
Stopping prerender due to instant validation errors."
`)
expect(result.exitCode).toBe(1)
}
})
it('invalid - static prefetch - missing suspense around dynamic in a layout', async () => {
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/static/missing-suspense-around-dynamic-layout'
)
await expect(browser).toDisplayCollapsedRedbox(`
{
"cause": [
{
"label": "Caused by: Instant Validation",
"source": "app/suspense-in-root/static/missing-suspense-around-dynamic-layout/layout.tsx (4:33) @ unstable_instant
> 4 | export const unstable_instant = true
| ^",
"stack": [
"unstable_instant app/suspense-in-root/static/missing-suspense-around-dynamic-layout/layout.tsx (4:33)",
"Set.forEach <anonymous>",
],
},
],
"code": "E1166",
"description": "Next.js encountered runtime data during the initial render.",
"environmentLabel": "Server",
"label": "Instant",
"source": "app/suspense-in-root/static/missing-suspense-around-dynamic-layout/layout.tsx (7:16) @ Layout
> 7 | await cookies()
| ^",
"stack": [
"Layout app/suspense-in-root/static/missing-suspense-around-dynamic-layout/layout.tsx (7:16)",
],
}
`)
} else {
const result = await prerender(
'/suspense-in-root/static/missing-suspense-around-dynamic-layout'
)
expect(extractBuildValidationError(result.cliOutput))
.toMatchInlineSnapshot(`
"Error: Route "/suspense-in-root/static/missing-suspense-around-dynamic-layout": Next.js encountered runtime data during the initial render.
\`cookies()\`, \`headers()\`, \`params\`, or \`searchParams\` accessed outside of \`<Suspense>\` blocks navigation, leading to a slower user experience.
Ways to fix this:
- Move the data access into a child component within a <Suspense> boundary
- Use \`generateStaticParams\` to make route params static
- Set \`export const instant = false\` to allow a blocking route
Learn more: https://nextjs.org/docs/messages/blocking-route
at body (<anonymous>)
at html (<anonymous>)
at a (<anonymous>)
Build-time instant validation failed for route "/suspense-in-root/static/missing-suspense-around-dynamic-layout".
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 "/suspense-in-root/static/missing-suspense-around-dynamic-layout" in your browser to investigate the error.
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
Stopping prerender due to instant validation errors."
`)
expect(result.exitCode).toBe(1)
}
})
it('invalid - runtime prefetch - missing suspense around dynamic in a layout', async () => {
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/runtime/missing-suspense-around-dynamic-layout'
)
await expect(browser).toDisplayCollapsedRedbox(`
{
"cause": [
{
"label": "Caused by: Instant Validation",
"source": "app/suspense-in-root/runtime/missing-suspense-around-dynamic-layout/layout.tsx (4:33) @ unstable_instant
> 4 | export const unstable_instant = true
| ^",
"stack": [
"unstable_instant app/suspense-in-root/runtime/missing-suspense-around-dynamic-layout/layout.tsx (4:33)",
"Set.forEach <anonymous>",
],
},
],
"code": "E1164",
"description": "Next.js encountered uncached data during the initial render.",
"environmentLabel": "Server",
"label": "Instant",
"source": "app/suspense-in-root/runtime/missing-suspense-around-dynamic-layout/layout.tsx (8:19) @ Layout
> 8 | await connection()
| ^",
"stack": [
"Layout app/suspense-in-root/runtime/missing-suspense-around-dynamic-layout/layout.tsx (8:19)",
],
}
`)
} else {
const result = await prerender(
'/suspense-in-root/runtime/missing-suspense-around-dynamic-layout'
)
expect(extractBuildValidationError(result.cliOutput))
.toMatchInlineSnapshot(`
"Error: Route "/suspense-in-root/runtime/missing-suspense-around-dynamic-layout": Next.js encountered uncached data during the initial render.
\`fetch(...)\` or \`connection()\` accessed outside of \`<Suspense>\` blocks navigation, leading to a slower user experience.
Ways to fix this:
- Cache the data access with \`"use cache"\`
- Move the data access into a child component within a <Suspense> boundary
- Set \`export const instant = false\` to allow a blocking route
Learn more: https://nextjs.org/docs/messages/blocking-route
at body (<anonymous>)
at html (<anonymous>)
at a (<anonymous>)
Build-time instant validation failed for route "/suspense-in-root/runtime/missing-suspense-around-dynamic-layout".
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 "/suspense-in-root/runtime/missing-suspense-around-dynamic-layout" in your browser to investigate the error.
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
Stopping prerender due to instant validation errors."
`)
expect(result.exitCode).toBe(1)
}
})
it('invalid - static prefetch - missing suspense around params', async () => {
// In build mode, providing params in the sample makes them resolve
// immediately, so the blocking behavior isn't detected. This case
// is only testable in dev mode.
if (!isNextDev) return
const browser = await navigateTo(
'/suspense-in-root/static/missing-suspense-around-params/123'
)
await expect(browser).toDisplayCollapsedRedbox(`
{
"cause": [
{
"label": "Caused by: Instant Validation",
"source": "app/suspense-in-root/static/missing-suspense-around-params/[param]/page.tsx (1:33) @ unstable_instant
> 1 | export const unstable_instant = { samples: [{ params: { param: '123' } }] }
| ^",
"stack": [
"unstable_instant app/suspense-in-root/static/missing-suspense-around-params/[param]/page.tsx (1:33)",
"Set.forEach <anonymous>",
],
},
],
"code": "E1166",
"description": "Next.js encountered runtime data during the initial render.",
"environmentLabel": "Server",
"label": "Instant",
"source": "app/suspense-in-root/static/missing-suspense-around-params/[param]/page.tsx (17:21) @ Runtime
> 17 | const { param } = await params
| ^",
"stack": [
"Runtime app/suspense-in-root/static/missing-suspense-around-params/[param]/page.tsx (17:21)",
"Page app/suspense-in-root/static/missing-suspense-around-params/[param]/page.tsx (11:7)",
],
}
`)
})
it('valid - runtime prefetch - does not require Suspense around params', async () => {
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/runtime/valid-no-suspense-around-params/123'
)
await expectNoDevValidationErrors(browser, await browser.url())
} else {
const result = await prerender(
'/suspense-in-root/runtime/valid-no-suspense-around-params/[param]'
)
expectNoBuildValidationErrors(result)
}
})
it('invalid - static prefetch - missing suspense around search params', async () => {
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/static/missing-suspense-around-search-params?foo=bar'
)
await expect(browser).toDisplayCollapsedRedbox(`
{
"cause": [
{
"label": "Caused by: Instant Validation",
"source": "app/suspense-in-root/static/missing-suspense-around-search-params/page.tsx (1:33) @ unstable_instant
> 1 | export const unstable_instant = { samples: [{ searchParams: { foo: 'bar' } }] }
| ^",
"stack": [
"unstable_instant app/suspense-in-root/static/missing-suspense-around-search-params/page.tsx (1:33)",
"Set.forEach <anonymous>",
],
},
],
"code": "E1166",
"description": "Next.js encountered runtime data during the initial render.",
"environmentLabel": "Server",
"label": "Instant",
"source": "app/suspense-in-root/static/missing-suspense-around-search-params/page.tsx (4:18) @ Page
> 4 | const search = await searchParams
| ^",
"stack": [
"Page app/suspense-in-root/static/missing-suspense-around-search-params/page.tsx (4:18)",
],
}
`)
} else {
const result = await prerender(
'/suspense-in-root/static/missing-suspense-around-search-params'
)
expect(extractBuildValidationError(result.cliOutput))
.toMatchInlineSnapshot(`
"Error: Route "/suspense-in-root/static/missing-suspense-around-search-params": Next.js encountered runtime data during the initial render.
\`cookies()\`, \`headers()\`, \`params\`, or \`searchParams\` accessed outside of \`<Suspense>\` blocks navigation, leading to a slower user experience.
Ways to fix this:
- Move the data access into a child component within a <Suspense> boundary
- Use \`generateStaticParams\` to make route params static
- Set \`export const instant = false\` to allow a blocking route
Learn more: https://nextjs.org/docs/messages/blocking-route
at body (<anonymous>)
at html (<anonymous>)
at a (<anonymous>)
Build-time instant validation failed for route "/suspense-in-root/static/missing-suspense-around-search-params".
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 "/suspense-in-root/static/missing-suspense-around-search-params" in your browser to investigate the error.
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
Stopping prerender due to instant validation errors."
`)
expect(result.exitCode).toBe(1)
}
})
it('valid - runtime prefetch - does not require Suspense around search params', async () => {
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/runtime/valid-no-suspense-around-search-params?foo=bar'
)
await expectNoDevValidationErrors(browser, await browser.url())
} else {
const result = await prerender(
'/suspense-in-root/runtime/valid-no-suspense-around-search-params'
)
expectNoBuildValidationErrors(result)
}
})
it('valid - target segment not visible in all navigations', async () => {
if (isNextDev) {
// Notable special case -- we accept that the segment with the assertion might not
// *itself* be visible in all navigations as long as they're instant.
// A parent layout might be blocked from rendering the children slot,
// but that's fine as long as it provides a fallback.
//
// This is in opposition to an alternate model we considered at some point,
// where putting an assertion on a segment would mean that it must be visible
// in all navigations (which would require that its parent layouts must never
// block the children slots)
const browser = await navigateTo(
'/default/static/valid-blocked-children'
)
await expectNoDevValidationErrors(browser, await browser.url())
} else {
const result = await prerender('/default/static/valid-blocked-children')
expectNoBuildValidationErrors(result)
}
})
it('invalid - static prefetch - suspense too high', async () => {
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/static/suspense-too-high'
)
await expect(browser).toDisplayCollapsedRedbox(`
{
"cause": [
{
"label": "Caused by: Instant Validation",
"source": "app/suspense-in-root/static/suspense-too-high/page.tsx (3:33) @ unstable_instant
> 3 | export const unstable_instant = true
| ^",
"stack": [
"unstable_instant app/suspense-in-root/static/suspense-too-high/page.tsx (3:33)",
"Set.forEach <anonymous>",
],
},
],
"code": "E1166",
"description": "Next.js encountered runtime data during the initial render.",
"environmentLabel": "Server",
"label": "Instant",
"source": "app/suspense-in-root/static/suspense-too-high/page.tsx (6:16) @ Page
> 6 | await cookies()
| ^",
"stack": [
"Page app/suspense-in-root/static/suspense-too-high/page.tsx (6:16)",
],
}
`)
} else {
const result = await prerender(
'/suspense-in-root/static/suspense-too-high'
)
expect(extractBuildValidationError(result.cliOutput))
.toMatchInlineSnapshot(`
"Error: Route "/suspense-in-root/static/suspense-too-high": Next.js encountered runtime data during the initial render.
\`cookies()\`, \`headers()\`, \`params\`, or \`searchParams\` accessed outside of \`<Suspense>\` blocks navigation, leading to a slower user experience.
Ways to fix this:
- Move the data access into a child component within a <Suspense> boundary
- Use \`generateStaticParams\` to make route params static
- Set \`export const instant = false\` to allow a blocking route
Learn more: https://nextjs.org/docs/messages/blocking-route
at a (<anonymous>)
at div (<anonymous>)
at div (<anonymous>)
at body (<anonymous>)
at html (<anonymous>)
at b (<anonymous>)
Build-time instant validation failed for route "/suspense-in-root/static/suspense-too-high".
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 "/suspense-in-root/static/suspense-too-high" in your browser to investigate the error.
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
Stopping prerender due to instant validation errors."
`)
expect(result.exitCode).toBe(1)
}
})
it('invalid - runtime prefetch - suspense too high', async () => {
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/runtime/suspense-too-high'
)
await expect(browser).toDisplayCollapsedRedbox(`
{
"cause": [
{
"label": "Caused by: Instant Validation",
"source": "app/suspense-in-root/runtime/suspense-too-high/page.tsx (4:33) @ unstable_instant
> 4 | export const unstable_instant = true
| ^",
"stack": [
"unstable_instant app/suspense-in-root/runtime/suspense-too-high/page.tsx (4:33)",
"Set.forEach <anonymous>",
],
},
],
"code": "E1164",
"description": "Next.js encountered uncached data during the initial render.",
"environmentLabel": "Server",
"label": "Instant",
"source": "app/suspense-in-root/runtime/suspense-too-high/page.tsx (24:19) @ Dynamic
> 24 | await connection()
| ^",
"stack": [
"Dynamic app/suspense-in-root/runtime/suspense-too-high/page.tsx (24:19)",
"Page app/suspense-in-root/runtime/suspense-too-high/page.tsx (17:9)",
],
}
`)
} else {
const result = await prerender(
'/suspense-in-root/runtime/suspense-too-high'
)
expect(extractBuildValidationError(result.cliOutput))
.toMatchInlineSnapshot(`
"Error: Route "/suspense-in-root/runtime/suspense-too-high": Next.js encountered uncached data during the initial render.
\`fetch(...)\` or \`connection()\` accessed outside of \`<Suspense>\` blocks navigation, leading to a slower user experience.
Ways to fix this:
- Cache the data access with \`"use cache"\`
- Move the data access into a child component within a <Suspense> boundary
- Set \`export const instant = false\` to allow a blocking route
Learn more: https://nextjs.org/docs/messages/blocking-route
at div (<anonymous>)
at main (<anonymous>)
at a (<anonymous>)
at body (<anonymous>)
at html (<anonymous>)
at b (<anonymous>)
Build-time instant validation failed for route "/suspense-in-root/runtime/suspense-too-high".
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 "/suspense-in-root/runtime/suspense-too-high" in your browser to investigate the error.
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
Stopping prerender due to instant validation errors."
`)
expect(result.exitCode).toBe(1)
}
})
it('invalid - runtime prefetch - sync IO after runtime API', async () => {
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/runtime/invalid-sync-io'
)
await expect(browser).toDisplayCollapsedRedbox(`
{
"code": "E394",
"description": "Route "/suspense-in-root/runtime/invalid-sync-io" used \`Date.now()\` before accessing either uncached data (e.g. \`fetch()\`) or awaiting \`connection()\`. When configured for Runtime prefetching, 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-runtime-current-time",
"environmentLabel": "Server",
"label": "Console Error",
"source": "app/suspense-in-root/runtime/invalid-sync-io/page.tsx (8:20) @ Page
> 8 | const now = Date.now()
| ^",
"stack": [
"Page app/suspense-in-root/runtime/invalid-sync-io/page.tsx (8:20)",
"Page <anonymous>",
],
}
`)
} else {
const result = await prerender(
'/suspense-in-root/runtime/invalid-sync-io'
)
expect(extractBuildValidationError(result.cliOutput))
.toMatchInlineSnapshot(`
"Error: Route "/suspense-in-root/runtime/invalid-sync-io" used \`Date.now()\` before accessing either uncached data (e.g. \`fetch()\`) or awaiting \`connection()\`. When configured for Runtime prefetching, 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-runtime-current-time
at a (app/suspense-in-root/runtime/invalid-sync-io/page.tsx:8:20)
6 | export default async function Page() {
7 | await cookies()
> 8 | const now = Date.now()
| ^
9 | return (
10 | <main>
11 | <p>This page uses sync IO after awaiting cookies(): {now}</p>
Error: Route "/suspense-in-root/runtime/invalid-sync-io" used \`Date.now()\` before accessing either uncached data (e.g. \`fetch()\`) or awaiting \`connection()\`. When configured for Runtime prefetching, 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-runtime-current-time
at b (app/suspense-in-root/runtime/invalid-sync-io/page.tsx:8:20)
6 | export default async function Page() {
7 | await cookies()
> 8 | const now = Date.now()
| ^
9 | return (
10 | <main>
11 | <p>This page uses sync IO after awaiting cookies(): {now}</p>
Build-time instant validation failed for route "/suspense-in-root/runtime/invalid-sync-io".
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 "/suspense-in-root/runtime/invalid-sync-io" in your browser to investigate the error.
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
Stopping prerender due to instant validation errors."
`)
expect(result.exitCode).toBe(1)
}
})
it('invalid - runtime prefetch - sync IO in runtime segment with valid static parent', async () => {
// The static parent layout has sync IO after cookies() which is fine
// because it's not runtime-prefetchable. But the page itself has
// runtime prefetch enabled and also has sync IO after cookies(),
// which should error.
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/runtime/invalid-sync-io-in-runtime-with-valid-static-parent'
)
await expect(browser).toDisplayCollapsedRedbox(`
{
"code": "E394",
"description": "Route "/suspense-in-root/runtime/invalid-sync-io-in-runtime-with-valid-static-parent" used \`Date.now()\` before accessing either uncached data (e.g. \`fetch()\`) or awaiting \`connection()\`. When configured for Runtime prefetching, 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-runtime-current-time",
"environmentLabel": "Server",
"label": "Console Error",
"source": "app/suspense-in-root/runtime/invalid-sync-io-in-runtime-with-valid-static-parent/page.tsx (12:20) @ Page
> 12 | const now = Date.now()
| ^",
"stack": [
"Page app/suspense-in-root/runtime/invalid-sync-io-in-runtime-with-valid-static-parent/page.tsx (12:20)",
"Page <anonymous>",
],
}
`)
} else {
const result = await prerender(
'/suspense-in-root/runtime/invalid-sync-io-in-runtime-with-valid-static-parent'
)
expect(extractBuildValidationError(result.cliOutput))
.toMatchInlineSnapshot(`
"Error: Route "/suspense-in-root/runtime/invalid-sync-io-in-runtime-with-valid-static-parent" used \`Date.now()\` before accessing either uncached data (e.g. \`fetch()\`) or awaiting \`connection()\`. When configured for Runtime prefetching, 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-runtime-current-time
at a (app/suspense-in-root/runtime/invalid-sync-io-in-runtime-with-valid-static-parent/page.tsx:12:20)
10 | export default async function Page() {
11 | await cookies()
> 12 | const now = Date.now()
| ^
13 | return (
14 | <main>
15 | <p>Runtime page with sync IO after cookies: {now}</p>
Error: Route "/suspense-in-root/runtime/invalid-sync-io-in-runtime-with-valid-static-parent" used \`Date.now()\` before accessing either uncached data (e.g. \`fetch()\`) or awaiting \`connection()\`. When configured for Runtime prefetching, 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-runtime-current-time
at b (app/suspense-in-root/runtime/invalid-sync-io-in-runtime-with-valid-static-parent/page.tsx:12:20)
10 | export default async function Page() {
11 | await cookies()
> 12 | const now = Date.now()
| ^
13 | return (
14 | <main>
15 | <p>Runtime page with sync IO after cookies: {now}</p>
Error: Route "/suspense-in-root/runtime/invalid-sync-io-in-runtime-with-valid-static-parent" used \`Date.now()\` before accessing either uncached data (e.g. \`fetch()\`) or awaiting \`connection()\`. When configured for Runtime prefetching, 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-runtime-current-time
at c (app/suspense-in-root/runtime/invalid-sync-io-in-runtime-with-valid-static-parent/page.tsx:12:20)
10 | export default async function Page() {
11 | await cookies()
> 12 | const now = Date.now()
| ^
13 | return (
14 | <main>
15 | <p>Runtime page with sync IO after cookies: {now}</p>
Build-time instant validation failed for route "/suspense-in-root/runtime/invalid-sync-io-in-runtime-with-valid-static-parent".
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 "/suspense-in-root/runtime/invalid-sync-io-in-runtime-with-valid-static-parent" in your browser to investigate the error.
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
Stopping prerender due to instant validation errors."
`)
expect(result.exitCode).toBe(1)
}
})
it('invalid - runtime prefetch - sync IO after public cache with cookie input', async () => {
// A public "use cache" function receives cookies() as a promise
// input (for cache keying). The cache body doesn't read the cookies.
// After the cache resolves, Date.now() is sync IO that should error
// because the cookies input causes the cache to resolve during the
// EarlyRuntime stage where canSyncInterrupt returns true.
//
// If the stage discrimination for cache inputs were broken (always
// using Runtime instead of getRuntimeStage), the cookies would
// resolve at Runtime where canSyncInterrupt returns false, and the
// sync IO would be silently allowed.
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/runtime/invalid-sync-io-after-cache-with-cookie-input'
)
await expect(browser).toDisplayCollapsedRedbox(`
{
"code": "E394",
"description": "Route "/suspense-in-root/runtime/invalid-sync-io-after-cache-with-cookie-input" used \`Date.now()\` before accessing either uncached data (e.g. \`fetch()\`) or awaiting \`connection()\`. When configured for Runtime prefetching, 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-runtime-current-time",
"environmentLabel": "Server",
"label": "Console Error",
"source": "app/suspense-in-root/runtime/invalid-sync-io-after-cache-with-cookie-input/page.tsx (28:20) @ Page
> 28 | const now = Date.now()
| ^",
"stack": [
"Page app/suspense-in-root/runtime/invalid-sync-io-after-cache-with-cookie-input/page.tsx (28:20)",
"Page <anonymous>",
],
}
`)
} else {
const result = await prerender(
'/suspense-in-root/runtime/invalid-sync-io-after-cache-with-cookie-input'
)
expect(extractBuildValidationError(result.cliOutput))
.toMatchInlineSnapshot(`
"Error: Route "/suspense-in-root/runtime/invalid-sync-io-after-cache-with-cookie-input" accessed cookie "testCookie" which is not defined in the \`samples\` of \`unstable_instant\`. Add it to the sample's \`cookies\` array, or \`{ name: "testCookie", value: null }\` if it should be absent.
at <unknown> (app/suspense-in-root/runtime/invalid-sync-io-after-cache-with-cookie-input/page.tsx:26:49)
24 |
25 | export default async function Page() {
> 26 | const cookiePromise = cookies().then((c) => c.get('testCookie')?.value ?? '')
| ^
27 | await cachedFn(cookiePromise)
28 | const now = Date.now()
29 | return ( {
digest: 'INSTANT_VALIDATION_ERROR'
}
Build-time instant validation failed for route "/suspense-in-root/runtime/invalid-sync-io-after-cache-with-cookie-input".
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 "/suspense-in-root/runtime/invalid-sync-io-after-cache-with-cookie-input" in your browser to investigate the error.
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
Stopping prerender due to instant validation errors."
`)
expect(result.exitCode).toBe(1)
}
})
it('valid - runtime prefetch - sync IO in a static parent layout is allowed', async () => {
// Sync IO (Date.now()) in a layout that is NOT runtime-prefetchable
// should not error, even though the child page has runtime prefetch
// enabled. Only segments that are runtime-prefetchable should be
// validated for sync IO after runtime APIs.
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/runtime/valid-sync-io-in-static-parent'
)
await expectNoDevValidationErrors(browser, await browser.url())
} else {
const result = await prerender(
'/suspense-in-root/runtime/valid-sync-io-in-static-parent'
)
expectNoBuildValidationErrors(result)
}
})
it('invalid - runtime prefetch - sync IO in generateMetadata', async () => {
// The page has runtime prefetch enabled. generateMetadata uses
// cookies() then Date.now(). Since metadata belongs to the Page
// and the Page is runtime-prefetchable, this should error.
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/runtime/invalid-sync-io-in-generate-metadata'
)
await expect(browser).toDisplayCollapsedRedbox(`
{
"code": "E394",
"description": "Route "/suspense-in-root/runtime/invalid-sync-io-in-generate-metadata" used \`Date.now()\` before accessing either uncached data (e.g. \`fetch()\`) or awaiting \`connection()\`. When configured for Runtime prefetching, 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-runtime-current-time",
"environmentLabel": "Server",
"label": "Console Error",
"source": "app/suspense-in-root/runtime/invalid-sync-io-in-generate-metadata/page.tsx (9:20) @ Module.generateMetadata
> 9 | const now = Date.now()
| ^",
"stack": [
"Module.generateMetadata app/suspense-in-root/runtime/invalid-sync-io-in-generate-metadata/page.tsx (9:20)",
"Next.MetadataOutlet <anonymous>",
],
}
`)
} else {
const result = await prerender(
'/suspense-in-root/runtime/invalid-sync-io-in-generate-metadata'
)
expect(extractBuildValidationError(result.cliOutput))
.toMatchInlineSnapshot(`
"Error: Route "/suspense-in-root/runtime/invalid-sync-io-in-generate-metadata" used \`Date.now()\` before accessing either uncached data (e.g. \`fetch()\`) or awaiting \`connection()\`. When configured for Runtime prefetching, 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-runtime-current-time
at Module.e [as generateMetadata] (app/suspense-in-root/runtime/invalid-sync-io-in-generate-metadata/page.tsx:9:20)
7 | export async function generateMetadata() {
8 | await cookies()
> 9 | const now = Date.now()
| ^
10 | return {
11 | title: \`Sync IO in metadata: \${now}\`,
12 | }
Error: Route "/suspense-in-root/runtime/invalid-sync-io-in-generate-metadata" used \`Date.now()\` before accessing either uncached data (e.g. \`fetch()\`) or awaiting \`connection()\`. When configured for Runtime prefetching, 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-runtime-current-time
at Module.e [as generateMetadata] (app/suspense-in-root/runtime/invalid-sync-io-in-generate-metadata/page.tsx:9:20)
7 | export async function generateMetadata() {
8 | await cookies()
> 9 | const now = Date.now()
| ^
10 | return {
11 | title: \`Sync IO in metadata: \${now}\`,
12 | }
Build-time instant validation failed for route "/suspense-in-root/runtime/invalid-sync-io-in-generate-metadata".
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 "/suspense-in-root/runtime/invalid-sync-io-in-generate-metadata" in your browser to investigate the error.
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
Stopping prerender due to instant validation errors."
`)
expect(result.exitCode).toBe(1)
}
})
it('valid - runtime prefetch - sync IO in generateMetadata on a static page is allowed', async () => {
// The page does NOT have runtime prefetch. generateMetadata uses
// cookies() then Date.now(). Since no segment is runtime-prefetchable,
// sync IO in generateMetadata should be allowed.
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/runtime/valid-sync-io-in-generate-metadata-static-page'
)
await expectNoDevValidationErrors(browser, await browser.url())
} else {
const result = await prerender(
'/suspense-in-root/runtime/valid-sync-io-in-generate-metadata-static-page'
)
expectNoBuildValidationErrors(result)
}
})
it('invalid - runtime prefetch - sync IO in layout generateMetadata when page is prefetchable', async () => {
// The layout has generateMetadata with sync IO after cookies().
// The layout itself does NOT have runtime prefetch, but the child
// page does. Since metadata belongs to the Page, and the Page is
// runtime-prefetchable, sync IO in the layout's generateMetadata
// should error.
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/runtime/invalid-sync-io-in-layout-generate-metadata'
)
await expect(browser).toDisplayCollapsedRedbox(`
{
"code": "E394",
"description": "Route "/suspense-in-root/runtime/invalid-sync-io-in-layout-generate-metadata" used \`Date.now()\` before accessing either uncached data (e.g. \`fetch()\`) or awaiting \`connection()\`. When configured for Runtime prefetching, 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-runtime-current-time",
"environmentLabel": "Server",
"label": "Console Error",
"source": "app/suspense-in-root/runtime/invalid-sync-io-in-layout-generate-metadata/layout.tsx (11:20) @ Module.generateMetadata
> 11 | const now = Date.now()
| ^",
"stack": [
"Module.generateMetadata app/suspense-in-root/runtime/invalid-sync-io-in-layout-generate-metadata/layout.tsx (11:20)",
"Next.MetadataOutlet <anonymous>",
],
}
`)
} else {
const result = await prerender(
'/suspense-in-root/runtime/invalid-sync-io-in-layout-generate-metadata'
)
expect(extractBuildValidationError(result.cliOutput))
.toMatchInlineSnapshot(`
"Error: Route "/suspense-in-root/runtime/invalid-sync-io-in-layout-generate-metadata" used \`Date.now()\` before accessing either uncached data (e.g. \`fetch()\`) or awaiting \`connection()\`. When configured for Runtime prefetching, 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-runtime-current-time
at Module.d [as generateMetadata] (app/suspense-in-root/runtime/invalid-sync-io-in-layout-generate-metadata/layout.tsx:11:20)
9 | export async function generateMetadata() {
10 | await cookies()
> 11 | const now = Date.now()
| ^
12 | return {
13 | title: \`Layout metadata with sync IO: \${now}\`,
14 | }
Error: Route "/suspense-in-root/runtime/invalid-sync-io-in-layout-generate-metadata" used \`Date.now()\` before accessing either uncached data (e.g. \`fetch()\`) or awaiting \`connection()\`. When configured for Runtime prefetching, 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-runtime-current-time
at Module.d [as generateMetadata] (app/suspense-in-root/runtime/invalid-sync-io-in-layout-generate-metadata/layout.tsx:11:20)
9 | export async function generateMetadata() {
10 | await cookies()
> 11 | const now = Date.now()
| ^
12 | return {
13 | title: \`Layout metadata with sync IO: \${now}\`,
14 | }
Error: Route "/suspense-in-root/runtime/invalid-sync-io-in-layout-generate-metadata" used \`Date.now()\` before accessing either uncached data (e.g. \`fetch()\`) or awaiting \`connection()\`. When configured for Runtime prefetching, 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-runtime-current-time
at Module.d [as generateMetadata] (app/suspense-in-root/runtime/invalid-sync-io-in-layout-generate-metadata/layout.tsx:11:20)
9 | export async function generateMetadata() {
10 | await cookies()
> 11 | const now = Date.now()
| ^
12 | return {
13 | title: \`Layout metadata with sync IO: \${now}\`,
14 | }
Build-time instant validation failed for route "/suspense-in-root/runtime/invalid-sync-io-in-layout-generate-metadata".
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 "/suspense-in-root/runtime/invalid-sync-io-in-layout-generate-metadata" in your browser to investigate the error.
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
Stopping prerender due to instant validation errors."
`)
expect(result.exitCode).toBe(1)
}
})
it('valid - runtime prefetch - sync IO in layout generateMetadata when page is NOT prefetchable', async () => {
// The layout has generateMetadata with sync IO after cookies().
// Neither the layout nor the page has runtime prefetch. Since no
// segment is runtime-prefetchable, sync IO in generateMetadata
// should be allowed.
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/runtime/valid-sync-io-in-layout-generate-metadata-static-page'
)
await expectNoDevValidationErrors(browser, await browser.url())
} else {
const result = await prerender(
'/suspense-in-root/runtime/valid-sync-io-in-layout-generate-metadata-static-page'
)
expectNoBuildValidationErrors(result)
}
})
it('valid - no suspense needed around dynamic in page if loading.js is present', async () => {
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/static/valid-only-loading-around-dynamic'
)
await expectNoDevValidationErrors(browser, await browser.url())
} else {
const result = await prerender(
'/suspense-in-root/static/valid-only-loading-around-dynamic'
)
expectNoBuildValidationErrors(result)
}
})
// The page is inside a route group with loading.tsx on the parent
// URL segment. Validation conservatively treats the route group as
// a potential shared boundary where loading.tsx's Suspense would
// already be revealed. A more advanced system could analyze siblings
// to determine if such a navigation is actually possible.
it('invalid - loading.js above route group does not cover dynamic in page', async () => {
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/static/invalid-loading-above-route-group'
)
await expect(browser).toDisplayCollapsedRedbox(`
{
"cause": [
{
"label": "Caused by: Instant Validation",
"source": "app/suspense-in-root/static/invalid-loading-above-route-group/(group)/page.tsx (4:33) @ unstable_instant
> 4 | export const unstable_instant = true
| ^",
"stack": [
"unstable_instant app/suspense-in-root/static/invalid-loading-above-route-group/(group)/page.tsx (4:33)",
"Set.forEach <anonymous>",
],
},
],
"code": "E1164",
"description": "Next.js encountered uncached data during the initial render.",
"environmentLabel": "Server",
"label": "Instant",
"source": "app/suspense-in-root/static/invalid-loading-above-route-group/(group)/page.tsx (34:19) @ Dynamic
> 34 | await connection()
| ^",
"stack": [
"Dynamic app/suspense-in-root/static/invalid-loading-above-route-group/(group)/page.tsx (34:19)",
"Page app/suspense-in-root/static/invalid-loading-above-route-group/(group)/page.tsx (22:9)",
],
}
`)
} else {
const result = await prerender(
'/suspense-in-root/static/invalid-loading-above-route-group/(group)'
)
expect(extractBuildValidationError(result.cliOutput))
.toMatchInlineSnapshot(`
"Error: Route "/suspense-in-root/static/invalid-loading-above-route-group": Next.js encountered uncached data during the initial render.
\`fetch(...)\` or \`connection()\` accessed outside of \`<Suspense>\` blocks navigation, leading to a slower user experience.
Ways to fix this:
- Cache the data access with \`"use cache"\`
- Move the data access into a child component within a <Suspense> boundary
- Set \`export const instant = false\` to allow a blocking route
Learn more: https://nextjs.org/docs/messages/blocking-route
at div (<anonymous>)
at main (<anonymous>)
at a (<anonymous>)
at body (<anonymous>)
at html (<anonymous>)
at b (<anonymous>)
Build-time instant validation failed for route "/suspense-in-root/static/invalid-loading-above-route-group".
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 "/suspense-in-root/static/invalid-loading-above-route-group" in your browser to investigate the error.
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
Stopping prerender due to instant validation errors."
`)
expect(result.exitCode).toBe(1)
}
})
it('invalid - loading.js covers page, but not layout at the same level', async () => {
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/static/invalid-dynamic-layout-with-loading'
)
await expect(browser).toDisplayCollapsedRedbox(`
{
"cause": [
{
"label": "Caused by: Instant Validation",
"source": "app/suspense-in-root/static/invalid-dynamic-layout-with-loading/layout.tsx (4:33) @ unstable_instant
> 4 | export const unstable_instant = true
| ^",
"stack": [
"unstable_instant app/suspense-in-root/static/invalid-dynamic-layout-with-loading/layout.tsx (4:33)",
"Set.forEach <anonymous>",
],
},
],
"code": "E1164",
"description": "Next.js encountered uncached data during the initial render.",
"environmentLabel": "Server",
"label": "Instant",
"source": "app/suspense-in-root/static/invalid-dynamic-layout-with-loading/layout.tsx (24:19) @ Dynamic
> 24 | await connection()
| ^",
"stack": [
"Dynamic app/suspense-in-root/static/invalid-dynamic-layout-with-loading/layout.tsx (24:19)",
"Layout app/suspense-in-root/static/invalid-dynamic-layout-with-loading/layout.tsx (15:9)",
],
}
`)
} else {
const result = await prerender(
'/suspense-in-root/static/invalid-dynamic-layout-with-loading'
)
expect(extractBuildValidationError(result.cliOutput))
.toMatchInlineSnapshot(`
"Error: Route "/suspense-in-root/static/invalid-dynamic-layout-with-loading": Next.js encountered uncached data during the initial render.
\`fetch(...)\` or \`connection()\` accessed outside of \`<Suspense>\` blocks navigation, leading to a slower user experience.
Ways to fix this:
- Cache the data access with \`"use cache"\`
- Move the data access into a child component within a <Suspense> boundary
- Set \`export const instant = false\` to allow a blocking route
Learn more: https://nextjs.org/docs/messages/blocking-route
at div (<anonymous>)
at body (<anonymous>)
at html (<anonymous>)
at a (<anonymous>)
Build-time instant validation failed for route "/suspense-in-root/static/invalid-dynamic-layout-with-loading".
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 "/suspense-in-root/static/invalid-dynamic-layout-with-loading" in your browser to investigate the error.
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
Stopping prerender due to instant validation errors."
`)
expect(result.exitCode).toBe(1)
}
})
describe('blocking', () => {
it('valid - blocking layout with unstable_instant = false is allowed to block', async () => {
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/static/blocking-layout'
)
await expectNoDevValidationErrors(browser, await browser.url())
} else {
const result = await prerender(
'/suspense-in-root/static/blocking-layout'
)
expectNoBuildValidationErrors(result)
}
})
it('invalid - missing suspense inside blocking layout', async () => {
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/static/blocking-layout/missing-suspense-around-dynamic'
)
await expect(browser).toDisplayCollapsedRedbox(`
{
"cause": [
{
"label": "Caused by: Instant Validation",
"source": "app/suspense-in-root/static/blocking-layout/missing-suspense-around-dynamic/page.tsx (3:33) @ unstable_instant
> 3 | export const unstable_instant = true
| ^",
"stack": [
"unstable_instant app/suspense-in-root/static/blocking-layout/missing-suspense-around-dynamic/page.tsx (3:33)",
"Set.forEach <anonymous>",
],
},
],
"code": "E1166",
"description": "Next.js encountered runtime data during the initial render.",
"environmentLabel": "Server",
"label": "Instant",
"source": "app/suspense-in-root/static/blocking-layout/missing-suspense-around-dynamic/page.tsx (6:16) @ Page
> 6 | await cookies()
| ^",
"stack": [
"Page app/suspense-in-root/static/blocking-layout/missing-suspense-around-dynamic/page.tsx (6:16)",
],
}
`)
} else {
const result = await prerender(
'/suspense-in-root/static/blocking-layout/missing-suspense-around-dynamic'
)
expect(extractBuildValidationError(result.cliOutput))
.toMatchInlineSnapshot(`
"Error: Route "/suspense-in-root/static/blocking-layout/missing-suspense-around-dynamic": Next.js encountered runtime data during the initial render.
\`cookies()\`, \`headers()\`, \`params\`, or \`searchParams\` accessed outside of \`<Suspense>\` blocks navigation, leading to a slower user experience.
Ways to fix this:
- Move the data access into a child component within a <Suspense> boundary
- Use \`generateStaticParams\` to make route params static
- Set \`export const instant = false\` to allow a blocking route
Learn more: https://nextjs.org/docs/messages/blocking-route
at body (<anonymous>)
at html (<anonymous>)
at a (<anonymous>)
Build-time instant validation failed for route "/suspense-in-root/static/blocking-layout/missing-suspense-around-dynamic".
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 "/suspense-in-root/static/blocking-layout/missing-suspense-around-dynamic" in your browser to investigate the error.
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
Stopping prerender due to instant validation errors."
`)
expect(result.exitCode).toBe(1)
}
})
it('valid - blocking page inside a static layout is allowed if the layout has suspense', async () => {
if (isNextDev) {
const browser = await navigateTo(
'/default/static/valid-blocking-inside-static'
)
await expectNoDevValidationErrors(browser, await browser.url())
} else {
const result = await prerender(
'/default/static/valid-blocking-inside-static'
)
expectNoBuildValidationErrors(result)
}
})
it('valid - blocking page inside a runtime layout is allowed if the layout has suspense', async () => {
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/runtime/valid-blocking-inside-runtime'
)
await expectNoDevValidationErrors(browser, await browser.url())
} else {
const result = await prerender(
'/suspense-in-root/runtime/valid-blocking-inside-runtime'
)
expectNoBuildValidationErrors(result)
}
})
it('invalid - blocking page inside a static layout is not allowed if the layout has no suspense', async () => {
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/static/invalid-blocking-inside-static'
)
await expect(browser).toDisplayCollapsedRedbox(`
{
"cause": [
{
"label": "Caused by: Instant Validation",
"source": "app/suspense-in-root/static/invalid-blocking-inside-static/layout.tsx (1:33) @ unstable_instant
> 1 | export const unstable_instant = true
| ^",
"stack": [
"unstable_instant app/suspense-in-root/static/invalid-blocking-inside-static/layout.tsx (1:33)",
"Set.forEach <anonymous>",
],
},
],
"code": "E1166",
"description": "Next.js encountered runtime data during the initial render.",
"environmentLabel": "Server",
"label": "Instant",
"source": "app/suspense-in-root/static/invalid-blocking-inside-static/page.tsx (6:16) @ BlockingPage
> 6 | await cookies()
| ^",
"stack": [
"BlockingPage app/suspense-in-root/static/invalid-blocking-inside-static/page.tsx (6:16)",
],
}
`)
} else {
const result = await prerender(
'/suspense-in-root/static/invalid-blocking-inside-static'
)
expect(extractBuildValidationError(result.cliOutput))
.toMatchInlineSnapshot(`
"Error: Route "/suspense-in-root/static/invalid-blocking-inside-static": Next.js encountered runtime data during the initial render.
\`cookies()\`, \`headers()\`, \`params\`, or \`searchParams\` accessed outside of \`<Suspense>\` blocks navigation, leading to a slower user experience.
Ways to fix this:
- Move the data access into a child component within a <Suspense> boundary
- Use \`generateStaticParams\` to make route params static
- Set \`export const instant = false\` to allow a blocking route
Learn more: https://nextjs.org/docs/messages/blocking-route
at div (<anonymous>)
at body (<anonymous>)
at html (<anonymous>)
at a (<anonymous>)
Build-time instant validation failed for route "/suspense-in-root/static/invalid-blocking-inside-static".
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 "/suspense-in-root/static/invalid-blocking-inside-static" in your browser to investigate the error.
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
Stopping prerender due to instant validation errors."
`)
expect(result.exitCode).toBe(1)
}
})
it('invalid - blocking page inside a runtime layout is not allowed if the layout has no suspense', async () => {
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/runtime/invalid-blocking-inside-runtime'
)
await expect(browser).toDisplayCollapsedRedbox(`
{
"cause": [
{
"label": "Caused by: Instant Validation",
"source": "app/suspense-in-root/runtime/invalid-blocking-inside-runtime/layout.tsx (3:33) @ unstable_instant
> 3 | export const unstable_instant = true
| ^",
"stack": [
"unstable_instant app/suspense-in-root/runtime/invalid-blocking-inside-runtime/layout.tsx (3:33)",
"Set.forEach <anonymous>",
],
},
],
"code": "E1164",
"description": "Next.js encountered uncached data during the initial render.",
"environmentLabel": "Server",
"label": "Instant",
"source": "app/suspense-in-root/runtime/invalid-blocking-inside-runtime/page.tsx (6:19) @ BlockingPage
> 6 | await connection()
| ^",
"stack": [
"BlockingPage app/suspense-in-root/runtime/invalid-blocking-inside-runtime/page.tsx (6:19)",
],
}
`)
} else {
const result = await prerender(
'/suspense-in-root/runtime/invalid-blocking-inside-runtime'
)
expect(extractBuildValidationError(result.cliOutput))
.toMatchInlineSnapshot(`
"Error: Route "/suspense-in-root/runtime/invalid-blocking-inside-runtime": Next.js encountered uncached data during the initial render.
\`fetch(...)\` or \`connection()\` accessed outside of \`<Suspense>\` blocks navigation, leading to a slower user experience.
Ways to fix this:
- Cache the data access with \`"use cache"\`
- Move the data access into a child component within a <Suspense> boundary
- Set \`export const instant = false\` to allow a blocking route
Learn more: https://nextjs.org/docs/messages/blocking-route
at div (<anonymous>)
at body (<anonymous>)
at html (<anonymous>)
at a (<anonymous>)
Build-time instant validation failed for route "/suspense-in-root/runtime/invalid-blocking-inside-runtime".
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 "/suspense-in-root/runtime/invalid-blocking-inside-runtime" in your browser to investigate the error.
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
Stopping prerender due to instant validation errors."
`)
expect(result.exitCode).toBe(1)
}
})
})
describe('invalid - missing suspense in parallel slot', () => {
// The "caused by" source differs between bundlers due to parallel
it('index', async () => {
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/static/missing-suspense-in-parallel-route'
)
await expect(browser).toDisplayCollapsedRedbox(`
{
"cause": [
{
"label": "Caused by: Instant Validation",
"source": "app/suspense-in-root/static/missing-suspense-in-parallel-route/@slot/layout.tsx (1:33) @ unstable_instant
> 1 | export const unstable_instant = true
| ^",
"stack": [
"unstable_instant app/suspense-in-root/static/missing-suspense-in-parallel-route/@slot/layout.tsx (1:33)",
"Set.forEach <anonymous>",
],
},
],
"code": "E1166",
"description": "Next.js encountered runtime data during the initial render.",
"environmentLabel": "Server",
"label": "Instant",
"source": "app/suspense-in-root/static/missing-suspense-in-parallel-route/@slot/page.tsx (4:16) @ IndexSlot
> 4 | await cookies()
| ^",
"stack": [
"IndexSlot app/suspense-in-root/static/missing-suspense-in-parallel-route/@slot/page.tsx (4:16)",
],
}
`)
} else {
const result = await prerender(
'/suspense-in-root/static/missing-suspense-in-parallel-route'
)
expect(extractBuildValidationError(result.cliOutput))
.toMatchInlineSnapshot(`
"Error: Route "/suspense-in-root/static/missing-suspense-in-parallel-route": Next.js encountered runtime data during the initial render.
\`cookies()\`, \`headers()\`, \`params\`, or \`searchParams\` accessed outside of \`<Suspense>\` blocks navigation, leading to a slower user experience.
Ways to fix this:
- Move the data access into a child component within a <Suspense> boundary
- Use \`generateStaticParams\` to make route params static
- Set \`export const instant = false\` to allow a blocking route
Learn more: https://nextjs.org/docs/messages/blocking-route
at div (<anonymous>)
at div (<anonymous>)
at body (<anonymous>)
at html (<anonymous>)
at a (<anonymous>)
Build-time instant validation failed for route "/suspense-in-root/static/missing-suspense-in-parallel-route".
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 "/suspense-in-root/static/missing-suspense-in-parallel-route" in your browser to investigate the error.
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
Stopping prerender due to instant validation errors."
`)
expect(result.exitCode).toBe(1)
}
})
it('subpage', async () => {
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/static/missing-suspense-in-parallel-route/foo'
)
await expect(browser).toDisplayCollapsedRedbox(`
{
"cause": [
{
"label": "Caused by: Instant Validation",
"source": "app/suspense-in-root/static/missing-suspense-in-parallel-route/foo/page.tsx (1:33) @ unstable_instant
> 1 | export const unstable_instant = true
| ^",
"stack": [
"unstable_instant app/suspense-in-root/static/missing-suspense-in-parallel-route/foo/page.tsx (1:33)",
"Set.forEach <anonymous>",
],
},
],
"code": "E1166",
"description": "Next.js encountered runtime data during the initial render.",
"environmentLabel": "Server",
"label": "Instant",
"source": "app/suspense-in-root/static/missing-suspense-in-parallel-route/@slot/foo/page.tsx (4:16) @ FooSlot
> 4 | await cookies()
| ^",
"stack": [
"FooSlot app/suspense-in-root/static/missing-suspense-in-parallel-route/@slot/foo/page.tsx (4:16)",
],
}
`)
} else {
const result = await prerender(
'/suspense-in-root/static/missing-suspense-in-parallel-route/foo'
)
expect(extractBuildValidationError(result.cliOutput))
.toMatchInlineSnapshot(`
"Error: Route "/suspense-in-root/static/missing-suspense-in-parallel-route/foo": Next.js encountered runtime data during the initial render.
\`cookies()\`, \`headers()\`, \`params\`, or \`searchParams\` accessed outside of \`<Suspense>\` blocks navigation, leading to a slower user experience.
Ways to fix this:
- Move the data access into a child component within a <Suspense> boundary
- Use \`generateStaticParams\` to make route params static
- Set \`export const instant = false\` to allow a blocking route
Learn more: https://nextjs.org/docs/messages/blocking-route
at div (<anonymous>)
at div (<anonymous>)
at body (<anonymous>)
at html (<anonymous>)
at a (<anonymous>)
Build-time instant validation failed for route "/suspense-in-root/static/missing-suspense-in-parallel-route/foo".
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 "/suspense-in-root/static/missing-suspense-in-parallel-route/foo" in your browser to investigate the error.
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
Stopping prerender due to instant validation errors."
`)
expect(result.exitCode).toBe(1)
}
})
it('default slot', async () => {
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/static/missing-suspense-in-parallel-route/bar'
)
await expect(browser).toDisplayCollapsedRedbox(`
{
"cause": [
{
"label": "Caused by: Instant Validation",
"source": "app/suspense-in-root/static/missing-suspense-in-parallel-route/bar/page.tsx (1:33) @ unstable_instant
> 1 | export const unstable_instant = true
| ^",
"stack": [
"unstable_instant app/suspense-in-root/static/missing-suspense-in-parallel-route/bar/page.tsx (1:33)",
"Set.forEach <anonymous>",
],
},
],
"code": "E1166",
"description": "Next.js encountered runtime data during the initial render.",
"environmentLabel": "Server",
"label": "Instant",
"source": "app/suspense-in-root/static/missing-suspense-in-parallel-route/@slot/default.tsx (4:16) @ DefaultSlot
> 4 | await cookies()
| ^",
"stack": [
"DefaultSlot app/suspense-in-root/static/missing-suspense-in-parallel-route/@slot/default.tsx (4:16)",
],
}
`)
} else {
const result = await prerender(
'/suspense-in-root/static/missing-suspense-in-parallel-route/bar'
)
expect(extractBuildValidationError(result.cliOutput))
.toMatchInlineSnapshot(`
"Error: Route "/suspense-in-root/static/missing-suspense-in-parallel-route/bar": Next.js encountered runtime data during the initial render.
\`cookies()\`, \`headers()\`, \`params\`, or \`searchParams\` accessed outside of \`<Suspense>\` blocks navigation, leading to a slower user experience.
Ways to fix this:
- Move the data access into a child component within a <Suspense> boundary
- Use \`generateStaticParams\` to make route params static
- Set \`export const instant = false\` to allow a blocking route
Learn more: https://nextjs.org/docs/messages/blocking-route
at div (<anonymous>)
at body (<anonymous>)
at html (<anonymous>)
at a (<anonymous>)
Build-time instant validation failed for route "/suspense-in-root/static/missing-suspense-in-parallel-route/bar".
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 "/suspense-in-root/static/missing-suspense-in-parallel-route/bar" in your browser to investigate the error.
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
Stopping prerender due to instant validation errors."
`)
expect(result.exitCode).toBe(1)
}
})
})
describe('client components', () => {
it('unable to validate - parent suspends on client data and blocks children', async () => {
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/static/invalid-client-data-blocks-validation'
)
await expect(browser).toDisplayCollapsedRedbox(`
{
"cause": [
{
"label": "Caused by: Instant Validation",
"source": "app/suspense-in-root/static/invalid-client-data-blocks-validation/page.tsx (1:33) @ unstable_instant
> 1 | export const unstable_instant = true
| ^",
"stack": [
"unstable_instant app/suspense-in-root/static/invalid-client-data-blocks-validation/page.tsx (1:33)",
"Set.forEach <anonymous>",
],
},
],
"code": "E1082",
"description": "Route "/suspense-in-root/static/invalid-client-data-blocks-validation": Could not validate \`unstable_instant\` because a Client Component in a parent segment prevented the page from rendering.",
"environmentLabel": "Server",
"label": "Console Error",
"source": "app/suspense-in-root/static/invalid-client-data-blocks-validation/client.tsx (12:19) @ FetchesClientData
> 12 | const data = use(promise)
| ^",
"stack": [
"FetchesClientData app/suspense-in-root/static/invalid-client-data-blocks-validation/client.tsx (12:19)",
"Layout app/suspense-in-root/static/invalid-client-data-blocks-validation/layout.tsx (17:9)",
],
}
`)
} else {
const result = await prerender(
'/suspense-in-root/static/invalid-client-data-blocks-validation'
)
expect(extractBuildValidationError(result.cliOutput))
.toMatchInlineSnapshot(`
"client-data-fetching-lib :: MISS my-key
client-data-fetching-lib :: MISS my-key
client-data-fetching-lib :: MISS my-key
Error: Route "/suspense-in-root/static/invalid-client-data-blocks-validation": Could not validate \`unstable_instant\` because a Client Component in a parent segment prevented the page from rendering.
at <unknown> (app/suspense-in-root/static/invalid-client-data-blocks-validation/client.tsx:6:37)
at div (<anonymous>)
at body (<anonymous>)
at html (<anonymous>)
at a (<anonymous>)
4 | import { useDataCache } from '../../../../client-data-fetching-lib/client'
5 |
> 6 | export function FetchesClientData({ children }) {
| ^
7 | const dataCache = useDataCache()
8 | const promise = dataCache.getOrLoad('my-key', async () => {
9 | await new Promise<void>((resolve) => setTimeout(resolve, 10))
Build-time instant validation failed for route "/suspense-in-root/static/invalid-client-data-blocks-validation".
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 "/suspense-in-root/static/invalid-client-data-blocks-validation" in your browser to investigate the error.
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
Stopping prerender due to instant validation errors."
`)
expect(result.exitCode).toBe(1)
}
})
it('valid - parent suspends on client data but does not block children', async () => {
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/static/valid-client-data-does-not-block-validation'
)
await expectNoDevValidationErrors(browser, await browser.url())
} else {
const result = await prerender(
'/suspense-in-root/static/valid-client-data-does-not-block-validation'
)
expectNoBuildValidationErrors(result)
}
})
it('valid - parent uses sync IO in a client component', async () => {
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/static/valid-client-api-in-parent/sync-io'
)
await expectNoDevValidationErrors(browser, await browser.url())
} else {
const result = await prerender(
'/suspense-in-root/static/valid-client-api-in-parent/sync-io'
)
expectNoBuildValidationErrors(result)
}
})
it('valid - parent uses dynamic usePathname() in a client component', async () => {
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/static/valid-client-api-in-parent/dynamic-params/123'
)
await expectNoDevValidationErrors(browser, await browser.url())
} else {
const result = await prerender(
'/suspense-in-root/static/valid-client-api-in-parent/dynamic-params/[id]'
)
expectNoBuildValidationErrors(result)
}
})
it('valid - parent uses useSearchParams() in a client component', async () => {
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/static/valid-client-api-in-parent/search-params'
)
await expectNoDevValidationErrors(browser, await browser.url())
} else {
const result = await prerender(
'/suspense-in-root/static/valid-client-api-in-parent/search-params'
)
expectNoBuildValidationErrors(result)
}
})
})
describe('client errors', () => {
function removeExpectedError(
errors: RedboxSnapshot,
shouldRemove: (error: ErrorSnapshot) => boolean
): ErrorSnapshot[] {
if (!Array.isArray(errors)) {
throw new Error('Expected to receive multiple errors to filter')
}
let found = false
const result = errors.filter((err) => {
if (shouldRemove(err)) {
found = true
return false
} else {
return true
}
})
if (!found) {
throw new Error(
`Did not find expected error in errors array: ${JSON.stringify(errors, null, 2)}`
)
}
return result
}
it('unable to validate - client error in parent blocks children', async () => {
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/static/invalid-client-error-in-parent-blocks-children'
)
// We expect a collapsed redbox. We need to open it to assert on the messages.
await openRedbox(browser)
let errors = await createRedboxSnapshot(browser, next)
if (!isClientNav) {
// In SSR, we expect a "Switched to client rendering ..." error because we deliberately throw in a client component.
// However, the timing of when it appears is inconsistent -- sometimes it's before validation errors,
// and sometimes it's after.
// To avoid flakiness, we filter it out (but assert that it appears in the redbox)
errors = removeExpectedError(errors, (err) => {
return (
err.label === 'Recoverable Error' &&
err.description.startsWith(
'Switched to client rendering because the server rendering errored:\n\nNo SSR please'
)
)
})
}
expect(errors).toMatchInlineSnapshot(`
[
{
"description": "Route "/suspense-in-root/static/invalid-client-error-in-parent-blocks-children": Could not validate \`unstable_instant\` because the target segment was prevented from rendering, likely due to the following error.",
"environmentLabel": "Server",
"label": "Console Error",
"source": "app/suspense-in-root/static/invalid-client-error-in-parent-blocks-children/page.tsx (1:33) @ unstable_instant
> 1 | export const unstable_instant = true
| ^",
"stack": [
"unstable_instant app/suspense-in-root/static/invalid-client-error-in-parent-blocks-children/page.tsx (1:33)",
],
},
{
"cause": [
{
"label": "Caused by: Error",
"message": "No SSR please",
"source": "app/suspense-in-root/static/invalid-client-error-in-parent-blocks-children/client.tsx (5:11) @ ErrorInSSR
> 5 | throw new Error('No SSR please')
| ^",
"stack": [
"ErrorInSSR app/suspense-in-root/static/invalid-client-error-in-parent-blocks-children/client.tsx (5:11)",
],
},
],
"code": "E1118",
"description": "An error occurred while attempting to validate instant UI. This error may be preventing the validation from completing.",
"environmentLabel": "Server",
"label": "Console Error",
"source": "app/suspense-in-root/static/invalid-client-error-in-parent-blocks-children/layout.tsx (19:11) @ Layout
> 19 | <ErrorInSSR>{children}</ErrorInSSR>
| ^",
"stack": [
"Layout app/suspense-in-root/static/invalid-client-error-in-parent-blocks-children/layout.tsx (19:11)",
],
},
]
`)
} else {
const result = await prerender(
'/suspense-in-root/static/invalid-client-error-in-parent-blocks-children'
)
expect(extractBuildValidationError(result.cliOutput))
.toMatchInlineSnapshot(`
"Error: Route "/suspense-in-root/static/invalid-client-error-in-parent-blocks-children": Could not validate \`unstable_instant\` because the target segment was prevented from rendering, likely due to the following error.
at ignore-listed frames
Error: An error occurred while attempting to validate instant UI. This error may be preventing the validation from completing.
at <unknown> (app/suspense-in-root/static/invalid-client-error-in-parent-blocks-children/client.tsx:3:30)
at a (<anonymous>)
at div (<anonymous>)
at body (<anonymous>)
at html (<anonymous>)
at b (<anonymous>)
1 | 'use client'
2 |
> 3 | export function ErrorInSSR({ children }) {
| ^
4 | if (typeof window === 'undefined') {
5 | throw new Error('No SSR please')
6 | } {
[cause]: Error: No SSR please
at <unknown> (app/suspense-in-root/static/invalid-client-error-in-parent-blocks-children/client.tsx:5:11)
3 | export function ErrorInSSR({ children }) {
4 | if (typeof window === 'undefined') {
> 5 | throw new Error('No SSR please')
| ^
6 | }
7 | return <>{children}</>
8 | }
}
Build-time instant validation failed for route "/suspense-in-root/static/invalid-client-error-in-parent-blocks-children".
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 "/suspense-in-root/static/invalid-client-error-in-parent-blocks-children" in your browser to investigate the error.
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
Stopping prerender due to instant validation errors."
`)
expect(result.exitCode).toBe(1)
}
})
it('unable to validate - client error in component from node_modules blocks children', async () => {
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/static/invalid-error-in-node-modules-blocks-children'
)
// We expect a collapsed redbox. We need to open it to assert on the messages.
await openRedbox(browser)
let errors = await createRedboxSnapshot(browser, next)
if (!isClientNav) {
// In SSR, we expect a "Switched to client rendering ..." error because we deliberately throw in a client component.
// However, the timing of when it appears is inconsistent -- sometimes it's before validation errors,
// and sometimes it's after.
// To avoid flakiness, we filter it out (but assert that it appears in the redbox)
errors = removeExpectedError(errors, (err) => {
return (
err.label === 'Recoverable Error' &&
err.description.startsWith(
'Switched to client rendering because the server rendering errored:\n\nError from node_modules'
)
)
})
}
expect(errors).toMatchInlineSnapshot(`
[
{
"description": "Route "/suspense-in-root/static/invalid-error-in-node-modules-blocks-children": Could not validate \`unstable_instant\` because the target segment was prevented from rendering, likely due to the following error.",
"environmentLabel": "Server",
"label": "Console Error",
"source": "app/suspense-in-root/static/invalid-error-in-node-modules-blocks-children/page.tsx (1:33) @ unstable_instant
> 1 | export const unstable_instant = true
| ^",
"stack": [
"unstable_instant app/suspense-in-root/static/invalid-error-in-node-modules-blocks-children/page.tsx (1:33)",
],
},
{
"cause": [
{
"label": "Caused by: Error",
"message": "Error from node_modules",
"source": null,
"stack": [],
},
],
"code": "E1118",
"description": "An error occurred while attempting to validate instant UI. This error may be preventing the validation from completing.",
"environmentLabel": "Server",
"label": "Console Error",
"source": "app/suspense-in-root/static/invalid-error-in-node-modules-blocks-children/layout.tsx (21:11) @ Layout
> 21 | <ErrorInSSRFromPackage>{children}</ErrorInSSRFromPackage>
| ^",
"stack": [
"Layout app/suspense-in-root/static/invalid-error-in-node-modules-blocks-children/layout.tsx (21:11)",
],
},
]
`)
} else {
const result = await prerender(
'/suspense-in-root/static/invalid-error-in-node-modules-blocks-children'
)
expect(extractBuildValidationError(result.cliOutput))
.toMatchInlineSnapshot(`
"Error: Route "/suspense-in-root/static/invalid-error-in-node-modules-blocks-children": Could not validate \`unstable_instant\` because the target segment was prevented from rendering, likely due to the following error.
at ignore-listed frames
Error: An error occurred while attempting to validate instant UI. This error may be preventing the validation from completing.
at a (<anonymous>)
at div (<anonymous>)
at body (<anonymous>)
at html (<anonymous>)
at b (<anonymous>) {
[cause]: Error: Error from node_modules
at ignore-listed frames
}
Build-time instant validation failed for route "/suspense-in-root/static/invalid-error-in-node-modules-blocks-children".
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 "/suspense-in-root/static/invalid-error-in-node-modules-blocks-children" in your browser to investigate the error.
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
Stopping prerender due to instant validation errors."
`)
expect(result.exitCode).toBe(1)
}
})
it('unable to validate - CSR bailout from next/dynamic blocks children', async () => {
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/static/invalid-csr-bailout-blocks-children'
)
await expect(browser).toDisplayCollapsedRedbox(`
[
{
"description": "Route "/suspense-in-root/static/invalid-csr-bailout-blocks-children": Could not validate \`unstable_instant\` because the target segment was prevented from rendering, likely due to the following error.",
"environmentLabel": "Server",
"label": "Console Error",
"source": "app/suspense-in-root/static/invalid-csr-bailout-blocks-children/page.tsx (1:33) @ unstable_instant
> 1 | export const unstable_instant = true
| ^",
"stack": [
"unstable_instant app/suspense-in-root/static/invalid-csr-bailout-blocks-children/page.tsx (1:33)",
],
},
{
"cause": [
{
"label": "Caused by: Error",
"message": "Bail out to client-side rendering: next/dynamic",
"source": null,
"stack": [],
},
],
"code": "E1118",
"description": "An error occurred while attempting to validate instant UI. This error may be preventing the validation from completing.",
"environmentLabel": "Server",
"label": "Console Error",
"source": "app/suspense-in-root/static/invalid-csr-bailout-blocks-children/layout.tsx (19:9) @ Layout
> 19 | <LazyClientWrapperWithNoSSR>{children}</LazyClientWrapperWithNoSSR>
| ^",
"stack": [
"Layout app/suspense-in-root/static/invalid-csr-bailout-blocks-children/layout.tsx (19:9)",
],
},
]
`)
} else {
const result = await prerender(
'/suspense-in-root/static/invalid-csr-bailout-blocks-children'
)
expect(extractBuildValidationError(result.cliOutput))
.toMatchInlineSnapshot(`
"Error: Route "/suspense-in-root/static/invalid-csr-bailout-blocks-children": Could not validate \`unstable_instant\` because the target segment was prevented from rendering, likely due to the following error.
at ignore-listed frames
Error: An error occurred while attempting to validate instant UI. This error may be preventing the validation from completing.
at a (<anonymous>)
at b (<anonymous>)
at div (<anonymous>)
at body (<anonymous>)
at html (<anonymous>)
at c (<anonymous>) {
[cause]: Error: Bail out to client-side rendering: next/dynamic
at ignore-listed frames {
reason: 'next/dynamic',
digest: 'BAILOUT_TO_CLIENT_SIDE_RENDERING'
}
}
Build-time instant validation failed for route "/suspense-in-root/static/invalid-csr-bailout-blocks-children".
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 "/suspense-in-root/static/invalid-csr-bailout-blocks-children" in your browser to investigate the error.
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
Stopping prerender due to instant validation errors."
`)
expect(result.exitCode).toBe(1)
}
})
it('unable to validate - client error from sibling of children slot without suspense', async () => {
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/static/invalid-client-error-in-parent-sibling'
)
if (isClientNav) {
// In a client navigation, the redbox will be collapsed.
await openRedbox(browser)
} else {
// In SSR, the redbox will be open due to the missing tags error.
await waitForRedbox(browser)
}
let errors = await createRedboxSnapshot(browser, next)
if (!isClientNav) {
// In SSR, we expect a "Switched to client rendering ..." error because we deliberately throw in a client component.
// However, the timing of when it appears is inconsistent -- sometimes it's before validation errors,
// and sometimes it's after.
// To avoid flakiness, we filter it out (but assert that it appears in the redbox)
errors = removeExpectedError(errors, (err) => {
return (
err.label === 'Runtime Error' &&
err.description.startsWith(
'Missing <html> and <body> tags in the root layout.'
)
)
})
}
expect(errors).toMatchInlineSnapshot(`
[
{
"description": "Route "/suspense-in-root/static/invalid-client-error-in-parent-sibling": Could not validate \`unstable_instant\` because the target segment was prevented from rendering, likely due to the following error.",
"environmentLabel": "Server",
"label": "Console Error",
"source": "app/suspense-in-root/static/invalid-client-error-in-parent-sibling/page.tsx (1:33) @ unstable_instant
> 1 | export const unstable_instant = true
| ^",
"stack": [
"unstable_instant app/suspense-in-root/static/invalid-client-error-in-parent-sibling/page.tsx (1:33)",
],
},
{
"cause": [
{
"label": "Caused by: Error",
"message": "No SSR please",
"source": "app/suspense-in-root/static/invalid-client-error-in-parent-sibling/client.tsx (5:11) @ ErrorInSSR
> 5 | throw new Error('No SSR please')
| ^",
"stack": [
"ErrorInSSR app/suspense-in-root/static/invalid-client-error-in-parent-sibling/client.tsx (5:11)",
],
},
],
"code": "E1118",
"description": "An error occurred while attempting to validate instant UI. This error may be preventing the validation from completing.",
"environmentLabel": "Server",
"label": "Console Error",
"source": "app/suspense-in-root/static/invalid-client-error-in-parent-sibling/layout.tsx (20:7) @ Layout
> 20 | <ErrorInSSR />
| ^",
"stack": [
"Layout app/suspense-in-root/static/invalid-client-error-in-parent-sibling/layout.tsx (20:7)",
],
},
]
`)
} else {
const result = await prerender(
'/suspense-in-root/static/invalid-client-error-in-parent-sibling'
)
expect(extractBuildValidationError(result.cliOutput))
.toMatchInlineSnapshot(`
"Error: Route "/suspense-in-root/static/invalid-client-error-in-parent-sibling": Could not validate \`unstable_instant\` because the target segment was prevented from rendering, likely due to the following error.
at ignore-listed frames
Error: An error occurred while attempting to validate instant UI. This error may be preventing the validation from completing.
at <unknown> (app/suspense-in-root/static/invalid-client-error-in-parent-sibling/client.tsx:5:11)
at body (<anonymous>)
at html (<anonymous>)
at a (<anonymous>)
3 | export function ErrorInSSR() {
4 | if (typeof window === 'undefined') {
> 5 | throw new Error('No SSR please')
| ^
6 | }
7 | return <div>Hello, browser!</div>
8 | } {
[cause]: Error: No SSR please
at <unknown> (app/suspense-in-root/static/invalid-client-error-in-parent-sibling/client.tsx:5:11)
3 | export function ErrorInSSR() {
4 | if (typeof window === 'undefined') {
> 5 | throw new Error('No SSR please')
| ^
6 | }
7 | return <div>Hello, browser!</div>
8 | }
}
Build-time instant validation failed for route "/suspense-in-root/static/invalid-client-error-in-parent-sibling".
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 "/suspense-in-root/static/invalid-client-error-in-parent-sibling" in your browser to investigate the error.
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
Stopping prerender due to instant validation errors."
`)
expect(result.exitCode).toBe(1)
}
})
it('valid - client error from sibling of children slot with suspense', async () => {
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/static/valid-client-error-in-parent-does-not-block-validation'
)
await waitForValidation(await browser.url(), getCliOutputSinceMark)
if (isClientNav) {
// In a client nav, no errors should be reported.
await waitForNoErrorToast(browser, NO_VALIDATION_ERRORS_WAIT)
} else {
// In SSR, we expect to only see the error coming from react.
await expect(browser).toDisplayCollapsedRedbox(`
{
"description": "Switched to client rendering because the server rendering errored:
No SSR please",
"environmentLabel": null,
"label": "Recoverable Error",
"source": "app/suspense-in-root/static/valid-client-error-in-parent-does-not-block-validation/client.tsx (5:11) @ ErrorInSSR
> 5 | throw new Error('No SSR please')
| ^",
"stack": [
"ErrorInSSR app/suspense-in-root/static/valid-client-error-in-parent-does-not-block-validation/client.tsx (5:11)",
],
}
`)
}
} else {
const result = await prerender(
'/suspense-in-root/static/valid-client-error-in-parent-does-not-block-validation'
)
expectNoBuildValidationErrors(result)
}
})
})
describe('head', () => {
it('valid - runtime prefetch - dynamic generateMetadata does not block navigation', async () => {
if (isNextDev) {
// Metadata streams and does not block navigation, so it can access
// dynamic data without failing validation.
const browser = await navigateTo(
'/suspense-in-root/head/valid-dynamic-metadata-in-runtime'
)
await expectNoDevValidationErrors(browser, await browser.url())
} else {
const result = await prerender(
'/suspense-in-root/head/valid-dynamic-metadata-in-runtime'
)
expectNoBuildValidationErrors(result)
}
})
it('valid - static prefetch - runtime generateMetadata does not block navigation', async () => {
if (isNextDev) {
// Metadata streams and does not block navigation, so it can access
// runtime data without failing validation.
const browser = await navigateTo(
'/suspense-in-root/head/valid-runtime-metadata-in-static'
)
await expectNoDevValidationErrors(browser, await browser.url())
} else {
const result = await prerender(
'/suspense-in-root/head/valid-runtime-metadata-in-static'
)
expectNoBuildValidationErrors(result)
}
})
it('invalid - static prefetch - runtime generateViewport blocks navigation', async () => {
if (isNextDev) {
// if generateViewport uses runtime data and we use a static prefetch,
// we won't have it available when navigating, so we'll block and should fail validation.
const browser = await navigateTo(
'/suspense-in-root/head/invalid-runtime-viewport-in-static'
)
await expect(browser).toDisplayCollapsedRedbox(`
{
"cause": [
{
"label": "Caused by: Instant Validation",
"source": "app/suspense-in-root/head/invalid-runtime-viewport-in-static/page.tsx (8:33) @ unstable_instant
> 8 | export const unstable_instant = true
| ^",
"stack": [
"unstable_instant app/suspense-in-root/head/invalid-runtime-viewport-in-static/page.tsx (8:33)",
"Set.forEach <anonymous>",
],
},
],
"code": "E1165",
"description": "Runtime data was accessed inside generateViewport()
Viewport metadata needs to be available on page load so accessing data that comes from a user Request while producing it prevents Next.js from prerendering an initial UI.cookies(), headers(), params, and searchParams are examples of Runtime data that can only come from a user request.
To fix this:
Remove the Runtime data requirement from generateViewport. This allows Next.js to statically prerender generateViewport() as part of the HTML document, so it's instantly visible to the user.
or
Put a <Suspense> around your document <body>.This indicate to Next.js that you are opting into allowing blocking navigations for any page.
params are usually considered Runtime data but if all params are provided a value using generateStaticParams they can be statically prerendered.
Learn more: https://nextjs.org/docs/messages/next-prerender-dynamic-viewport",
"environmentLabel": "Server",
"label": "Blocking Route",
"source": "app/suspense-in-root/head/invalid-runtime-viewport-in-static/page.tsx (11:16) @ Module.generateViewport
> 11 | await cookies()
| ^",
"stack": [
"Module.generateViewport app/suspense-in-root/head/invalid-runtime-viewport-in-static/page.tsx (11:16)",
],
}
`)
} else {
const result = await prerender(
'/suspense-in-root/head/invalid-runtime-viewport-in-static'
)
expect(extractBuildValidationError(result.cliOutput))
.toMatchInlineSnapshot(`
"Error: Route "/suspense-in-root/head/invalid-runtime-viewport-in-static": Next.js encountered runtime data such as \`cookies()\`, \`headers()\`, \`params\`, or \`searchParams\` inside \`generateViewport\`. This delays the entire page from rendering, resulting in a slow user experience. Learn more: https://nextjs.org/docs/messages/next-prerender-dynamic-viewport
at ignore-listed frames
Build-time instant validation failed for route "/suspense-in-root/head/invalid-runtime-viewport-in-static".
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 "/suspense-in-root/head/invalid-runtime-viewport-in-static" in your browser to investigate the error.
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
Stopping prerender due to instant validation errors."
`)
expect(result.exitCode).toBe(1)
}
})
it('invalid - runtime prefetch - dynamic viewport blocks navigation', async () => {
if (isNextDev) {
// if generateViewport uses dynamic data and we use a runtime prefetch,
// we won't have it available when navigating, so we'll block and should fail validation.
const browser = await navigateTo(
'/suspense-in-root/head/invalid-dynamic-viewport-in-runtime'
)
await expect(browser).toDisplayCollapsedRedbox(`
{
"cause": [
{
"label": "Caused by: Instant Validation",
"source": "app/suspense-in-root/head/invalid-dynamic-viewport-in-runtime/page.tsx (6:33) @ unstable_instant
> 6 | export const unstable_instant = true
| ^",
"stack": [
"unstable_instant app/suspense-in-root/head/invalid-dynamic-viewport-in-runtime/page.tsx (6:33)",
"Set.forEach <anonymous>",
],
},
],
"code": "E1167",
"description": "Data that blocks navigation was accessed inside generateViewport()
Viewport metadata needs to be available on page load so accessing data that waits for a user navigation while producing it prevents Next.js from prerendering an initial UI. Uncached data such as fetch(...), cached data with a low expire time, or connection() are all examples of data that only resolve on navigation.
To fix this:
Move the asynchronous await into a Cache Component ("use cache"). This allows Next.js to statically prerender generateViewport() as part of the HTML document, so it's instantly visible to the user.
or
Put a <Suspense> around your document <body>.This indicate to Next.js that you are opting into allowing blocking navigations for any page.
Learn more: https://nextjs.org/docs/messages/next-prerender-dynamic-viewport",
"environmentLabel": "Server",
"label": "Blocking Route",
"source": "app/suspense-in-root/head/invalid-dynamic-viewport-in-runtime/page.tsx (11:19) @ Module.generateViewport
> 11 | await connection()
| ^",
"stack": [
"Module.generateViewport app/suspense-in-root/head/invalid-dynamic-viewport-in-runtime/page.tsx (11:19)",
],
}
`)
} else {
const result = await prerender(
'/suspense-in-root/head/invalid-dynamic-viewport-in-runtime'
)
expect(extractBuildValidationError(result.cliOutput))
.toMatchInlineSnapshot(`
"Error: Route "/suspense-in-root/head/invalid-dynamic-viewport-in-runtime": Next.js encountered uncached data such as \`fetch(...)\` or \`connection()\` inside \`generateViewport\`. This delays the entire page from rendering, resulting in a slow user experience. Learn more: https://nextjs.org/docs/messages/next-prerender-dynamic-viewport
at ignore-listed frames
Build-time instant validation failed for route "/suspense-in-root/head/invalid-dynamic-viewport-in-runtime".
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 "/suspense-in-root/head/invalid-dynamic-viewport-in-runtime" in your browser to investigate the error.
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
Stopping prerender due to instant validation errors."
`)
expect(result.exitCode).toBe(1)
}
})
it('valid - runtime prefetch - runtime generateViewport does not block navigation', async () => {
if (isNextDev) {
// if generateViewport uses runtime data and we use a runtime prefetch,
// we'll have it available when navigating, so we won't block and validation should succeed.
const browser = await navigateTo(
'/suspense-in-root/head/valid-runtime-viewport-in-runtime'
)
await expectNoDevValidationErrors(browser, await browser.url())
} else {
const result = await prerender(
'/suspense-in-root/head/valid-runtime-viewport-in-runtime'
)
expectNoBuildValidationErrors(result)
}
})
it('valid - blocking layout - dynamic viewport is allowed to block', async () => {
if (isNextDev) {
// if generateViewport uses dynamic data, it'll always block regardless of prefetching.
// however, this is valid if the page opts into blocking via `instant = false`.
const browser = await navigateTo(
'/suspense-in-root/head/valid-dynamic-viewport-in-blocking'
)
await expectNoDevValidationErrors(browser, await browser.url())
} else {
const result = await prerender(
'/suspense-in-root/head/valid-dynamic-viewport-in-blocking'
)
// The only way to allow this is to have `instant = false` on the page,
// and no assertions in layouts above -- they can't pass because a dynamic
// generateViewport will always block the navigation.
// This test is just here to ensure this behavior doesn't break.
expectBuildValidationSkipped(result)
}
})
it('invalid - blocking page inside static - dynamic viewport is not allowed to block', async () => {
if (isNextDev) {
// if generateViewport uses dynamic data, it'll always block regardless of prefetching.
// this can be allowed if a page opts into blocking. but if it violates a static
// assertion on a parent layout, it should still fail.
const browser = await navigateTo(
'/suspense-in-root/head/invalid-dynamic-viewport-in-blocking-inside-static'
)
// TODO(instant-validation): why aren't we pointing to `await connection()` here?
await expect(browser).toDisplayCollapsedRedbox(`
{
"cause": [
{
"label": "Caused by: Instant Validation",
"source": "app/suspense-in-root/head/invalid-dynamic-viewport-in-blocking-inside-static/layout.tsx (3:33) @ unstable_instant
> 3 | export const unstable_instant = true
| ^",
"stack": [
"unstable_instant app/suspense-in-root/head/invalid-dynamic-viewport-in-blocking-inside-static/layout.tsx (3:33)",
"Set.forEach <anonymous>",
],
},
],
"code": "E1167",
"description": "Data that blocks navigation was accessed inside generateViewport()
Viewport metadata needs to be available on page load so accessing data that waits for a user navigation while producing it prevents Next.js from prerendering an initial UI. Uncached data such as fetch(...), cached data with a low expire time, or connection() are all examples of data that only resolve on navigation.
To fix this:
Move the asynchronous await into a Cache Component ("use cache"). This allows Next.js to statically prerender generateViewport() as part of the HTML document, so it's instantly visible to the user.
or
Put a <Suspense> around your document <body>.This indicate to Next.js that you are opting into allowing blocking navigations for any page.
Learn more: https://nextjs.org/docs/messages/next-prerender-dynamic-viewport",
"environmentLabel": "Server",
"label": "Blocking Route",
"source": "app/suspense-in-root/head/invalid-dynamic-viewport-in-blocking-inside-static/page.tsx (6:23) @ Module.generateViewport
> 6 | export async function generateViewport(): Promise<Viewport> {
| ^",
"stack": [
"Module.generateViewport app/suspense-in-root/head/invalid-dynamic-viewport-in-blocking-inside-static/page.tsx (6:23)",
],
}
`)
} else {
const result = await prerender(
'/suspense-in-root/head/invalid-dynamic-viewport-in-blocking-inside-static'
)
expect(extractBuildValidationError(result.cliOutput))
.toMatchInlineSnapshot(`
"Error: Route "/suspense-in-root/head/invalid-dynamic-viewport-in-blocking-inside-static": Next.js encountered uncached data such as \`fetch(...)\` or \`connection()\` inside \`generateViewport\`. This delays the entire page from rendering, resulting in a slow user experience. Learn more: https://nextjs.org/docs/messages/next-prerender-dynamic-viewport
at ignore-listed frames
Build-time instant validation failed for route "/suspense-in-root/head/invalid-dynamic-viewport-in-blocking-inside-static".
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 "/suspense-in-root/head/invalid-dynamic-viewport-in-blocking-inside-static" in your browser to investigate the error.
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
Stopping prerender due to instant validation errors."
`)
expect(result.exitCode).toBe(1)
}
})
})
describe('route groups', () => {
it('invalid - config on route group layout - cookies() blocks below', async () => {
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/static/route-group-config-only'
)
await expect(browser).toDisplayCollapsedRedbox(`
{
"cause": [
{
"label": "Caused by: Instant Validation",
"source": "app/suspense-in-root/static/route-group-config-only/(group)/layout.tsx (3:33) @ unstable_instant
> 3 | export const unstable_instant = true
| ^",
"stack": [
"unstable_instant app/suspense-in-root/static/route-group-config-only/(group)/layout.tsx (3:33)",
"Set.forEach <anonymous>",
],
},
],
"code": "E1166",
"description": "Next.js encountered runtime data during the initial render.",
"environmentLabel": "Server",
"label": "Instant",
"source": "app/suspense-in-root/static/route-group-config-only/(group)/page.tsx (4:16) @ Page
> 4 | await cookies()
| ^",
"stack": [
"Page app/suspense-in-root/static/route-group-config-only/(group)/page.tsx (4:16)",
],
}
`)
} else {
const result = await prerender(
'/suspense-in-root/static/route-group-config-only/(group)'
)
expect(extractBuildValidationError(result.cliOutput))
.toMatchInlineSnapshot(`
"Error: Route "/suspense-in-root/static/route-group-config-only": Next.js encountered runtime data during the initial render.
\`cookies()\`, \`headers()\`, \`params\`, or \`searchParams\` accessed outside of \`<Suspense>\` blocks navigation, leading to a slower user experience.
Ways to fix this:
- Move the data access into a child component within a <Suspense> boundary
- Use \`generateStaticParams\` to make route params static
- Set \`export const instant = false\` to allow a blocking route
Learn more: https://nextjs.org/docs/messages/blocking-route
at div (<anonymous>)
at body (<anonymous>)
at html (<anonymous>)
at a (<anonymous>)
Build-time instant validation failed for route "/suspense-in-root/static/route-group-config-only".
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 "/suspense-in-root/static/route-group-config-only" in your browser to investigate the error.
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
Stopping prerender due to instant validation errors."
`)
expect(result.exitCode).toBe(1)
}
})
it('invalid - config on both route group and segment layout - cookies() blocks below', async () => {
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/static/route-group-config-and-segment-config'
)
await expect(browser).toDisplayCollapsedRedbox(`
{
"cause": [
{
"label": "Caused by: Instant Validation",
"source": "app/suspense-in-root/static/route-group-config-and-segment-config/(group)/layout.tsx (3:33) @ unstable_instant
> 3 | export const unstable_instant = true
| ^",
"stack": [
"unstable_instant app/suspense-in-root/static/route-group-config-and-segment-config/(group)/layout.tsx (3:33)",
"Set.forEach <anonymous>",
],
},
],
"code": "E1166",
"description": "Next.js encountered runtime data during the initial render.",
"environmentLabel": "Server",
"label": "Instant",
"source": "app/suspense-in-root/static/route-group-config-and-segment-config/(group)/page.tsx (4:16) @ Page
> 4 | await cookies()
| ^",
"stack": [
"Page app/suspense-in-root/static/route-group-config-and-segment-config/(group)/page.tsx (4:16)",
],
}
`)
} else {
const result = await prerender(
'/suspense-in-root/static/route-group-config-and-segment-config/(group)'
)
expect(extractBuildValidationError(result.cliOutput))
.toMatchInlineSnapshot(`
"Error: Route "/suspense-in-root/static/route-group-config-and-segment-config": Next.js encountered runtime data during the initial render.
\`cookies()\`, \`headers()\`, \`params\`, or \`searchParams\` accessed outside of \`<Suspense>\` blocks navigation, leading to a slower user experience.
Ways to fix this:
- Move the data access into a child component within a <Suspense> boundary
- Use \`generateStaticParams\` to make route params static
- Set \`export const instant = false\` to allow a blocking route
Learn more: https://nextjs.org/docs/messages/blocking-route
at div (<anonymous>)
at div (<anonymous>)
at body (<anonymous>)
at html (<anonymous>)
at a (<anonymous>)
Build-time instant validation failed for route "/suspense-in-root/static/route-group-config-and-segment-config".
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 "/suspense-in-root/static/route-group-config-and-segment-config" in your browser to investigate the error.
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
Stopping prerender due to instant validation errors."
`)
expect(result.exitCode).toBe(1)
}
})
it('invalid - config on segment layout - cookies() blocks through route group below', async () => {
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/static/route-group-segment-config-only'
)
await expect(browser).toDisplayCollapsedRedbox(`
{
"cause": [
{
"label": "Caused by: Instant Validation",
"source": "app/suspense-in-root/static/route-group-segment-config-only/layout.tsx (3:33) @ unstable_instant
> 3 | export const unstable_instant = true
| ^",
"stack": [
"unstable_instant app/suspense-in-root/static/route-group-segment-config-only/layout.tsx (3:33)",
"Set.forEach <anonymous>",
],
},
],
"code": "E1166",
"description": "Next.js encountered runtime data during the initial render.",
"environmentLabel": "Server",
"label": "Instant",
"source": "app/suspense-in-root/static/route-group-segment-config-only/(group)/page.tsx (4:16) @ Page
> 4 | await cookies()
| ^",
"stack": [
"Page app/suspense-in-root/static/route-group-segment-config-only/(group)/page.tsx (4:16)",
],
}
`)
} else {
const result = await prerender(
'/suspense-in-root/static/route-group-segment-config-only/(group)'
)
expect(extractBuildValidationError(result.cliOutput))
.toMatchInlineSnapshot(`
"Error: Route "/suspense-in-root/static/route-group-segment-config-only": Next.js encountered runtime data during the initial render.
\`cookies()\`, \`headers()\`, \`params\`, or \`searchParams\` accessed outside of \`<Suspense>\` blocks navigation, leading to a slower user experience.
Ways to fix this:
- Move the data access into a child component within a <Suspense> boundary
- Use \`generateStaticParams\` to make route params static
- Set \`export const instant = false\` to allow a blocking route
Learn more: https://nextjs.org/docs/messages/blocking-route
at div (<anonymous>)
at div (<anonymous>)
at body (<anonymous>)
at html (<anonymous>)
at a (<anonymous>)
Build-time instant validation failed for route "/suspense-in-root/static/route-group-segment-config-only".
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 "/suspense-in-root/static/route-group-segment-config-only" in your browser to investigate the error.
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
Stopping prerender due to instant validation errors."
`)
expect(result.exitCode).toBe(1)
}
})
it('invalid - config on route group layout - cookies() blocks in deeper segment', async () => {
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/static/route-group-config-with-deeper-segment/inner'
)
await expect(browser).toDisplayCollapsedRedbox(`
{
"cause": [
{
"label": "Caused by: Instant Validation",
"source": "app/suspense-in-root/static/route-group-config-with-deeper-segment/(group)/layout.tsx (3:33) @ unstable_instant
> 3 | export const unstable_instant = true
| ^",
"stack": [
"unstable_instant app/suspense-in-root/static/route-group-config-with-deeper-segment/(group)/layout.tsx (3:33)",
"Set.forEach <anonymous>",
],
},
],
"code": "E1166",
"description": "Next.js encountered runtime data during the initial render.",
"environmentLabel": "Server",
"label": "Instant",
"source": "app/suspense-in-root/static/route-group-config-with-deeper-segment/(group)/inner/page.tsx (4:16) @ Page
> 4 | await cookies()
| ^",
"stack": [
"Page app/suspense-in-root/static/route-group-config-with-deeper-segment/(group)/inner/page.tsx (4:16)",
],
}
`)
} else {
const result = await prerender(
'/suspense-in-root/static/route-group-config-with-deeper-segment/(group)/inner'
)
expect(extractBuildValidationError(result.cliOutput))
.toMatchInlineSnapshot(`
"Error: Route "/suspense-in-root/static/route-group-config-with-deeper-segment/inner": Next.js encountered runtime data during the initial render.
\`cookies()\`, \`headers()\`, \`params\`, or \`searchParams\` accessed outside of \`<Suspense>\` blocks navigation, leading to a slower user experience.
Ways to fix this:
- Move the data access into a child component within a <Suspense> boundary
- Use \`generateStaticParams\` to make route params static
- Set \`export const instant = false\` to allow a blocking route
Learn more: https://nextjs.org/docs/messages/blocking-route
at div (<anonymous>)
at div (<anonymous>)
at body (<anonymous>)
at html (<anonymous>)
at a (<anonymous>)
Build-time instant validation failed for route "/suspense-in-root/static/route-group-config-with-deeper-segment/inner".
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 "/suspense-in-root/static/route-group-config-with-deeper-segment/inner" in your browser to investigate the error.
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
Stopping prerender due to instant validation errors."
`)
expect(result.exitCode).toBe(1)
}
})
it('invalid - config on segment layout inside route group - cookies() blocks below', async () => {
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/static/route-group-deeper-segment-config/inner'
)
await expect(browser).toDisplayCollapsedRedbox(`
{
"cause": [
{
"label": "Caused by: Instant Validation",
"source": "app/suspense-in-root/static/route-group-deeper-segment-config/(group)/inner/layout.tsx (3:33) @ unstable_instant
> 3 | export const unstable_instant = true
| ^",
"stack": [
"unstable_instant app/suspense-in-root/static/route-group-deeper-segment-config/(group)/inner/layout.tsx (3:33)",
"Set.forEach <anonymous>",
],
},
],
"code": "E1166",
"description": "Next.js encountered runtime data during the initial render.",
"environmentLabel": "Server",
"label": "Instant",
"source": "app/suspense-in-root/static/route-group-deeper-segment-config/(group)/inner/page.tsx (4:16) @ Page
> 4 | await cookies()
| ^",
"stack": [
"Page app/suspense-in-root/static/route-group-deeper-segment-config/(group)/inner/page.tsx (4:16)",
],
}
`)
} else {
const result = await prerender(
'/suspense-in-root/static/route-group-deeper-segment-config/(group)/inner'
)
expect(extractBuildValidationError(result.cliOutput))
.toMatchInlineSnapshot(`
"Error: Route "/suspense-in-root/static/route-group-deeper-segment-config/inner": Next.js encountered runtime data during the initial render.
\`cookies()\`, \`headers()\`, \`params\`, or \`searchParams\` accessed outside of \`<Suspense>\` blocks navigation, leading to a slower user experience.
Ways to fix this:
- Move the data access into a child component within a <Suspense> boundary
- Use \`generateStaticParams\` to make route params static
- Set \`export const instant = false\` to allow a blocking route
Learn more: https://nextjs.org/docs/messages/blocking-route
at div (<anonymous>)
at div (<anonymous>)
at body (<anonymous>)
at html (<anonymous>)
at a (<anonymous>)
Build-time instant validation failed for route "/suspense-in-root/static/route-group-deeper-segment-config/inner".
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 "/suspense-in-root/static/route-group-deeper-segment-config/inner" in your browser to investigate the error.
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
Stopping prerender due to instant validation errors."
`)
expect(result.exitCode).toBe(1)
}
})
})
describe('route group shared boundary', () => {
// When navigating from /foo to /, (outer)/layout is shared — its
// Suspense doesn't apply to the new tree. (inner)/layout awaits
// cookies() without its own Suspense, so the navigation should
// block and produce a validation error. The group depth iteration
// catches this by treating (outer) as shared and (inner) as new.
it('invalid - blocking layout inside shared route group boundary', async () => {
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/static/route-group-shared-boundary'
)
await expect(browser).toDisplayCollapsedRedbox(`
{
"cause": [
{
"label": "Caused by: Instant Validation",
"source": "app/suspense-in-root/static/route-group-shared-boundary/(outer)/(inner)/page.tsx (6:33) @ unstable_instant
> 6 | export const unstable_instant = true
| ^",
"stack": [
"unstable_instant app/suspense-in-root/static/route-group-shared-boundary/(outer)/(inner)/page.tsx (6:33)",
"Set.forEach <anonymous>",
],
},
],
"code": "E1166",
"description": "Next.js encountered runtime data during the initial render.",
"environmentLabel": "Server",
"label": "Instant",
"source": "app/suspense-in-root/static/route-group-shared-boundary/(outer)/(inner)/layout.tsx (13:16) @ InnerLayout
> 13 | await cookies()
| ^",
"stack": [
"InnerLayout app/suspense-in-root/static/route-group-shared-boundary/(outer)/(inner)/layout.tsx (13:16)",
],
}
`)
} else {
const result = await prerender(
'/suspense-in-root/static/route-group-shared-boundary/(outer)/(inner)'
)
expect(extractBuildValidationError(result.cliOutput))
.toMatchInlineSnapshot(`
"Error: Route "/suspense-in-root/static/route-group-shared-boundary": Next.js encountered runtime data during the initial render.
\`cookies()\`, \`headers()\`, \`params\`, or \`searchParams\` accessed outside of \`<Suspense>\` blocks navigation, leading to a slower user experience.
Ways to fix this:
- Move the data access into a child component within a <Suspense> boundary
- Use \`generateStaticParams\` to make route params static
- Set \`export const instant = false\` to allow a blocking route
Learn more: https://nextjs.org/docs/messages/blocking-route
at a (<anonymous>)
at div (<anonymous>)
at body (<anonymous>)
at html (<anonymous>)
at b (<anonymous>)
Build-time instant validation failed for route "/suspense-in-root/static/route-group-shared-boundary".
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 "/suspense-in-root/static/route-group-shared-boundary" in your browser to investigate the error.
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
Stopping prerender due to instant validation errors."
`)
expect(result.exitCode).toBe(1)
}
})
})
describe('parallel slots with different group depths', () => {
// @slot has 3 groups, children has 2 groups. The validation
// iterates from deepest group depth (3) down to 0. Deeper
// holes in one slot are detected before shallower holes in
// another slot because the shallower slot stays entirely
// shared at higher group depths.
it('invalid - deep hole in @slot detected before shallow hole in children', async () => {
// @slot/(g1)/(g2)/(g3)/layout.tsx has cookies() — the 3rd group blocks.
// (b1)/(b2)/layout.tsx has cookies() — the 2nd group blocks.
// At groupDepth=2: @slot's g2 is boundary, g3 enters new tree →
// g3's cookies() detected at Static stage. children only has
// 2 groups which is < groupDepth=2, so children stays entirely
// shared. Only @slot's error is reported.
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/static/parallel-group-depths-deep-slot-hole'
)
await expect(browser).toDisplayCollapsedRedbox(`
{
"cause": [
{
"label": "Caused by: Instant Validation",
"source": "app/suspense-in-root/static/parallel-group-depths-deep-slot-hole/@slot/(g1)/(g2)/(g3)/page.tsx (1:33) @ unstable_instant
> 1 | export const unstable_instant = true
| ^",
"stack": [
"unstable_instant app/suspense-in-root/static/parallel-group-depths-deep-slot-hole/@slot/(g1)/(g2)/(g3)/page.tsx (1:33)",
"Set.forEach <anonymous>",
],
},
],
"code": "E1166",
"description": "Next.js encountered runtime data during the initial render.",
"environmentLabel": "Server",
"label": "Instant",
"source": "app/suspense-in-root/static/parallel-group-depths-deep-slot-hole/@slot/(g1)/(g2)/(g3)/layout.tsx (7:16) @ G3Layout
> 7 | await cookies()
| ^",
"stack": [
"G3Layout app/suspense-in-root/static/parallel-group-depths-deep-slot-hole/@slot/(g1)/(g2)/(g3)/layout.tsx (7:16)",
],
}
`)
} else {
const result = await prerender(
'/suspense-in-root/static/parallel-group-depths-deep-slot-hole/(b1)/(b2)'
)
expect(extractBuildValidationError(result.cliOutput))
.toMatchInlineSnapshot(`
"Error: Route "/suspense-in-root/static/parallel-group-depths-deep-slot-hole": Next.js encountered runtime data during the initial render.
\`cookies()\`, \`headers()\`, \`params\`, or \`searchParams\` accessed outside of \`<Suspense>\` blocks navigation, leading to a slower user experience.
Ways to fix this:
- Move the data access into a child component within a <Suspense> boundary
- Use \`generateStaticParams\` to make route params static
- Set \`export const instant = false\` to allow a blocking route
Learn more: https://nextjs.org/docs/messages/blocking-route
at div (<anonymous>)
at div (<anonymous>)
at body (<anonymous>)
at html (<anonymous>)
at a (<anonymous>)
Build-time instant validation failed for route "/suspense-in-root/static/parallel-group-depths-deep-slot-hole".
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 "/suspense-in-root/static/parallel-group-depths-deep-slot-hole" in your browser to investigate the error.
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
Stopping prerender due to instant validation errors."
`)
expect(result.exitCode).toBe(1)
}
})
it('invalid - children hole detected before shallow @slot hole', async () => {
// @slot/(g1)/layout.tsx has cookies() — the 1st group blocks.
// (b1)/(b2)/layout.tsx has cookies() — the 2nd group blocks.
// At groupDepth=1: @slot's g1 is boundary (shared, cookies()
// runs at Dynamic stage — not detected). children's b1 is
// boundary, b2 enters new tree → b2's cookies() detected.
// The "caused by" config source differs between bundlers due
// to parallel route key iteration order when slot markers
// aren't supported in webpack.
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/static/parallel-group-depths-shallow-slot-hole'
)
await expect(browser).toDisplayCollapsedRedbox(`
{
"cause": [
{
"label": "Caused by: Instant Validation",
"source": "app/suspense-in-root/static/parallel-group-depths-shallow-slot-hole/(b1)/(b2)/page.tsx (1:33) @ unstable_instant
> 1 | export const unstable_instant = true
| ^",
"stack": [
"unstable_instant app/suspense-in-root/static/parallel-group-depths-shallow-slot-hole/(b1)/(b2)/page.tsx (1:33)",
"Set.forEach <anonymous>",
],
},
],
"code": "E1166",
"description": "Next.js encountered runtime data during the initial render.",
"environmentLabel": "Server",
"label": "Instant",
"source": "app/suspense-in-root/static/parallel-group-depths-shallow-slot-hole/(b1)/(b2)/layout.tsx (5:16) @ B2Layout
> 5 | await cookies()
| ^",
"stack": [
"B2Layout app/suspense-in-root/static/parallel-group-depths-shallow-slot-hole/(b1)/(b2)/layout.tsx (5:16)",
],
}
`)
} else {
const result = await prerender(
'/suspense-in-root/static/parallel-group-depths-shallow-slot-hole/(b1)/(b2)'
)
expect(extractBuildValidationError(result.cliOutput))
.toMatchInlineSnapshot(`
"Error: Route "/suspense-in-root/static/parallel-group-depths-shallow-slot-hole": Next.js encountered runtime data during the initial render.
\`cookies()\`, \`headers()\`, \`params\`, or \`searchParams\` accessed outside of \`<Suspense>\` blocks navigation, leading to a slower user experience.
Ways to fix this:
- Move the data access into a child component within a <Suspense> boundary
- Use \`generateStaticParams\` to make route params static
- Set \`export const instant = false\` to allow a blocking route
Learn more: https://nextjs.org/docs/messages/blocking-route
at div (<anonymous>)
at body (<anonymous>)
at html (<anonymous>)
at a (<anonymous>)
Build-time instant validation failed for route "/suspense-in-root/static/parallel-group-depths-shallow-slot-hole".
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 "/suspense-in-root/static/parallel-group-depths-shallow-slot-hole" in your browser to investigate the error.
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
Stopping prerender due to instant validation errors."
`)
expect(result.exitCode).toBe(1)
}
})
})
// TODO(instant-validation): The error message for this case is
// technically correct but confusing. The developer configured
// runtime prefetching on the inner layout, so they expect
// cookies() to be fine. But the parent layout above the config
// gets static prefetching by default, making cookies() a
// blocking violation. The error should explain that segments
// above the config use static prefetching and suggest either
// moving the config up or adding Suspense around the runtime
// data in the parent layout.
it('invalid - static layout above runtime config blocks navigation', async () => {
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/runtime/static-layout-above-runtime-config/inner'
)
await expect(browser).toDisplayCollapsedRedbox(`
{
"cause": [
{
"label": "Caused by: Instant Validation",
"source": "app/suspense-in-root/runtime/static-layout-above-runtime-config/inner/layout.tsx (6:33) @ unstable_instant
> 6 | export const unstable_instant = true
| ^",
"stack": [
"unstable_instant app/suspense-in-root/runtime/static-layout-above-runtime-config/inner/layout.tsx (6:33)",
"Set.forEach <anonymous>",
],
},
],
"code": "E1166",
"description": "Next.js encountered runtime data during the initial render.",
"environmentLabel": "Server",
"label": "Instant",
"source": "app/suspense-in-root/runtime/static-layout-above-runtime-config/layout.tsx (15:16) @ StaticLayout
> 15 | await cookies()
| ^",
"stack": [
"StaticLayout app/suspense-in-root/runtime/static-layout-above-runtime-config/layout.tsx (15:16)",
],
}
`)
} else {
const result = await prerender(
'/suspense-in-root/runtime/static-layout-above-runtime-config/inner'
)
expect(extractBuildValidationError(result.cliOutput))
.toMatchInlineSnapshot(`
"Error: Route "/suspense-in-root/runtime/static-layout-above-runtime-config/inner": Next.js encountered runtime data during the initial render.
\`cookies()\`, \`headers()\`, \`params\`, or \`searchParams\` accessed outside of \`<Suspense>\` blocks navigation, leading to a slower user experience.
Ways to fix this:
- Move the data access into a child component within a <Suspense> boundary
- Use \`generateStaticParams\` to make route params static
- Set \`export const instant = false\` to allow a blocking route
Learn more: https://nextjs.org/docs/messages/blocking-route
at body (<anonymous>)
at html (<anonymous>)
at a (<anonymous>)
Build-time instant validation failed for route "/suspense-in-root/runtime/static-layout-above-runtime-config/inner".
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 "/suspense-in-root/runtime/static-layout-above-runtime-config/inner" in your browser to investigate the error.
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
Stopping prerender due to instant validation errors."
`)
expect(result.exitCode).toBe(1)
}
})
describe('config depth preference', () => {
// When multiple slots have instant configs at different depths,
// the deepest config is preferred as the root cause. At equal
// depth, children is preferred over named slots.
it('invalid - deeper children config preferred over shallower slot config', async () => {
// children has config deep (deeper/still/deep/page.tsx, depth 2)
// @anotherSlot has config shallow (page.tsx, depth 0)
// @slot blocks with no config — cause should be children's deep config
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/static/config-depth-preference/deeper/still/deep'
)
await expect(browser).toDisplayCollapsedRedbox(`
{
"cause": [
{
"label": "Caused by: Instant Validation",
"source": "app/suspense-in-root/static/config-depth-preference/deeper/still/deep/page.tsx (3:33) @ unstable_instant
> 3 | export const unstable_instant = true
| ^",
"stack": [
"unstable_instant app/suspense-in-root/static/config-depth-preference/deeper/still/deep/page.tsx (3:33)",
"Set.forEach <anonymous>",
],
},
],
"code": "E1166",
"description": "Next.js encountered runtime data during the initial render.",
"environmentLabel": "Server",
"label": "Instant",
"source": "app/suspense-in-root/static/config-depth-preference/@slot/[...catchall]/page.tsx (8:16) @ CatchallSlotPage
> 8 | await cookies()
| ^",
"stack": [
"CatchallSlotPage app/suspense-in-root/static/config-depth-preference/@slot/[...catchall]/page.tsx (8:16)",
],
}
`)
} else {
const result = await prerender(
'/suspense-in-root/static/config-depth-preference/deeper/still/deep'
)
expect(extractBuildValidationError(result.cliOutput))
.toMatchInlineSnapshot(`
"Error [InvariantError]: Invariant: An unexpected error occurred during instant validation. This is a bug in Next.js.
at ignore-listed frames {
[cause]: Error [InvariantError]: Invariant: Missing value for segment key: "catchall" with dynamic param type: c. This is a bug in Next.js.
at ignore-listed frames
}
Stopping prerender due to instant validation errors."
`)
expect(result.exitCode).toBe(1)
}
})
it('invalid - deeper slot config preferred over shallower children catchall', async () => {
// @anotherSlot has config deep (still/deep/page.tsx, depth 2)
// children has config shallow ([...rest]/page.tsx, depth 1)
// @slot blocks with no config — cause should be @anotherSlot's deep config
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/static/config-depth-preference-slot-wins/deeper/still/deep'
)
await expect(browser).toDisplayCollapsedRedbox(`
{
"cause": [
{
"label": "Caused by: Instant Validation",
"source": "app/suspense-in-root/static/config-depth-preference-slot-wins/deeper/@anotherSlot/still/deep/page.tsx (3:33) @ unstable_instant
> 3 | export const unstable_instant = true
| ^",
"stack": [
"unstable_instant app/suspense-in-root/static/config-depth-preference-slot-wins/deeper/@anotherSlot/still/deep/page.tsx (3:33)",
"Set.forEach <anonymous>",
],
},
],
"code": "E1166",
"description": "Next.js encountered runtime data during the initial render.",
"environmentLabel": "Server",
"label": "Instant",
"source": "app/suspense-in-root/static/config-depth-preference-slot-wins/@slot/[...catchall]/page.tsx (7:16) @ CatchallSlotPage
> 7 | await cookies()
| ^",
"stack": [
"CatchallSlotPage app/suspense-in-root/static/config-depth-preference-slot-wins/@slot/[...catchall]/page.tsx (7:16)",
],
}
`)
} else {
const result = await prerender(
'/suspense-in-root/static/config-depth-preference-slot-wins/deeper/[...rest]'
)
expect(extractBuildValidationError(result.cliOutput))
.toMatchInlineSnapshot(`
"Error: Route "/suspense-in-root/static/config-depth-preference-slot-wins/deeper/[...rest]": Next.js encountered runtime data during the initial render.
\`cookies()\`, \`headers()\`, \`params\`, or \`searchParams\` accessed outside of \`<Suspense>\` blocks navigation, leading to a slower user experience.
Ways to fix this:
- Move the data access into a child component within a <Suspense> boundary
- Use \`generateStaticParams\` to make route params static
- Set \`export const instant = false\` to allow a blocking route
Learn more: https://nextjs.org/docs/messages/blocking-route
at div (<anonymous>)
at div (<anonymous>)
at body (<anonymous>)
at html (<anonymous>)
at a (<anonymous>)
Build-time instant validation failed for route "/suspense-in-root/static/config-depth-preference-slot-wins/deeper/[...rest]".
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 "/suspense-in-root/static/config-depth-preference-slot-wins/deeper/[...rest]" in your browser to investigate the error.
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
Stopping prerender due to instant validation errors."
`)
expect(result.exitCode).toBe(1)
}
})
it('invalid - children config preferred at equal depth', async () => {
// children and @other both have config at same depth (page level)
// @slot blocks with no config — cause should be children's config
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/static/config-children-preferred'
)
await expect(browser).toDisplayCollapsedRedbox(`
{
"cause": [
{
"label": "Caused by: Instant Validation",
"source": "app/suspense-in-root/static/config-children-preferred/page.tsx (4:33) @ unstable_instant
> 4 | export const unstable_instant = true
| ^",
"stack": [
"unstable_instant app/suspense-in-root/static/config-children-preferred/page.tsx (4:33)",
"Set.forEach <anonymous>",
],
},
],
"code": "E1166",
"description": "Next.js encountered runtime data during the initial render.",
"environmentLabel": "Server",
"label": "Instant",
"source": "app/suspense-in-root/static/config-children-preferred/@slot/page.tsx (7:16) @ SlotPage
> 7 | await cookies()
| ^",
"stack": [
"SlotPage app/suspense-in-root/static/config-children-preferred/@slot/page.tsx (7:16)",
],
}
`)
} else {
const result = await prerender(
'/suspense-in-root/static/config-children-preferred'
)
expect(extractBuildValidationError(result.cliOutput))
.toMatchInlineSnapshot(`
"Error: Route "/suspense-in-root/static/config-children-preferred": Next.js encountered runtime data during the initial render.
\`cookies()\`, \`headers()\`, \`params\`, or \`searchParams\` accessed outside of \`<Suspense>\` blocks navigation, leading to a slower user experience.
Ways to fix this:
- Move the data access into a child component within a <Suspense> boundary
- Use \`generateStaticParams\` to make route params static
- Set \`export const instant = false\` to allow a blocking route
Learn more: https://nextjs.org/docs/messages/blocking-route
at div (<anonymous>)
at div (<anonymous>)
at body (<anonymous>)
at html (<anonymous>)
at a (<anonymous>)
Build-time instant validation failed for route "/suspense-in-root/static/config-children-preferred".
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 "/suspense-in-root/static/config-children-preferred" in your browser to investigate the error.
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
Stopping prerender due to instant validation errors."
`)
expect(result.exitCode).toBe(1)
}
})
it('invalid - cross-slot blocking falls back to deep children config', async () => {
// @slot catchall blocks with no config
// children has config deep behind a second fork with @panel
// cause should fall back to children's deep config
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/static/cross-slot-blocking/inner/deep'
)
await expect(browser).toDisplayCollapsedRedbox(`
{
"cause": [
{
"label": "Caused by: Instant Validation",
"source": "app/suspense-in-root/static/cross-slot-blocking/inner/deep/page.tsx (5:33) @ unstable_instant
> 5 | export const unstable_instant = true
| ^",
"stack": [
"unstable_instant app/suspense-in-root/static/cross-slot-blocking/inner/deep/page.tsx (5:33)",
"Set.forEach <anonymous>",
],
},
],
"code": "E1166",
"description": "Next.js encountered runtime data during the initial render.",
"environmentLabel": "Server",
"label": "Instant",
"source": "app/suspense-in-root/static/cross-slot-blocking/@slot/[...catchall]/page.tsx (8:16) @ CatchallSlotPage
> 8 | await cookies()
| ^",
"stack": [
"CatchallSlotPage app/suspense-in-root/static/cross-slot-blocking/@slot/[...catchall]/page.tsx (8:16)",
],
}
`)
} else {
const result = await prerender(
'/suspense-in-root/static/cross-slot-blocking/inner/deep'
)
expect(extractBuildValidationError(result.cliOutput))
.toMatchInlineSnapshot(`
"Error [InvariantError]: Invariant: An unexpected error occurred during instant validation. This is a bug in Next.js.
at ignore-listed frames {
[cause]: Error [InvariantError]: Invariant: Missing value for segment key: "catchall" with dynamic param type: c. This is a bug in Next.js.
at ignore-listed frames
}
Stopping prerender due to instant validation errors."
`)
expect(result.exitCode).toBe(1)
}
})
})
describe('disabling validation', () => {
it('in a layout', async () => {
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/disable-validation/in-layout'
)
await expectNoDevValidationErrors(browser, await browser.url())
} else {
const result = await prerender(
'/suspense-in-root/disable-validation/in-layout'
)
expectBuildValidationSkipped(result)
}
})
it('in a page', async () => {
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/disable-validation/in-page'
)
await expectNoDevValidationErrors(browser, await browser.url())
} else {
const result = await prerender(
'/suspense-in-root/disable-validation/in-page'
)
expectBuildValidationSkipped(result)
}
})
it('in a page with a parent that has a config', async () => {
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/disable-validation/in-page-with-outer'
)
await expectNoDevValidationErrors(browser, await browser.url())
} else {
const result = await prerender(
'/suspense-in-root/disable-validation/in-page-with-outer'
)
expectBuildValidationSkipped(result)
}
})
it('disabling dev validation', async () => {
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/disable-validation/disable-dev'
)
await expectNoDevValidationErrors(browser, await browser.url())
} else {
const result = await prerender(
'/suspense-in-root/disable-validation/disable-dev'
)
expect(extractBuildValidationError(result.cliOutput))
.toMatchInlineSnapshot(`
"Error: Route "/suspense-in-root/disable-validation/disable-dev": Next.js encountered uncached data during the initial render.
\`fetch(...)\` or \`connection()\` accessed outside of \`<Suspense>\` blocks navigation, leading to a slower user experience.
Ways to fix this:
- Cache the data access with \`"use cache"\`
- Move the data access into a child component within a <Suspense> boundary
- Set \`export const instant = false\` to allow a blocking route
Learn more: https://nextjs.org/docs/messages/blocking-route
at body (<anonymous>)
at html (<anonymous>)
at a (<anonymous>)
Build-time instant validation failed for route "/suspense-in-root/disable-validation/disable-dev".
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 "/suspense-in-root/disable-validation/disable-dev" in your browser to investigate the error.
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
Stopping prerender due to instant validation errors."
`)
expect(result.exitCode).toBe(1)
}
})
it('disabling build validation', async () => {
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/disable-validation/disable-build'
)
await expect(browser).toDisplayCollapsedRedbox(`
{
"cause": [
{
"label": "Caused by: Instant Validation",
"source": "app/suspense-in-root/disable-validation/disable-build/page.tsx (3:33) @ unstable_instant
> 3 | export const unstable_instant = { unstable_disableBuildValidation: true }
| ^",
"stack": [
"unstable_instant app/suspense-in-root/disable-validation/disable-build/page.tsx (3:33)",
"Set.forEach <anonymous>",
],
},
],
"code": "E1164",
"description": "Next.js encountered uncached data during the initial render.",
"environmentLabel": "Server",
"label": "Instant",
"source": "app/suspense-in-root/disable-validation/disable-build/page.tsx (6:19) @ Page
> 6 | await connection()
| ^",
"stack": [
"Page app/suspense-in-root/disable-validation/disable-build/page.tsx (6:19)",
],
}
`)
} else {
const result = await prerender(
'/suspense-in-root/disable-validation/disable-build'
)
expectBuildValidationSkipped(result)
}
})
})
})
})