[ci] Split up large instant-validation tests (#95627)

See https://github.com/vercel/next.js/pull/95553 for an explanation of
why we want to split these up. TL;DR it makes CI faster and saves us
some money.

In CI we have to run tests at the suite/file granularity, so if a test
suite is too long it limits parallelism and if it has to be retried, we
have to retry the full suite.
This commit is contained in:
Benjamin Woodruff
2026-07-08 19:56:28 -07:00
committed by GitHub
parent a87692e17c
commit 9286844641
20 changed files with 4767 additions and 4579 deletions
@@ -1,2 +1,2 @@
process.env.__NEXT_PARTIAL_PREFETCHING = 'true'
require('./instant-validation.test')
require('./client.test')
@@ -0,0 +1,6 @@
import { runInstantValidationTests } from './harness.util'
import { registerClientTests } from './client.util'
runInstantValidationTests((ctx) => {
registerClientTests(ctx)
})
@@ -0,0 +1,615 @@
import {
expectNoBuildValidationErrors,
extractBuildValidationError,
waitForValidation,
} from 'e2e-utils/instant-validation'
import {
openRedbox,
waitForNoErrorToast,
waitForRedbox,
} from '../../../lib/next-test-utils'
import {
createRedboxSnapshot,
type ErrorSnapshot,
type RedboxSnapshot,
} from '../../../lib/add-redbox-matchers'
import {
NO_VALIDATION_ERRORS_WAIT,
type InstantValidationCaseContext,
} from './harness.util'
export function registerClientTests(ctx: InstantValidationCaseContext) {
const {
next,
isNextDev,
isClientNav,
navigateTo,
expectNoDevValidationErrors,
getCliOutputSinceMark,
prerender,
} = ctx
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:24) @ instant
> 1 | export const instant = { level: 'experimental-error' }
| ^",
"stack": [
"instant app/suspense-in-root/static/invalid-client-data-blocks-validation/page.tsx (1:24)",
"Set.forEach <anonymous>",
],
},
],
"code": "E1331",
"description": "Route "/suspense-in-root/static/invalid-client-data-blocks-validation": Could not validate \`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 (21:11)",
],
}
`)
} 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 \`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)
}
})
it('valid - unguarded params in client page', async () => {
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/static/valid-client-params/123'
)
await expectNoDevValidationErrors(browser, await browser.url())
} else {
const result = await prerender(
'/suspense-in-root/static/valid-client-params/[slug]'
)
expectNoBuildValidationErrors(result)
}
})
it('valid - unguarded searchParams in client page', async () => {
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/static/valid-client-search-params?query=foo'
)
await expectNoDevValidationErrors(browser, await browser.url())
} else {
const result = await prerender(
'/suspense-in-root/static/valid-client-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 \`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:24) @ instant
> 1 | export const instant = { level: 'experimental-error' }
| ^",
"stack": [
"instant app/suspense-in-root/static/invalid-client-error-in-parent-blocks-children/page.tsx (1:24)",
],
},
{
"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 \`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 \`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:24) @ instant
> 1 | export const instant = { level: 'experimental-error' }
| ^",
"stack": [
"instant app/suspense-in-root/static/invalid-error-in-node-modules-blocks-children/page.tsx (1:24)",
],
},
{
"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 \`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 \`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:24) @ instant
> 1 | export const instant = { level: 'experimental-error' }
| ^",
"stack": [
"instant app/suspense-in-root/static/invalid-csr-bailout-blocks-children/page.tsx (1:24)",
],
},
{
"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 \`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 \`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:24) @ instant
> 1 | export const instant = { level: 'experimental-error' }
| ^",
"stack": [
"instant app/suspense-in-root/static/invalid-client-error-in-parent-sibling/page.tsx (1:24)",
],
},
{
"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 \`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)
}
})
})
}
@@ -0,0 +1,196 @@
import { nextTestSetup, type NextInstance, type Playwright } from 'e2e-utils'
import { waitForValidation } from 'e2e-utils/instant-validation'
import { retry, waitForNoErrorToast } from '../../../lib/next-test-utils'
export interface InstantValidationCaseContext {
next: NextInstance
isNextDev: boolean
isNextStart: boolean
isTurbopack: boolean
/** Whether this case navigates via soft navigation instead of initial load */
isClientNav: boolean
/**
* Navigate to a page either via initial load or soft navigation.
* For soft nav, navigates to the index page first, then clicks the link.
*/
navigateTo: (href: string) => Promise<Playwright>
expectNoDevValidationErrors: (
browser: Playwright,
url: string
) => Promise<void>
getCliOutputSinceMark: () => string
/** Prerender a single page with `--experimental-build-mode generate` */
prerender: (pathname: string) => ReturnType<NextInstance['build']>
}
export const NO_VALIDATION_ERRORS_WAIT: Parameters<
typeof waitForNoErrorToast
>[1] = {
waitInMs: 500,
}
// This suite is far too slow to run as a single CI test file, so it's split
// into one `*.test.ts` entry file per group of sections (each with a
// `.partial-prefetching` variant), all sharing this wrapper.
// Every entry boots its own server (and, in `next start` mode, runs its own
// `--experimental-build-mode compile` build plus a `generate` build per
// test). All entries use the same describe title so test full names stay
// stable across the split. Snapshots can be updated with the sibling
// update-instant-validation-snapshots.mjs script.
export function runInstantValidationTests(
registerTests: (ctx: InstantValidationCaseContext) => void
) {
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 })
}
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.
// We have multiple root layouts, so each needs a separate index page
// because navigating between root layouts would be an MPA nav,
// and we want to test soft navs.
const indexPage = ((): string => {
if (href.startsWith('/shells/')) {
// If this is the root params page, use the same root param value
let match: ReturnType<String['match']>
if (
(match = href.match(
/^\/shells\/with-root-param\/(?<lang>[^/]+)\/.*/
))
) {
const lang = match.groups!.lang
return `/shells/with-root-param/${lang}`
} else {
return '/shells'
}
}
for (const prefix of ['/default', '/suspense-in-root']) {
if (href.startsWith(prefix + '/')) {
return prefix
}
}
throw new Error(`Could not find index page for ${href}`)
})()
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)
},
// Webpack can be slow to compile new routes in CI, which blocks the
// navigation. Instant Validation itself is non-blocking and does not
// affect this, so this is only covering for compilation speed.
isTurbopack ? 5_000 : 10_000,
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
}
registerTests({
next,
isNextDev,
isNextStart,
isTurbopack,
isClientNav,
navigateTo,
expectNoDevValidationErrors,
getCliOutputSinceMark,
prerender,
})
})
})
}
@@ -0,0 +1,2 @@
process.env.__NEXT_PARTIAL_PREFETCHING = 'true'
require('./head-and-reporting.test')
@@ -0,0 +1,6 @@
import { runInstantValidationTests } from './harness.util'
import { registerHeadAndReportingTests } from './head-and-reporting.util'
runInstantValidationTests((ctx) => {
registerHeadAndReportingTests(ctx)
})
@@ -0,0 +1,933 @@
import {
expectNoBuildValidationErrors,
expectBuildValidationSkipped,
extractBuildValidationError,
getDevCliValidationOutput,
} from 'e2e-utils/instant-validation'
import { type InstantValidationCaseContext } from './harness.util'
const partialPrefetching = !!process.env.__NEXT_PARTIAL_PREFETCHING
export function registerHeadAndReportingTests(
ctx: InstantValidationCaseContext
) {
const {
isNextDev,
isClientNav,
navigateTo,
expectNoDevValidationErrors,
getCliOutputSinceMark,
prerender,
} = ctx
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 - shell prefetch - URL data in 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 (9:24) @ instant
> 9 | export const instant = { level: 'experimental-error' }
| ^",
"stack": [
"instant app/suspense-in-root/head/invalid-runtime-viewport-in-static/page.tsx (9:24)",
"Set.forEach <anonymous>",
],
},
],
"code": "E1409",
"description": "Next.js encountered URL data in generateViewport().",
"environmentLabel": "Server",
"label": "Blocking Route",
"source": "app/suspense-in-root/head/invalid-runtime-viewport-in-static/page.tsx (16:3) @ Module.generateViewport
> 16 | await searchParams
| ^",
"stack": [
"Module.generateViewport app/suspense-in-root/head/invalid-runtime-viewport-in-static/page.tsx (16:3)",
],
}
`)
} 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 URL data in \`generateViewport()\`.
\`params\` or \`searchParams\` in \`generateViewport()\` prevents the page from being prerendered, leading to a slower user experience.
Ways to fix this:
- [static] Use a static viewport export instead of \`generateViewport()\`
https://nextjs.org/docs/messages/blocking-prerender-viewport-runtime#use-static-viewport
- [block] Set \`export const instant = false\` to allow a blocking route
https://nextjs.org/docs/messages/blocking-prerender-viewport-runtime#allow-blocking-route
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:24) @ instant
> 6 | export const instant = { level: 'experimental-error' }
| ^",
"stack": [
"instant app/suspense-in-root/head/invalid-dynamic-viewport-in-runtime/page.tsx (6:24)",
"Set.forEach <anonymous>",
],
},
],
"code": "E1395",
"description": "Next.js encountered uncached data in generateViewport().",
"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 in \`generateViewport()\`.
\`fetch(...)\` or \`connection()\` in \`generateViewport()\` prevents the page from being prerendered, leading to a slower user experience.
Ways to fix this:
- [cache] Cache the viewport data with \`"use cache"\` in \`generateViewport()\` (does not apply to \`connection()\`)
https://nextjs.org/docs/messages/blocking-prerender-viewport-dynamic#cache-the-viewport-data
- [block] Set \`export const instant = false\` to allow a blocking route
https://nextjs.org/docs/messages/blocking-prerender-viewport-dynamic#allow-blocking-route
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 the 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:24) @ instant
> 3 | export const instant = { level: 'experimental-error' }
| ^",
"stack": [
"instant app/suspense-in-root/head/invalid-dynamic-viewport-in-blocking-inside-static/layout.tsx (3:24)",
"Set.forEach <anonymous>",
],
},
],
"code": "E1395",
"description": "Next.js encountered uncached data in generateViewport().",
"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 in \`generateViewport()\`.
\`fetch(...)\` or \`connection()\` in \`generateViewport()\` prevents the page from being prerendered, leading to a slower user experience.
Ways to fix this:
- [cache] Cache the viewport data with \`"use cache"\` in \`generateViewport()\` (does not apply to \`connection()\`)
https://nextjs.org/docs/messages/blocking-prerender-viewport-dynamic#cache-the-viewport-data
- [block] Set \`export const instant = false\` to allow a blocking route
https://nextjs.org/docs/messages/blocking-prerender-viewport-dynamic#allow-blocking-route
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('multi-depth fallback deferral', () => {
// The validation outer loop iterates from deepest configured depth
// to shallowest. When the deepest iteration only produces a missing-
// boundary fallback (i.e., the configured boundary didn't render and
// there were no thrown errors), that fallback should be deferred so
// a real error from a shallower depth can win. If no shallower depth
// surfaces a real error, the deferred fallback eventually surfaces
// so the user is still made aware that validation didn't complete.
it('surfaces deferred fallback when no shallower depth has a real error', async () => {
// Outer layout has instant and validates cleanly. Inner
// page has instant but its parent layout drops {children},
// so the inner boundary can't render. Without the deferral, we'd
// bail out after the deepest iteration; with deferral, the outer
// iteration runs cleanly and the deferred fallback then surfaces.
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/static/multi-depth-deferred-fallback/inner'
)
await expect(browser).toDisplayCollapsedRedbox(`
{
"code": "E1286",
"description": "Next.js could not validate that a segment in your UI has instant navigation.",
"environmentLabel": "Server",
"label": "Instant",
"source": "/suspense-in-root/static/multi-depth-deferred-fallback/inner
│
│ ├─ suspense-in-root/
│ │ ├─ static/
│ │ │ ├─ multi-depth-deferred-fallback/
│ │ │ │ ├─ inner/
│ └─ page.tsx ← dropped from rendering
│",
"stack": [],
}
`)
} else {
const result = await prerender(
'/suspense-in-root/static/multi-depth-deferred-fallback/inner'
)
expect(extractBuildValidationError(result.cliOutput))
.toMatchInlineSnapshot(`
"Error: Route "/suspense-in-root/static/multi-depth-deferred-fallback/inner": Could not validate that a segment in your UI has instant navigation.
This segment was dropped from rendering. Issues that would prevent instant navigation will go undetected.
Dropped segment:
app/suspense-in-root/static/multi-depth-deferred-fallback/inner/page.tsx
Ways to fix this:
- [render] Render the dropped segment
https://nextjs.org/docs/messages/instant-unrendered-segment#render-the-dropped-segment
- [ignore] Set \`export const instant = false\` to opt the dropped segment out of instant-navigation validation
https://nextjs.org/docs/messages/instant-unrendered-segment#skip-validation-on-the-segment
at ignore-listed frames
Build-time instant validation failed for route "/suspense-in-root/static/multi-depth-deferred-fallback/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/multi-depth-deferred-fallback/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('unrendered segment file reporting', () => {
it('reports the shallowest unrendered file, not the configured file', async () => {
// Config is on inter/inner/page.tsx. The shallowest boundary
// iteration lands at test-firstmod, and inter/layout.tsx is the
// first child mod that didn't render — not the configured page,
// and not test-firstmod/layout.tsx (which DID render but dropped
// its children).
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/static/test-firstmod/inter/inner'
)
await expect(browser).toDisplayCollapsedRedbox(`
{
"code": "E1286",
"description": "Next.js could not validate that a segment in your UI has instant navigation.",
"environmentLabel": "Server",
"label": "Instant",
"source": "/suspense-in-root/static/test-firstmod/inter/inner
│
│ ├─ suspense-in-root/
│ │ ├─ static/
│ │ │ ├─ test-firstmod/
│ │ │ │ ├─ inter/
│ └─ layout.tsx ← dropped from rendering
│",
"stack": [],
}
`)
} else {
const result = await prerender(
'/suspense-in-root/static/test-firstmod/inter/inner'
)
expect(extractBuildValidationError(result.cliOutput))
.toMatchInlineSnapshot(`
"Error: Route "/suspense-in-root/static/test-firstmod/inter/inner": Could not validate that a segment in your UI has instant navigation.
This segment was dropped from rendering. Issues that would prevent instant navigation will go undetected.
Dropped segment:
app/suspense-in-root/static/test-firstmod/inter/layout.tsx
Ways to fix this:
- [render] Render the dropped segment
https://nextjs.org/docs/messages/instant-unrendered-segment#render-the-dropped-segment
- [ignore] Set \`export const instant = false\` to opt the dropped segment out of instant-navigation validation
https://nextjs.org/docs/messages/instant-unrendered-segment#skip-validation-on-the-segment
at ignore-listed frames
Build-time instant validation failed for route "/suspense-in-root/static/test-firstmod/inter/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/test-firstmod/inter/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('reports the boundary segment layout when multiple slots are dropped', async () => {
// Layout drops both {children} and {sidebar}. Both have
// configured pages, but only one boundary id is created (at
// the segment level, covering all slots). The reported file is
// the boundary segment's own layout — the nearest mod to the
// boundary placement.
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/static/test-multi-unrendered'
)
await expect(browser).toDisplayCollapsedRedbox(`
{
"code": "E1286",
"description": "Next.js could not validate that a segment in your UI has instant navigation.",
"environmentLabel": "Server",
"label": "Instant",
"source": "/suspense-in-root/static/test-multi-unrendered
│
│ ├─ suspense-in-root/
│ │ ├─ static/
│ │ │ ├─ test-multi-unrendered/
│ │ │ │ ├─ @sidebar/
│ │ │ │ │ └─ page.tsx ← dropped from rendering
│ └─ page.tsx ← dropped from rendering
│",
"stack": [],
}
`)
} else {
const result = await prerender(
'/suspense-in-root/static/test-multi-unrendered'
)
expect(extractBuildValidationError(result.cliOutput))
.toMatchInlineSnapshot(`
"Error: Route "/suspense-in-root/static/test-multi-unrendered": Could not validate that a segment in your UI has instant navigation.
This segment was dropped from rendering. Issues that would prevent instant navigation will go undetected.
Dropped segments:
app/suspense-in-root/static/test-multi-unrendered/@sidebar/page.tsx
app/suspense-in-root/static/test-multi-unrendered/page.tsx
Ways to fix this:
- [render] Render the dropped segment
https://nextjs.org/docs/messages/instant-unrendered-segment#render-the-dropped-segment
- [ignore] Set \`export const instant = false\` to opt the dropped segment out of instant-navigation validation
https://nextjs.org/docs/messages/instant-unrendered-segment#skip-validation-on-the-segment
at ignore-listed frames
Build-time instant validation failed for route "/suspense-in-root/static/test-multi-unrendered".
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/test-multi-unrendered" 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('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 prerendering or a navigation.
\`fetch(...)\` or \`connection()\` accessed outside of \`<Suspense>\` prevents the route from being prerendered or the navigation from being instant, leading to a slower user experience.
Ways to fix this:
- [stream] Provide a placeholder with \`<Suspense fallback={...}>\` around the data access
https://nextjs.org/docs/messages/blocking-prerender-dynamic#wrap-in-or-move-into-suspense
- [cache] Cache the data access with \`"use cache"\` (does not apply to \`connection()\`)
https://nextjs.org/docs/messages/blocking-prerender-dynamic#cache-the-component-or-data
- [block] Set \`export const instant = false\` to allow a blocking route
https://nextjs.org/docs/messages/blocking-prerender-dynamic#allow-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:24) @ instant
> 3 | export const instant = {
| ^",
"stack": [
"instant app/suspense-in-root/disable-validation/disable-build/page.tsx (3:24)",
"Set.forEach <anonymous>",
],
},
],
"code": "E1398",
"description": "Next.js encountered uncached data during a navigation.",
"environmentLabel": "Server",
"label": "Instant",
"source": "app/suspense-in-root/disable-validation/disable-build/page.tsx (9:19) @ Page
> 9 | await connection()
| ^",
"stack": [
"Page app/suspense-in-root/disable-validation/disable-build/page.tsx (9:19)",
],
}
`)
} else {
const result = await prerender(
'/suspense-in-root/disable-validation/disable-build'
)
expectBuildValidationSkipped(result)
}
})
})
if (partialPrefetching) {
describe('app shell validation', () => {
it('valid - session data is allowed in a shell', async () => {
if (isNextDev) {
const browser = await navigateTo('/shells/valid-session-only')
await expectNoDevValidationErrors(browser, await browser.url())
} else {
const result = await prerender('/shells/(default)/valid-session-only')
expectNoBuildValidationErrors(result)
}
})
it('valid - session data is allowed in a shell (with dynamic data)', async () => {
if (isNextDev) {
const browser = await navigateTo('/shells/valid-session-with-dynamic')
await expectNoDevValidationErrors(browser, await browser.url())
} else {
const result = await prerender(
'/shells/(default)/valid-session-with-dynamic'
)
expectNoBuildValidationErrors(result)
}
})
it('valid - static params guarded by suspense in a shell', async () => {
if (isNextDev) {
const browser = await navigateTo('/shells/valid-static-with-gsp/123')
await expectNoDevValidationErrors(browser, await browser.url())
} else {
const result = await prerender(
'/shells/(default)/valid-static-with-gsp/[slug]'
)
expectNoBuildValidationErrors(result)
}
})
it('invalid - unguarded params in a runtime-prefetchable shell', async () => {
if (isNextDev) {
const browser = await navigateTo('/shells/invalid-runtime-params/123')
await expect(browser).toDisplayCollapsedRedbox(`
{
"cause": [
{
"label": "Caused by: Instant Validation",
"source": "app/shells/(default)/invalid-runtime-params/[slug]/page.tsx (3:33) @ instant
> 3 | export const instant: Instant = {
| ^",
"stack": [
"instant app/shells/(default)/invalid-runtime-params/[slug]/page.tsx (3:33)",
"Set.forEach <anonymous>",
],
},
],
"code": "E1408",
"description": "Next.js encountered URL data outside of Suspense.",
"environmentLabel": "Server",
"label": "Instant",
"source": "app/shells/(default)/invalid-runtime-params/[slug]/page.tsx (28:3) @ LinkData
> 28 | await params
| ^",
"stack": [
"LinkData app/shells/(default)/invalid-runtime-params/[slug]/page.tsx (28:3)",
"Page app/shells/(default)/invalid-runtime-params/[slug]/page.tsx (22:7)",
],
}
`)
} else {
const result = await prerender(
'/shells/(default)/invalid-runtime-params/[slug]'
)
// TODO(app-shells): missing fallback params in build validation
// It seems like `workUnitStore.fallbackParams` is undefined
// during the validation render, which makes us treat these params as static.
// In partialPrefetching, static params are also delayed until the runtime stage,
// which ultimately makes the validation fail, but also hides the underlying issue.
expect(extractBuildValidationError(result.cliOutput))
.toMatchInlineSnapshot(`
"Error: Route "/shells/invalid-runtime-params/[slug]": Next.js encountered URL data during prerendering or a navigation.
\`params\` or \`searchParams\` accessed outside of \`<Suspense>\` may prevent the navigation from being instant, leading to a slower user experience.
Ways to fix this:
- [stream] Provide a placeholder with \`<Suspense fallback={...}>\` around the data access
https://nextjs.org/docs/messages/instant-shell-url-data#wrap-in-or-move-into-suspense
- [block] Set \`export const instant = false\` to allow a blocking route
https://nextjs.org/docs/messages/instant-shell-url-data#allow-blocking-route
at main (<anonymous>)
at body (<anonymous>)
at html (<anonymous>)
Build-time instant validation failed for route "/shells/invalid-runtime-params/[slug]".
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 "/shells/invalid-runtime-params/[slug]" 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 - unguarded search params in a runtime-prefetchable shell', async () => {
if (isNextDev) {
const browser = await navigateTo(
'/shells/invalid-runtime-searchparams?foo=bar'
)
if (isClientNav) {
// TODO(app-shells): redbox is flaky and sometimes doesn't appear even though validation runs.
// as a stopgap, we assert on CLI output instead
// await expect(browser).toDisplayCollapsedRedbox(`...`)
expect(
await getDevCliValidationOutput(
await browser.url(),
getCliOutputSinceMark
)
).toMatchInlineSnapshot(`
"Error: Route "/shells/invalid-runtime-searchparams": Next.js encountered URL data during prerendering or a navigation.
\`params\` or \`searchParams\` accessed outside of \`<Suspense>\` may prevent the navigation from being instant, leading to a slower user experience.
Ways to fix this:
- [stream] Provide a placeholder with \`<Suspense fallback={...}>\` around the data access
https://nextjs.org/docs/messages/instant-shell-url-data#wrap-in-or-move-into-suspense
- [block] Set \`export const instant = false\` to allow a blocking route
https://nextjs.org/docs/messages/instant-shell-url-data#allow-blocking-route
at LinkData (app/shells/(default)/invalid-runtime-searchparams/page.tsx:27:3)
at Page (app/shells/(default)/invalid-runtime-searchparams/page.tsx:17:7)
25 | searchParams: Promise<Record<string, string | string[]>>
26 | }) {
> 27 | await searchParams
| ^
28 | return <div>Link data - search params</div>
29 | }
30 | {
[cause]: Instant Validation:
at instant (app/shells/(default)/invalid-runtime-searchparams/page.tsx:3:33)
1 | import { Instant } from 'next'
2 |
> 3 | export const instant: Instant = {
| ^
4 | level: 'experimental-error',
5 | unstable_samples: [{ searchParams: {} }],
6 | }
}"
`)
} else {
await expect(browser).toDisplayCollapsedRedbox(`
{
"cause": [
{
"label": "Caused by: Instant Validation",
"source": "app/shells/(default)/invalid-runtime-searchparams/page.tsx (3:33) @ instant
> 3 | export const instant: Instant = {
| ^",
"stack": [
"instant app/shells/(default)/invalid-runtime-searchparams/page.tsx (3:33)",
"Set.forEach <anonymous>",
],
},
],
"code": "E1408",
"description": "Next.js encountered URL data outside of Suspense.",
"environmentLabel": "Server",
"label": "Instant",
"source": "app/shells/(default)/invalid-runtime-searchparams/page.tsx (27:3) @ LinkData
> 27 | await searchParams
| ^",
"stack": [
"LinkData app/shells/(default)/invalid-runtime-searchparams/page.tsx (27:3)",
"Page app/shells/(default)/invalid-runtime-searchparams/page.tsx (17:7)",
],
}
`)
}
} else {
const result = await prerender(
'/shells/(default)/invalid-runtime-searchparams'
)
expect(extractBuildValidationError(result.cliOutput))
.toMatchInlineSnapshot(`
"Error: Route "/shells/invalid-runtime-searchparams": Next.js encountered URL data during prerendering or a navigation.
\`params\` or \`searchParams\` accessed outside of \`<Suspense>\` may prevent the navigation from being instant, leading to a slower user experience.
Ways to fix this:
- [stream] Provide a placeholder with \`<Suspense fallback={...}>\` around the data access
https://nextjs.org/docs/messages/instant-shell-url-data#wrap-in-or-move-into-suspense
- [block] Set \`export const instant = false\` to allow a blocking route
https://nextjs.org/docs/messages/instant-shell-url-data#allow-blocking-route
at main (<anonymous>)
at body (<anonymous>)
at html (<anonymous>)
Build-time instant validation failed for route "/shells/invalid-runtime-searchparams".
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 "/shells/invalid-runtime-searchparams" 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 - unguarded static params in metadata', async () => {
// TODO(app-shells): static params currently aren't excluded from the shell.
// This should be failing validation.
if (isNextDev) {
const browser = await navigateTo(
'/shells/invalid-static-with-gsp-metadata/123'
)
await expect(browser).toDisplayCollapsedRedbox(`
{
"cause": [
{
"label": "Caused by: Instant Validation",
"source": "app/shells/(default)/invalid-static-with-gsp-metadata/[slug]/page.tsx (3:33) @ instant
> 3 | export const instant: Instant = {
| ^",
"stack": [
"instant app/shells/(default)/invalid-static-with-gsp-metadata/[slug]/page.tsx (3:33)",
"Set.forEach <anonymous>",
],
},
],
"code": "E1407",
"description": "Next.js encountered URL data in generateMetadata().",
"environmentLabel": "Server",
"label": "Blocking Route",
"source": "app/shells/(default)/invalid-static-with-gsp-metadata/[slug]/page.tsx (3:33) @ instant
> 3 | export const instant: Instant = {
| ^",
"stack": [
"Suspense <anonymous>",
"div <anonymous>",
"MetadataWrapper [Prerender] <anonymous>",
],
}
`)
} else {
const result = await prerender(
'/shells/(default)/invalid-static-with-gsp-metadata/[slug]'
)
// expect(
// extractBuildValidationError(result.cliOutput)
// ).toMatchInlineSnapshot(`...`)
// expect(result.exitCode).toBe(1)
// TODO(app-shells): The build errors here with a confusing error before we get to instant validation.
// This should be improved.
expect(result.cliOutput).toContain(
`Route "/shells/invalid-static-with-gsp-metadata/[slug]": Next.js encountered uncached or runtime data in \`generateMetadata()\`.`
)
expect(result.exitCode).toBe(1)
}
})
it('invalid - unguarded static params in a shell', async () => {
if (isNextDev) {
const browser = await navigateTo(
'/shells/invalid-static-with-gsp/123'
)
await expect(browser).toDisplayCollapsedRedbox(`
{
"cause": [
{
"label": "Caused by: Instant Validation",
"source": "app/shells/(default)/invalid-static-with-gsp/[slug]/page.tsx (3:33) @ instant
> 3 | export const instant: Instant = {
| ^",
"stack": [
"instant app/shells/(default)/invalid-static-with-gsp/[slug]/page.tsx (3:33)",
"Set.forEach <anonymous>",
],
},
],
"code": "E1408",
"description": "Next.js encountered URL data outside of Suspense.",
"environmentLabel": "Server",
"label": "Instant",
"source": "app/shells/(default)/invalid-static-with-gsp/[slug]/page.tsx (31:20) @ LinkData
> 31 | const { slug } = await params
| ^",
"stack": [
"LinkData app/shells/(default)/invalid-static-with-gsp/[slug]/page.tsx (31:20)",
"Page app/shells/(default)/invalid-static-with-gsp/[slug]/page.tsx (25:7)",
],
}
`)
} else {
const result = await prerender(
'/shells/(default)/invalid-static-with-gsp/[slug]'
)
expect(extractBuildValidationError(result.cliOutput))
.toMatchInlineSnapshot(`
"Error: Route "/shells/invalid-static-with-gsp/[slug]": Next.js encountered URL data during prerendering or a navigation.
\`params\` or \`searchParams\` accessed outside of \`<Suspense>\` may prevent the navigation from being instant, leading to a slower user experience.
Ways to fix this:
- [stream] Provide a placeholder with \`<Suspense fallback={...}>\` around the data access
https://nextjs.org/docs/messages/instant-shell-url-data#wrap-in-or-move-into-suspense
- [block] Set \`export const instant = false\` to allow a blocking route
https://nextjs.org/docs/messages/instant-shell-url-data#allow-blocking-route
at main (<anonymous>)
at body (<anonymous>)
at html (<anonymous>)
Build-time instant validation failed for route "/shells/invalid-static-with-gsp/[slug]".
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 "/shells/invalid-static-with-gsp/[slug]" 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 - unguarded root param', async () => {
if (isNextDev) {
const browser = await navigateTo(
'/shells/with-root-param/en/valid-unguarded-root-param'
)
await expectNoDevValidationErrors(browser, await browser.url())
} else {
const result = await prerender(
'/shells/with-root-param/[lang]/valid-unguarded-root-param'
)
expectNoBuildValidationErrors(result)
}
})
it('valid - unguarded root param accessed via params prop', async () => {
if (isNextDev) {
const browser = await navigateTo(
'/shells/with-root-param/en/valid-unguarded-root-param-via-params'
)
await expectNoDevValidationErrors(browser, await browser.url())
} else {
const result = await prerender(
'/shells/with-root-param/[lang]/valid-unguarded-root-param-via-params'
)
expectNoBuildValidationErrors(result)
}
})
})
} else {
describe('non-app shell validation', () => {
it('valid - unguarded static params', async () => {
if (isNextDev) {
const browser = await navigateTo(
'/suspense-in-root/non-app-shell/valid-unguarded-static-params/123'
)
await expectNoDevValidationErrors(browser, await browser.url())
} else {
const result = await prerender(
'/suspense-in-root/non-app-shell/valid-unguarded-static-params/[slug]'
)
expectNoBuildValidationErrors(result)
}
})
})
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,2 @@
process.env.__NEXT_PARTIAL_PREFETCHING = 'true'
require('./slots-and-groups.test')
@@ -0,0 +1,6 @@
import { runInstantValidationTests } from './harness.util'
import { registerSlotsAndGroupsTests } from './slots-and-groups.util'
runInstantValidationTests((ctx) => {
registerSlotsAndGroupsTests(ctx)
})
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,2 @@
process.env.__NEXT_PARTIAL_PREFETCHING = 'true'
require('./suspense-boundaries.test')
@@ -0,0 +1,6 @@
import { runInstantValidationTests } from './harness.util'
import { registerSuspenseBoundariesTests } from './suspense-boundaries.util'
runInstantValidationTests((ctx) => {
registerSuspenseBoundariesTests(ctx)
})
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,2 @@
process.env.__NEXT_PARTIAL_PREFETCHING = 'true'
require('./sync-io-and-blocking.test')
@@ -0,0 +1,6 @@
import { runInstantValidationTests } from './harness.util'
import { registerSyncIoAndBlockingTests } from './sync-io-and-blocking.util'
runInstantValidationTests((ctx) => {
registerSyncIoAndBlockingTests(ctx)
})
@@ -0,0 +1,636 @@
import {
expectNoBuildValidationErrors,
extractBuildValidationError,
} from 'e2e-utils/instant-validation'
import { getDeterministicOutput } from '../cache-components-errors/utils'
import { type InstantValidationCaseContext } from './harness.util'
const partialPrefetching = !!process.env.__NEXT_PARTIAL_PREFETCHING
export function registerSyncIoAndBlockingTests(
ctx: InstantValidationCaseContext
) {
const { isNextDev, navigateTo, expectNoDevValidationErrors, prerender } = ctx
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": "E1295",
"description": "Next.js encountered the unstable value Date.now() while prerendering.",
"environmentLabel": "Server",
"label": "Blocking Route",
"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": Next.js encountered the unstable value \`Date.now()\` while prerendering.
This value can change between renders, so it must be either prerendered or computed later.
Ways to fix this:
- [dynamic] Render at request time by adding a dynamic data access (e.g. \`await connection()\`) before this call
https://nextjs.org/docs/messages/blocking-prerender-current-time#generate-on-every-request
- [cache] Prerender and cache the value with \`"use cache"\`
https://nextjs.org/docs/messages/blocking-prerender-current-time#cache-the-timestamp
- [client] Render the value on the client with \`"use client"\`
https://nextjs.org/docs/messages/blocking-prerender-current-time#render-on-the-client
- [measure] If the value is for telemetry, use a timing API such as \`performance.now()\`
https://nextjs.org/docs/messages/blocking-prerender-current-time#for-telemetry-use-a-timing-api
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>
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": "E1295",
"description": "Next.js encountered the unstable value Date.now() while prerendering.",
"environmentLabel": "Server",
"label": "Blocking Route",
"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": Next.js encountered the unstable value \`Date.now()\` while prerendering.
This value can change between renders, so it must be either prerendered or computed later.
Ways to fix this:
- [dynamic] Render at request time by adding a dynamic data access (e.g. \`await connection()\`) before this call
https://nextjs.org/docs/messages/blocking-prerender-current-time#generate-on-every-request
- [cache] Prerender and cache the value with \`"use cache"\`
https://nextjs.org/docs/messages/blocking-prerender-current-time#cache-the-timestamp
- [client] Render the value on the client with \`"use client"\`
https://nextjs.org/docs/messages/blocking-prerender-current-time#render-on-the-client
- [measure] If the value is for telemetry, use a timing API such as \`performance.now()\`
https://nextjs.org/docs/messages/blocking-prerender-current-time#for-telemetry-use-a-timing-api
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>
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": "E1295",
"description": "Next.js encountered the unstable value Date.now() while prerendering.",
"environmentLabel": "Server",
"label": "Blocking Route",
"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'
)
// TODO: This currently fails with the static-prerender sync-IO error on
// Date.now(), not the instant-validation sync-IO error described above.
// The cookies() promise hangs during the static shell, but the cache
// body doesn't read it, so the cache resolves with 'cached result'
// regardless and Date.now() runs before instant validation gets a
// chance. When we add staged rendering to static prerendering too,
// cookies should resolve at the runtime stage and the cache call should
// defer until its args serialize, so the cache function (and the
// Date.now() that follows) lands in the runtime stage.
expect(
getDeterministicOutput(result.cliOutput, {
isMinified: true,
startingLineMatch: 'Collecting page data',
})
).toMatchInlineSnapshot(`
"Error: Route "/suspense-in-root/runtime/invalid-sync-io-after-cache-with-cookie-input": Next.js encountered the unstable value \`Date.now()\` while prerendering.
This value can change between renders, so it must be either prerendered or computed later.
Ways to fix this:
- [dynamic] Render at request time by adding a dynamic data access (e.g. \`await connection()\`) before this call
https://nextjs.org/docs/messages/blocking-prerender-current-time#generate-on-every-request
- [cache] Prerender and cache the value with \`"use cache"\`
https://nextjs.org/docs/messages/blocking-prerender-current-time#cache-the-timestamp
- [client] Render the value on the client with \`"use client"\`
https://nextjs.org/docs/messages/blocking-prerender-current-time#render-on-the-client
- [measure] If the value is for telemetry, use a timing API such as \`performance.now()\`
https://nextjs.org/docs/messages/blocking-prerender-current-time#for-telemetry-use-a-timing-api
at a (app/suspense-in-root/runtime/invalid-sync-io-after-cache-with-cookie-input/page.tsx:28:20)
26 | const cookiePromise = cookies().then((c) => c.get('testCookie')?.value ?? '')
27 | await cachedFn(cookiePromise)
> 28 | const now = Date.now()
| ^
29 | return (
30 | <main>
31 | <p>Runtime page with sync IO after cache with cookie input: {now}</p>
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.
Error occurred prerendering page "/suspense-in-root/runtime/invalid-sync-io-after-cache-with-cookie-input". Read more: https://nextjs.org/docs/messages/prerender-error
Export encountered an error on /suspense-in-root/runtime/invalid-sync-io-after-cache-with-cookie-input/page: /suspense-in-root/runtime/invalid-sync-io-after-cache-with-cookie-input, exiting the build."
`)
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": "E1295",
"description": "Next.js encountered the unstable value Date.now() while prerendering.",
"environmentLabel": "Server",
"label": "Blocking Route",
"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": Next.js encountered the unstable value \`Date.now()\` while prerendering.
This value can change between renders, so it must be either prerendered or computed later.
Ways to fix this:
- [dynamic] Render at request time by adding a dynamic data access (e.g. \`await connection()\`) before this call
https://nextjs.org/docs/messages/blocking-prerender-current-time#generate-on-every-request
- [cache] Prerender and cache the value with \`"use cache"\`
https://nextjs.org/docs/messages/blocking-prerender-current-time#cache-the-timestamp
- [client] Render the value on the client with \`"use client"\`
https://nextjs.org/docs/messages/blocking-prerender-current-time#render-on-the-client
- [measure] If the value is for telemetry, use a timing API such as \`performance.now()\`
https://nextjs.org/docs/messages/blocking-prerender-current-time#for-telemetry-use-a-timing-api
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": "E1295",
"description": "Next.js encountered the unstable value Date.now() while prerendering.",
"environmentLabel": "Server",
"label": "Blocking Route",
"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": Next.js encountered the unstable value \`Date.now()\` while prerendering.
This value can change between renders, so it must be either prerendered or computed later.
Ways to fix this:
- [dynamic] Render at request time by adding a dynamic data access (e.g. \`await connection()\`) before this call
https://nextjs.org/docs/messages/blocking-prerender-current-time#generate-on-every-request
- [cache] Prerender and cache the value with \`"use cache"\`
https://nextjs.org/docs/messages/blocking-prerender-current-time#cache-the-timestamp
- [client] Render the value on the client with \`"use client"\`
https://nextjs.org/docs/messages/blocking-prerender-current-time#render-on-the-client
- [measure] If the value is for telemetry, use a timing API such as \`performance.now()\`
https://nextjs.org/docs/messages/blocking-prerender-current-time#for-telemetry-use-a-timing-api
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)
}
})
describe('blocking', () => {
it('valid - blocking layout with 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'
)
if (partialPrefetching) {
// This page uses a runtime shell, so it can use cookies
// TODO(app-shells): missing "allow-runtime"
await expectNoDevValidationErrors(browser, await browser.url())
} else {
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:24) @ instant
> 3 | export const instant = { level: 'experimental-error' }
| ^",
"stack": [
"instant app/suspense-in-root/static/blocking-layout/missing-suspense-around-dynamic/page.tsx (3:24)",
"Set.forEach <anonymous>",
],
},
],
"code": "E1402",
"description": "Next.js encountered runtime data during a navigation.",
"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'
)
if (partialPrefetching) {
// This page uses a runtime shell, so it can use cookies
// TODO(app-shells): missing "allow-runtime"
expectNoBuildValidationErrors(result)
} else {
expect(extractBuildValidationError(result.cliOutput))
.toMatchInlineSnapshot(`
"Error: Route "/suspense-in-root/static/blocking-layout/missing-suspense-around-dynamic": Next.js encountered runtime data during prerendering or a navigation.
\`cookies()\`, \`headers()\`, \`params\`, or \`searchParams\` accessed outside of \`<Suspense>\` prevents the route from being prerendered or the navigation from being instant, leading to a slower user experience.
Ways to fix this:
- [stream] Provide a placeholder with \`<Suspense fallback={...}>\` around the data access
https://nextjs.org/docs/messages/blocking-prerender-runtime#wrap-in-or-move-into-suspense
- [block] Set \`export const instant = false\` to allow a blocking route
https://nextjs.org/docs/messages/blocking-prerender-runtime#allow-blocking-route
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'
)
if (partialPrefetching) {
// This page uses a runtime shell, so it can use cookies
// TODO(app-shells): missing "allow-runtime"
await expectNoDevValidationErrors(browser, await browser.url())
} else {
await expect(browser).toDisplayCollapsedRedbox(`
{
"cause": [
{
"label": "Caused by: Instant Validation",
"source": "app/suspense-in-root/static/invalid-blocking-inside-static/layout.tsx (1:24) @ instant
> 1 | export const instant = { level: 'experimental-error' }
| ^",
"stack": [
"instant app/suspense-in-root/static/invalid-blocking-inside-static/layout.tsx (1:24)",
"Set.forEach <anonymous>",
],
},
],
"code": "E1402",
"description": "Next.js encountered runtime data during a navigation.",
"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'
)
if (partialPrefetching) {
// This page uses a runtime shell, so it can use cookies
// TODO(app-shells): missing "allow-runtime"
expectNoBuildValidationErrors(result)
} else {
expect(extractBuildValidationError(result.cliOutput))
.toMatchInlineSnapshot(`
"Error: Route "/suspense-in-root/static/invalid-blocking-inside-static": Next.js encountered runtime data during prerendering or a navigation.
\`cookies()\`, \`headers()\`, \`params\`, or \`searchParams\` accessed outside of \`<Suspense>\` prevents the route from being prerendered or the navigation from being instant, leading to a slower user experience.
Ways to fix this:
- [stream] Provide a placeholder with \`<Suspense fallback={...}>\` around the data access
https://nextjs.org/docs/messages/blocking-prerender-runtime#wrap-in-or-move-into-suspense
- [block] Set \`export const instant = false\` to allow a blocking route
https://nextjs.org/docs/messages/blocking-prerender-runtime#allow-blocking-route
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:24) @ instant
> 3 | export const instant = { level: 'experimental-error' }
| ^",
"stack": [
"instant app/suspense-in-root/runtime/invalid-blocking-inside-runtime/layout.tsx (3:24)",
"Set.forEach <anonymous>",
],
},
],
"code": "E1398",
"description": "Next.js encountered uncached data during a navigation.",
"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 prerendering or a navigation.
\`fetch(...)\` or \`connection()\` accessed outside of \`<Suspense>\` prevents the route from being prerendered or the navigation from being instant, leading to a slower user experience.
Ways to fix this:
- [stream] Provide a placeholder with \`<Suspense fallback={...}>\` around the data access
https://nextjs.org/docs/messages/blocking-prerender-dynamic#wrap-in-or-move-into-suspense
- [cache] Cache the data access with \`"use cache"\` (does not apply to \`connection()\`)
https://nextjs.org/docs/messages/blocking-prerender-dynamic#cache-the-component-or-data
- [block] Set \`export const instant = false\` to allow a blocking route
https://nextjs.org/docs/messages/blocking-prerender-dynamic#allow-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)
}
})
})
}
+122 -90
View File
@@ -6134,7 +6134,7 @@
"flakey": [],
"runtimeError": false
},
"test/e2e/app-dir/instant-validation/instant-validation-parallel-slots.test.ts": {
"test/e2e/app-dir/instant-validation/parallel-slots.test.ts": {
"passed": [
"instant validation - parallel slot configs dev - client navigation conditional slot rendering errors when both slots render and breadcrumbs calls cookies",
"instant validation - parallel slot configs dev - client navigation conditional slot rendering errors when configured children slot is hidden, breadcrumbs blocked",
@@ -6168,14 +6168,8 @@
"flakey": [],
"runtimeError": false
},
"test/e2e/app-dir/instant-validation/instant-validation.test.ts": {
"test/e2e/app-dir/instant-validation/client.test.ts": {
"passed": [
"instant validation dev - client navigation blocking invalid - blocking page inside a runtime layout is not allowed if the layout has no suspense",
"instant validation dev - client navigation blocking invalid - blocking page inside a static layout is not allowed if the layout has no suspense",
"instant validation dev - client navigation blocking invalid - missing suspense inside blocking layout",
"instant validation dev - client navigation blocking valid - blocking layout with instant = false is allowed to block",
"instant validation dev - client navigation blocking valid - blocking page inside a runtime layout is allowed if the layout has suspense",
"instant validation dev - client navigation blocking valid - blocking page inside a static layout is allowed if the layout has suspense",
"instant validation dev - client navigation client components unable to validate - parent suspends on client data and blocks children",
"instant validation dev - client navigation client components valid - parent suspends on client data but does not block children",
"instant validation dev - client navigation client components valid - parent uses dynamic usePathname() in a client component",
@@ -6186,10 +6180,24 @@
"instant validation dev - client navigation client errors unable to validate - client error in component from node_modules blocks children",
"instant validation dev - client navigation client errors unable to validate - client error in parent blocks children",
"instant validation dev - client navigation client errors valid - client error from sibling of children slot with suspense",
"instant validation dev - client navigation config depth preference invalid - children config preferred at equal depth",
"instant validation dev - client navigation config depth preference invalid - cross-slot blocking falls back to deep children config",
"instant validation dev - client navigation config depth preference invalid - deeper children config preferred over shallower slot config",
"instant validation dev - client navigation config depth preference invalid - deeper slot config preferred over shallower children catchall",
"instant validation dev - initial load client components unable to validate - parent suspends on client data and blocks children",
"instant validation dev - initial load client components valid - parent suspends on client data but does not block children",
"instant validation dev - initial load client components valid - parent uses dynamic usePathname() in a client component",
"instant validation dev - initial load client components valid - parent uses sync IO in a client component",
"instant validation dev - initial load client components valid - parent uses useSearchParams() in a client component",
"instant validation dev - initial load client errors unable to validate - CSR bailout from next/dynamic blocks children",
"instant validation dev - initial load client errors unable to validate - client error from sibling of children slot without suspense",
"instant validation dev - initial load client errors unable to validate - client error in component from node_modules blocks children",
"instant validation dev - initial load client errors unable to validate - client error in parent blocks children",
"instant validation dev - initial load client errors valid - client error from sibling of children slot with suspense"
],
"failed": [],
"pending": [],
"flakey": [],
"runtimeError": false
},
"test/e2e/app-dir/instant-validation/head-and-reporting.test.ts": {
"passed": [
"instant validation dev - client navigation disabling validation disabling build validation",
"instant validation dev - client navigation disabling validation disabling dev validation",
"instant validation dev - client navigation disabling validation in a layout",
@@ -6202,63 +6210,6 @@
"instant validation dev - client navigation head valid - runtime prefetch - dynamic generateMetadata does not block navigation",
"instant validation dev - client navigation head valid - runtime prefetch - runtime generateViewport does not block navigation",
"instant validation dev - client navigation head valid - static prefetch - runtime generateMetadata does not block navigation",
"instant validation dev - client navigation invalid - loading.js above route group does not cover dynamic in page",
"instant validation dev - client navigation invalid - loading.js covers page, but not layout at the same level",
"instant validation dev - client navigation invalid - missing suspense in parallel slot default slot",
"instant validation dev - client navigation invalid - missing suspense in parallel slot index",
"instant validation dev - client navigation invalid - missing suspense in parallel slot subpage",
"instant validation dev - client navigation invalid - runtime prefetch - missing suspense around dynamic",
"instant validation dev - client navigation invalid - runtime prefetch - missing suspense around dynamic in a layout",
"instant validation dev - client navigation invalid - runtime prefetch - suspense too high",
"instant validation dev - client navigation invalid - runtime prefetch - sync IO after public cache with cookie input",
"instant validation dev - client navigation invalid - runtime prefetch - sync IO after runtime API",
"instant validation dev - client navigation invalid - runtime prefetch - sync IO in generateMetadata",
"instant validation dev - client navigation invalid - runtime prefetch - sync IO in layout generateMetadata when page is prefetchable",
"instant validation dev - client navigation invalid - runtime prefetch - sync IO in runtime segment with valid static parent",
"instant validation dev - client navigation invalid - static layout above runtime config blocks navigation",
"instant validation dev - client navigation invalid - static prefetch - missing suspense around dynamic",
"instant validation dev - client navigation invalid - static prefetch - missing suspense around dynamic in a layout",
"instant validation dev - client navigation invalid - static prefetch - missing suspense around params",
"instant validation dev - client navigation invalid - static prefetch - missing suspense around runtime",
"instant validation dev - client navigation invalid - static prefetch - missing suspense around search params",
"instant validation dev - client navigation invalid - static prefetch - suspense too high",
"instant validation dev - client navigation parallel slots with different group depths invalid - children hole detected before shallow @slot hole",
"instant validation dev - client navigation parallel slots with different group depths invalid - deep hole in @slot detected before shallow hole in children",
"instant validation dev - client navigation route group shared boundary invalid - blocking layout inside shared route group boundary",
"instant validation dev - client navigation route groups invalid - config on both route group and segment layout - cookies() blocks below",
"instant validation dev - client navigation route groups invalid - config on route group layout - cookies() blocks below",
"instant validation dev - client navigation route groups invalid - config on route group layout - cookies() blocks in deeper segment",
"instant validation dev - client navigation route groups invalid - config on segment layout - cookies() blocks through route group below",
"instant validation dev - client navigation route groups invalid - config on segment layout inside route group - cookies() blocks below",
"instant validation dev - client navigation valid - no suspense needed around dynamic in page if loading.js is present",
"instant validation dev - client navigation valid - runtime prefetch - does not require Suspense around params",
"instant validation dev - client navigation valid - runtime prefetch - does not require Suspense around search params",
"instant validation dev - client navigation valid - runtime prefetch - suspense only around dynamic",
"instant validation dev - client navigation valid - runtime prefetch - sync IO in a static parent layout is allowed",
"instant validation dev - client navigation valid - runtime prefetch - sync IO in generateMetadata on a static page is allowed",
"instant validation dev - client navigation valid - runtime prefetch - sync IO in layout generateMetadata when page is NOT prefetchable",
"instant validation dev - client navigation valid - static prefetch - suspense around runtime and dynamic",
"instant validation dev - client navigation valid - target segment not visible in all navigations",
"instant validation dev - initial load blocking invalid - blocking page inside a runtime layout is not allowed if the layout has no suspense",
"instant validation dev - initial load blocking invalid - blocking page inside a static layout is not allowed if the layout has no suspense",
"instant validation dev - initial load blocking invalid - missing suspense inside blocking layout",
"instant validation dev - initial load blocking valid - blocking layout with instant = false is allowed to block",
"instant validation dev - initial load blocking valid - blocking page inside a runtime layout is allowed if the layout has suspense",
"instant validation dev - initial load blocking valid - blocking page inside a static layout is allowed if the layout has suspense",
"instant validation dev - initial load client components unable to validate - parent suspends on client data and blocks children",
"instant validation dev - initial load client components valid - parent suspends on client data but does not block children",
"instant validation dev - initial load client components valid - parent uses dynamic usePathname() in a client component",
"instant validation dev - initial load client components valid - parent uses sync IO in a client component",
"instant validation dev - initial load client components valid - parent uses useSearchParams() in a client component",
"instant validation dev - initial load client errors unable to validate - CSR bailout from next/dynamic blocks children",
"instant validation dev - initial load client errors unable to validate - client error from sibling of children slot without suspense",
"instant validation dev - initial load client errors unable to validate - client error in component from node_modules blocks children",
"instant validation dev - initial load client errors unable to validate - client error in parent blocks children",
"instant validation dev - initial load client errors valid - client error from sibling of children slot with suspense",
"instant validation dev - initial load config depth preference invalid - children config preferred at equal depth",
"instant validation dev - initial load config depth preference invalid - cross-slot blocking falls back to deep children config",
"instant validation dev - initial load config depth preference invalid - deeper children config preferred over shallower slot config",
"instant validation dev - initial load config depth preference invalid - deeper slot config preferred over shallower children catchall",
"instant validation dev - initial load disabling validation disabling build validation",
"instant validation dev - initial load disabling validation disabling dev validation",
"instant validation dev - initial load disabling validation in a layout",
@@ -6270,27 +6221,39 @@
"instant validation dev - initial load head valid - blocking layout - dynamic viewport is allowed to block",
"instant validation dev - initial load head valid - runtime prefetch - dynamic generateMetadata does not block navigation",
"instant validation dev - initial load head valid - runtime prefetch - runtime generateViewport does not block navigation",
"instant validation dev - initial load head valid - static prefetch - runtime generateMetadata does not block navigation",
"instant validation dev - initial load invalid - loading.js above route group does not cover dynamic in page",
"instant validation dev - initial load invalid - loading.js covers page, but not layout at the same level",
"instant validation dev - initial load head valid - static prefetch - runtime generateMetadata does not block navigation"
],
"failed": [],
"pending": [],
"flakey": [],
"runtimeError": false
},
"test/e2e/app-dir/instant-validation/slots-and-groups.test.ts": {
"passed": [
"instant validation dev - client navigation config depth preference invalid - children config preferred at equal depth",
"instant validation dev - client navigation config depth preference invalid - cross-slot blocking falls back to deep children config",
"instant validation dev - client navigation config depth preference invalid - deeper children config preferred over shallower slot config",
"instant validation dev - client navigation config depth preference invalid - deeper slot config preferred over shallower children catchall",
"instant validation dev - client navigation invalid - missing suspense in parallel slot default slot",
"instant validation dev - client navigation invalid - missing suspense in parallel slot index",
"instant validation dev - client navigation invalid - missing suspense in parallel slot subpage",
"instant validation dev - client navigation invalid - static layout above runtime config blocks navigation",
"instant validation dev - client navigation parallel slots with different group depths invalid - children hole detected before shallow @slot hole",
"instant validation dev - client navigation parallel slots with different group depths invalid - deep hole in @slot detected before shallow hole in children",
"instant validation dev - client navigation route group shared boundary invalid - blocking layout inside shared route group boundary",
"instant validation dev - client navigation route groups invalid - config on both route group and segment layout - cookies() blocks below",
"instant validation dev - client navigation route groups invalid - config on route group layout - cookies() blocks below",
"instant validation dev - client navigation route groups invalid - config on route group layout - cookies() blocks in deeper segment",
"instant validation dev - client navigation route groups invalid - config on segment layout - cookies() blocks through route group below",
"instant validation dev - client navigation route groups invalid - config on segment layout inside route group - cookies() blocks below",
"instant validation dev - initial load config depth preference invalid - children config preferred at equal depth",
"instant validation dev - initial load config depth preference invalid - cross-slot blocking falls back to deep children config",
"instant validation dev - initial load config depth preference invalid - deeper children config preferred over shallower slot config",
"instant validation dev - initial load config depth preference invalid - deeper slot config preferred over shallower children catchall",
"instant validation dev - initial load invalid - missing suspense in parallel slot default slot",
"instant validation dev - initial load invalid - missing suspense in parallel slot index",
"instant validation dev - initial load invalid - missing suspense in parallel slot subpage",
"instant validation dev - initial load invalid - runtime prefetch - missing suspense around dynamic",
"instant validation dev - initial load invalid - runtime prefetch - missing suspense around dynamic in a layout",
"instant validation dev - initial load invalid - runtime prefetch - suspense too high",
"instant validation dev - initial load invalid - runtime prefetch - sync IO after public cache with cookie input",
"instant validation dev - initial load invalid - runtime prefetch - sync IO after runtime API",
"instant validation dev - initial load invalid - runtime prefetch - sync IO in generateMetadata",
"instant validation dev - initial load invalid - runtime prefetch - sync IO in layout generateMetadata when page is prefetchable",
"instant validation dev - initial load invalid - runtime prefetch - sync IO in runtime segment with valid static parent",
"instant validation dev - initial load invalid - static layout above runtime config blocks navigation",
"instant validation dev - initial load invalid - static prefetch - missing suspense around dynamic",
"instant validation dev - initial load invalid - static prefetch - missing suspense around dynamic in a layout",
"instant validation dev - initial load invalid - static prefetch - missing suspense around params",
"instant validation dev - initial load invalid - static prefetch - missing suspense around runtime",
"instant validation dev - initial load invalid - static prefetch - missing suspense around search params",
"instant validation dev - initial load invalid - static prefetch - suspense too high",
"instant validation dev - initial load parallel slots with different group depths invalid - children hole detected before shallow @slot hole",
"instant validation dev - initial load parallel slots with different group depths invalid - deep hole in @slot detected before shallow hole in children",
"instant validation dev - initial load route group shared boundary invalid - blocking layout inside shared route group boundary",
@@ -6298,14 +6261,47 @@
"instant validation dev - initial load route groups invalid - config on route group layout - cookies() blocks below",
"instant validation dev - initial load route groups invalid - config on route group layout - cookies() blocks in deeper segment",
"instant validation dev - initial load route groups invalid - config on segment layout - cookies() blocks through route group below",
"instant validation dev - initial load route groups invalid - config on segment layout inside route group - cookies() blocks below",
"instant validation dev - initial load route groups invalid - config on segment layout inside route group - cookies() blocks below"
],
"failed": [],
"pending": [],
"flakey": [],
"runtimeError": false
},
"test/e2e/app-dir/instant-validation/suspense-boundaries.test.ts": {
"passed": [
"instant validation dev - client navigation invalid - loading.js above route group does not cover dynamic in page",
"instant validation dev - client navigation invalid - loading.js covers page, but not layout at the same level",
"instant validation dev - client navigation invalid - runtime prefetch - missing suspense around dynamic",
"instant validation dev - client navigation invalid - runtime prefetch - missing suspense around dynamic in a layout",
"instant validation dev - client navigation invalid - runtime prefetch - suspense too high",
"instant validation dev - client navigation invalid - static prefetch - missing suspense around dynamic",
"instant validation dev - client navigation invalid - static prefetch - missing suspense around dynamic in a layout",
"instant validation dev - client navigation invalid - static prefetch - missing suspense around params",
"instant validation dev - client navigation invalid - static prefetch - missing suspense around runtime",
"instant validation dev - client navigation invalid - static prefetch - missing suspense around search params",
"instant validation dev - client navigation invalid - static prefetch - suspense too high",
"instant validation dev - client navigation valid - no suspense needed around dynamic in page if loading.js is present",
"instant validation dev - client navigation valid - runtime prefetch - does not require Suspense around params",
"instant validation dev - client navigation valid - runtime prefetch - does not require Suspense around search params",
"instant validation dev - client navigation valid - runtime prefetch - suspense only around dynamic",
"instant validation dev - client navigation valid - static prefetch - suspense around runtime and dynamic",
"instant validation dev - client navigation valid - target segment not visible in all navigations",
"instant validation dev - initial load invalid - loading.js above route group does not cover dynamic in page",
"instant validation dev - initial load invalid - loading.js covers page, but not layout at the same level",
"instant validation dev - initial load invalid - runtime prefetch - missing suspense around dynamic",
"instant validation dev - initial load invalid - runtime prefetch - missing suspense around dynamic in a layout",
"instant validation dev - initial load invalid - runtime prefetch - suspense too high",
"instant validation dev - initial load invalid - static prefetch - missing suspense around dynamic",
"instant validation dev - initial load invalid - static prefetch - missing suspense around dynamic in a layout",
"instant validation dev - initial load invalid - static prefetch - missing suspense around params",
"instant validation dev - initial load invalid - static prefetch - missing suspense around runtime",
"instant validation dev - initial load invalid - static prefetch - missing suspense around search params",
"instant validation dev - initial load invalid - static prefetch - suspense too high",
"instant validation dev - initial load valid - no suspense needed around dynamic in page if loading.js is present",
"instant validation dev - initial load valid - runtime prefetch - does not require Suspense around params",
"instant validation dev - initial load valid - runtime prefetch - does not require Suspense around search params",
"instant validation dev - initial load valid - runtime prefetch - suspense only around dynamic",
"instant validation dev - initial load valid - runtime prefetch - sync IO in a static parent layout is allowed",
"instant validation dev - initial load valid - runtime prefetch - sync IO in generateMetadata on a static page is allowed",
"instant validation dev - initial load valid - runtime prefetch - sync IO in layout generateMetadata when page is NOT prefetchable",
"instant validation dev - initial load valid - static prefetch - suspense around runtime and dynamic",
"instant validation dev - initial load valid - target segment not visible in all navigations"
],
@@ -6314,6 +6310,42 @@
"flakey": [],
"runtimeError": false
},
"test/e2e/app-dir/instant-validation/sync-io-and-blocking.test.ts": {
"passed": [
"instant validation dev - client navigation blocking invalid - blocking page inside a runtime layout is not allowed if the layout has no suspense",
"instant validation dev - client navigation blocking invalid - blocking page inside a static layout is not allowed if the layout has no suspense",
"instant validation dev - client navigation blocking invalid - missing suspense inside blocking layout",
"instant validation dev - client navigation blocking valid - blocking layout with instant = false is allowed to block",
"instant validation dev - client navigation blocking valid - blocking page inside a runtime layout is allowed if the layout has suspense",
"instant validation dev - client navigation blocking valid - blocking page inside a static layout is allowed if the layout has suspense",
"instant validation dev - client navigation invalid - runtime prefetch - sync IO after public cache with cookie input",
"instant validation dev - client navigation invalid - runtime prefetch - sync IO after runtime API",
"instant validation dev - client navigation invalid - runtime prefetch - sync IO in generateMetadata",
"instant validation dev - client navigation invalid - runtime prefetch - sync IO in layout generateMetadata when page is prefetchable",
"instant validation dev - client navigation invalid - runtime prefetch - sync IO in runtime segment with valid static parent",
"instant validation dev - client navigation valid - runtime prefetch - sync IO in a static parent layout is allowed",
"instant validation dev - client navigation valid - runtime prefetch - sync IO in generateMetadata on a static page is allowed",
"instant validation dev - client navigation valid - runtime prefetch - sync IO in layout generateMetadata when page is NOT prefetchable",
"instant validation dev - initial load blocking invalid - blocking page inside a runtime layout is not allowed if the layout has no suspense",
"instant validation dev - initial load blocking invalid - blocking page inside a static layout is not allowed if the layout has no suspense",
"instant validation dev - initial load blocking invalid - missing suspense inside blocking layout",
"instant validation dev - initial load blocking valid - blocking layout with instant = false is allowed to block",
"instant validation dev - initial load blocking valid - blocking page inside a runtime layout is allowed if the layout has suspense",
"instant validation dev - initial load blocking valid - blocking page inside a static layout is allowed if the layout has suspense",
"instant validation dev - initial load invalid - runtime prefetch - sync IO after public cache with cookie input",
"instant validation dev - initial load invalid - runtime prefetch - sync IO after runtime API",
"instant validation dev - initial load invalid - runtime prefetch - sync IO in generateMetadata",
"instant validation dev - initial load invalid - runtime prefetch - sync IO in layout generateMetadata when page is prefetchable",
"instant validation dev - initial load invalid - runtime prefetch - sync IO in runtime segment with valid static parent",
"instant validation dev - initial load valid - runtime prefetch - sync IO in a static parent layout is allowed",
"instant validation dev - initial load valid - runtime prefetch - sync IO in generateMetadata on a static page is allowed",
"instant validation dev - initial load valid - runtime prefetch - sync IO in layout generateMetadata when page is NOT prefetchable"
],
"failed": [],
"pending": [],
"flakey": [],
"runtimeError": false
},
"test/e2e/app-dir/instrumentation-order/instrumentation-order.test.ts": {
"passed": ["instrumentation-order should work"],
"failed": [],