diff --git a/packages/next/src/server/app-render/app-render.tsx b/packages/next/src/server/app-render/app-render.tsx index 6725d7ba7217e..bc4ac7723f44d 100644 --- a/packages/next/src/server/app-render/app-render.tsx +++ b/packages/next/src/server/app-render/app-render.tsx @@ -716,7 +716,8 @@ async function generateDynamicFlightRenderResultWithStagesInDev( req: BaseNextRequest, ctx: AppRenderContext, initialRequestStore: RequestStore, - createRequestStore: (() => RequestStore) | undefined + createRequestStore: (() => RequestStore) | undefined, + tree: LoaderTree ): Promise { const { htmlRequestId, @@ -746,6 +747,8 @@ async function generateDynamicFlightRenderResultWithStagesInDev( onFlightDataRenderError ) + const [resolveValidation, validationOutlet] = createValidationOutlet() + const getPayload = async (requestStore: RequestStore) => { const payload: RSCPayload & RSCPayloadDevProperties = await workUnitAsyncStorage.run( @@ -763,6 +766,8 @@ async function generateDynamicFlightRenderResultWithStagesInDev( }) } + payload._validation = validationOutlet + return payload } @@ -822,6 +827,24 @@ async function generateDynamicFlightRenderResultWithStagesInDev( setReactDebugChannel(debugChannel.clientSide, htmlRequestId, requestId) } + const devValidatingFallbackParams = + getRequestMeta(req, 'devValidatingFallbackParams') || null + + // TODO(restart-on-cache-miss): + // This can probably be optimized to do less work, + // because we've already made sure that we have warm caches. + consoleAsyncStorage.run( + { dim: true }, + spawnDynamicValidationInDev, + resolveValidation, + tree, + ctx, + false, + ctx.clientReferenceManifest, + initialRequestStore, + devValidatingFallbackParams + ) + return new FlightRenderResult(stream, { fetchMetrics: workStore.fetchMetrics, }) @@ -2016,7 +2039,8 @@ async function renderToHTMLOrFlightImpl( req, ctx, requestStore, - createRequestStore + createRequestStore, + loaderTree ) } else { return generateDynamicFlightRenderResult(req, ctx, requestStore) diff --git a/test/development/app-dir/cache-components-dev-errors/cache-components-dev-errors.test.ts b/test/development/app-dir/cache-components-dev-errors/cache-components-dev-errors.test.ts index 0028e5acf0c50..335421dd1b401 100644 --- a/test/development/app-dir/cache-components-dev-errors/cache-components-dev-errors.test.ts +++ b/test/development/app-dir/cache-components-dev-errors/cache-components-dev-errors.test.ts @@ -1,11 +1,6 @@ import stripAnsi from 'strip-ansi' import { nextTestSetup } from 'e2e-utils' -import { - assertNoRedbox, - assertNoErrorToast, - hasErrorToast, - retry, -} from 'next-test-utils' +import { assertNoRedbox, hasErrorToast, retry } from 'next-test-utils' import { createSandbox } from 'development-sandbox' import { outdent } from 'outdent' @@ -37,7 +32,7 @@ describe('Cache Components Dev Errors', () => { `) }) - it('should not show a red box error on client navigations', async () => { + it('should show a red box error on client navigations', async () => { const browser = await next.browser('/no-error') await retry(async () => { @@ -45,10 +40,24 @@ describe('Cache Components Dev Errors', () => { }) await browser.elementByCss("[href='/error']").click() - await assertNoErrorToast(browser) + // TODO: React should not include the anon stack in the Owner Stack. + await expect(browser).toDisplayCollapsedRedbox(` + { + "description": "Route "/error" used \`Math.random()\` before accessing either uncached data (e.g. \`fetch()\`) or Request data (e.g. \`cookies()\`, \`headers()\`, \`connection()\`, and \`searchParams\`). Accessing random values synchronously in a Server Component requires reading one of these data sources first. Alternatively, consider moving this expression into a Client Component or Cache Component. See more info here: https://nextjs.org/docs/messages/next-prerender-random", + "environmentLabel": "Server", + "label": "Console Error", + "source": "app/error/page.tsx (2:23) @ Page + > 2 | const random = Math.random() + | ^", + "stack": [ + "Page app/error/page.tsx (2:23)", + "Page ", + "LogSafely ", + ], + } + `) await browser.loadPage(`${next.url}/error`) - // TODO: React should not include the anon stack in the Owner Stack. await expect(browser).toDisplayCollapsedRedbox(` {