Files
Hendrik LiebauandGitHub 3c4afc1a3a Run Cache Components dev validation on a worker thread (#96153)
This moves the dev-mode Cache Components validation renders off the dev
server's main thread onto a worker thread, so rapid navigation no longer
starves the event loop. The worker crosses into the app-page bundle
exactly once, calling a new
`ComponentMod.routeModule.runValidationInDev` entry that rebuilds the
render context, work store, and request store from a serializable
snapshot and runs the whole validation there, so the client prerender
and the user's client components resolve the single app-page React
instance rather than a second copy.

A thin worker shell (`dev-validation-worker.ts`) plus a single-worker
pool (`dev-validation-worker-pool.ts`) load the user bundle via
`loadComponents`, install code-frame support for CLI output, and forward
the validation errors back as Flight bytes; the main thread only
delivers them to the overlay through `sendErrorsToBrowser`. The
snapshot, the globalThis-symbol handoff, and the shared error-delivery
helpers live in their own modules. The dev server installs the worker
when `experimental.devValidationWorker` is not `false`, and
`runDevValidationInBackground` uses it when present, falling back to the
in-process path otherwise. A one-slot `SharedArrayBuffer` propagates a
supersede abort into the worker so a newer navigation cancels an
in-flight validation.

The runtime bundle gains an `app-worker` entry for the worker with the
`build/swc` boundary externalized for the code-frame native binding, and
`patch-error-inspect.ts` now backs its code-frame renderer with a
globalThis symbol so all copies of the module share it across the
thread.

The synthetic `bench/dev-validation` benchmark navigates back-to-back,
so every click lands inside the validation window — the worst case for
main-thread contention. Browser-observed navigation TTFB in that window,
worker vs in-process:

| Route  | Worker (p50/p95/max) | In-process (p50/p95/max) |
| ------ | -------------------- | ------------------------ |
| client | 19 / 24 / 27 ms      | 40 / 66 / 7762 ms        |
| server | 42 / 45 / 46 ms      | 122 / 158 / 252 ms       |
| sprite | 109 / 117 / 169 ms   | 196 / 208 / 299 ms       |

The steady-state difference is only tens of milliseconds; the effect
that matters is the tail. In-process, a navigation that collides with an
in-flight validation render can stall for seconds (this run peaked at
~7.8s on the client route, and the peak varies run to run) because a
staged render does not yield until it finishes; off-thread the main
thread stays free and that stall disappears.

Read these as an upper bound, not a speedup that generalizes. The work
moved off-thread is the validation render's CPU — bounded,
route-dependent, and free of IO — so it does not grow with the main
render's cost. In an app whose main render is dominated by IO the same
absolute saving is a small fraction of the request, and it only appears
when a navigation lands in the brief validation window; at ordinary
click speed it is largely invisible. This is a dev-only responsiveness
improvement whose benefit varies widely with the app and the navigation
pattern.

As a follow-up, the validation work that still runs on the main thread
could move to the worker as well. When the main render can't be reused
for validation (for example after a cache miss), the validation
re-renders on the main thread to produce its inputs, resuming from the
Resume Data Cache (RDC) the main render already filled, and only then
hands the resulting Flight chunks to the worker. A later iteration could
run those renders inside the worker too, transporting the (serializable)
RDC so the worker can resume from the filled caches rather than reading
them back on the main thread.

closes NAR-895
2026-07-25 07:21:03 +02:00

624 lines
29 KiB
TypeScript

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'
)
// Wait for validation to finish before snapshotting. Unlike the other
// client-error cases, this page also throws a client error during the
// render itself, which opens the redbox before validation reports its
// own errors. Without waiting, the snapshot can race the
// (asynchronously delivered) validation errors and capture only the
// render error.
await waitForValidation(await browser.url(), getCliOutputSinceMark)
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)
}
})
})
}