diff --git a/packages/next/src/lib/metadata/metadata.tsx b/packages/next/src/lib/metadata/metadata.tsx index 3ac86f0f74db8..ec5c5f0935162 100644 --- a/packages/next/src/lib/metadata/metadata.tsx +++ b/packages/next/src/lib/metadata/metadata.tsx @@ -25,11 +25,13 @@ export async function MetadataTree({ pathname, searchParams, getDynamicParamFromSegment, + appUsingSizeAdjust, }: { tree: LoaderTree pathname: string searchParams: { [key: string]: any } getDynamicParamFromSegment: GetDynamicParamFromSegment + appUsingSizeAdjust: boolean }) { const metadataContext = { pathname, @@ -56,6 +58,8 @@ export async function MetadataTree({ IconsMetadata({ icons: metadata.icons }), ]) + if (appUsingSizeAdjust) elements.push() + return ( <> {elements.map((el, index) => { diff --git a/packages/next/src/server/app-render/app-render.tsx b/packages/next/src/server/app-render/app-render.tsx index 4bd3cf103e025..752eca8ec9e68 100644 --- a/packages/next/src/server/app-render/app-render.tsx +++ b/packages/next/src/server/app-render/app-render.tsx @@ -17,7 +17,10 @@ import type { RequestAsyncStorage } from '../../client/components/request-async- import React from 'react' import { NotFound as DefaultNotFound } from '../../client/components/error' -import { createServerComponentRenderer } from './create-server-components-renderer' +import { + createServerComponentRenderer, + ErrorHtml, +} from './create-server-components-renderer' import { ParsedUrlQuery } from 'querystring' import { NextParsedUrlQuery } from '../request-meta' @@ -195,7 +198,7 @@ export async function renderToHTMLOrFlight( serverActionsBodySizeLimit, } = renderOpts - const appUsingSizeAdjust = nextFontManifest?.appUsingSizeAdjust + const appUsingSizeAdjust = !!nextFontManifest?.appUsingSizeAdjust const clientReferenceManifest = renderOpts.clientReferenceManifest! @@ -1216,8 +1219,8 @@ export async function renderToHTMLOrFlight( pathname={pathname} searchParams={providedSearchParams} getDynamicParamFromSegment={getDynamicParamFromSegment} + appUsingSizeAdjust={appUsingSizeAdjust} /> - {appUsingSizeAdjust ? : null} ), injectedCSS: new Set(), @@ -1259,12 +1262,12 @@ export async function renderToHTMLOrFlight( /** GlobalError can be either the default error boundary or the overwritten app/global-error.js **/ ComponentMod.GlobalError as typeof import('../../client/components/error-boundary').GlobalError - let serverComponentsInlinedTransformStream: TransformStream< + const serverComponentsInlinedTransformStream: TransformStream< Uint8Array, Uint8Array > = new TransformStream() - let serverErrorComponentsInlinedTransformStream: TransformStream< + const serverErrorComponentsInlinedTransformStream: TransformStream< Uint8Array, Uint8Array > = new TransformStream() @@ -1367,6 +1370,7 @@ export async function renderToHTMLOrFlight( pathname={pathname} searchParams={providedSearchParams} getDynamicParamFromSegment={getDynamicParamFromSegment} + appUsingSizeAdjust={appUsingSizeAdjust} /> ) @@ -1384,22 +1388,15 @@ export async function renderToHTMLOrFlight( assetPrefix={assetPrefix} initialCanonicalUrl={pathname} initialTree={initialTree} - initialHead={ - <> - {createMetadata(loaderTree)} - {appUsingSizeAdjust ? : null} - - } + initialHead={<>{createMetadata(loaderTree)}} globalErrorComponent={GlobalError} notFound={ NotFound ? ( - - - {createMetadata(loaderTree)} - {notFoundStyles} - - - + + {createMetadata(loaderTree)} + {notFoundStyles} + + ) : undefined } asNotFound={props.asNotFound} @@ -1480,16 +1477,16 @@ export async function renderToHTMLOrFlight( let polyfillsFlushed = false let flushedErrorMetaTagsUntilIndex = 0 - const getServerInsertedHTML = () => { + const getServerInsertedHTML = (serverCapturedErrors: Error[]) => { // Loop through all the errors that have been captured but not yet // flushed. const errorMetaTags = [] for ( ; - flushedErrorMetaTagsUntilIndex < allCapturedErrors.length; + flushedErrorMetaTagsUntilIndex < serverCapturedErrors.length; flushedErrorMetaTagsUntilIndex++ ) { - const error = allCapturedErrors[flushedErrorMetaTagsUntilIndex] + const error = serverCapturedErrors[flushedErrorMetaTagsUntilIndex] if (isNotFoundError(error)) { errorMetaTags.push( @@ -1571,7 +1568,8 @@ export async function renderToHTMLOrFlight( dataStream: serverComponentsInlinedTransformStream.readable, generateStaticHTML: staticGenerationStore.isStaticGeneration || generateStaticHTML, - getServerInsertedHTML, + getServerInsertedHTML: () => + getServerInsertedHTML(allCapturedErrors), serverInsertedHTMLToHead: true, ...validateRootLayout, }) @@ -1610,23 +1608,6 @@ export async function renderToHTMLOrFlight( res.setHeader('Location', getURLFromRedirectError(err)) } - const defaultErrorComponent = ( - - - {/* @ts-expect-error allow to use async server component */} - - {appUsingSizeAdjust ? : null} - - - - ) - const use404Error = res.statusCode === 404 const useDefaultError = res.statusCode < 400 || res.statusCode === 307 @@ -1643,48 +1624,45 @@ export async function renderToHTMLOrFlight( ? interopDefault(await rootLayoutModule()) : null - const serverErrorElement = useDefaultError - ? defaultErrorComponent - : React.createElement( - createServerComponentRenderer( - async () => { - // only pass plain object to client - return ( - <> - {/* @ts-expect-error allow to use async server component */} - - {use404Error ? ( + const serverErrorElement = ( + + } + > + {useDefaultError + ? null + : React.createElement( + createServerComponentRenderer( + async () => { + return ( <> - - {notFoundStyles} - - + {use404Error ? ( + + {notFoundStyles} + + + + ) : undefined} - ) : ( - - )} - + ) + }, + ComponentMod, + serverErrorComponentsRenderOpts, + serverComponentsErrorHandler, + nonce ) - }, - ComponentMod, - serverErrorComponentsRenderOpts, - serverComponentsErrorHandler, - nonce - ) - ) + )} + + ) const renderStream = await renderToInitialStream({ ReactDOMServer: require('react-dom/server.edge'), @@ -1713,7 +1691,7 @@ export async function renderToHTMLOrFlight( : serverErrorComponentsInlinedTransformStream ).readable, generateStaticHTML: staticGenerationStore.isStaticGeneration, - getServerInsertedHTML, + getServerInsertedHTML: () => getServerInsertedHTML([]), serverInsertedHTMLToHead: true, ...validateRootLayout, }) diff --git a/packages/next/src/server/app-render/create-server-components-renderer.tsx b/packages/next/src/server/app-render/create-server-components-renderer.tsx index 0d11231ed110d..5fdc1008bf819 100644 --- a/packages/next/src/server/app-render/create-server-components-renderer.tsx +++ b/packages/next/src/server/app-render/create-server-components-renderer.tsx @@ -75,3 +75,18 @@ export function createServerComponentRenderer( return use(response) } } + +export function ErrorHtml({ + head, + children, +}: { + head?: React.ReactNode + children?: React.ReactNode +}) { + return ( + + {head} + {children} + + ) +} diff --git a/test/e2e/app-dir/global-error/app/client/page.js b/test/e2e/app-dir/global-error/basic/app/client/page.js similarity index 100% rename from test/e2e/app-dir/global-error/app/client/page.js rename to test/e2e/app-dir/global-error/basic/app/client/page.js diff --git a/test/e2e/app-dir/global-error/app/global-error.js b/test/e2e/app-dir/global-error/basic/app/global-error.js similarity index 100% rename from test/e2e/app-dir/global-error/app/global-error.js rename to test/e2e/app-dir/global-error/basic/app/global-error.js diff --git a/test/e2e/app-dir/global-error/app/layout.js b/test/e2e/app-dir/global-error/basic/app/layout.js similarity index 100% rename from test/e2e/app-dir/global-error/app/layout.js rename to test/e2e/app-dir/global-error/basic/app/layout.js diff --git a/test/e2e/app-dir/global-error/app/ssr/client/page.js b/test/e2e/app-dir/global-error/basic/app/ssr/client/page.js similarity index 100% rename from test/e2e/app-dir/global-error/app/ssr/client/page.js rename to test/e2e/app-dir/global-error/basic/app/ssr/client/page.js diff --git a/test/e2e/app-dir/global-error/app/ssr/server/page.js b/test/e2e/app-dir/global-error/basic/app/ssr/server/page.js similarity index 100% rename from test/e2e/app-dir/global-error/app/ssr/server/page.js rename to test/e2e/app-dir/global-error/basic/app/ssr/server/page.js diff --git a/test/e2e/app-dir/global-error/index.test.ts b/test/e2e/app-dir/global-error/basic/index.test.ts similarity index 100% rename from test/e2e/app-dir/global-error/index.test.ts rename to test/e2e/app-dir/global-error/basic/index.test.ts diff --git a/test/e2e/app-dir/global-error/layout-error/app/global-error.js b/test/e2e/app-dir/global-error/layout-error/app/global-error.js new file mode 100644 index 0000000000000..d960556d75cd5 --- /dev/null +++ b/test/e2e/app-dir/global-error/layout-error/app/global-error.js @@ -0,0 +1,14 @@ +'use client' + +export default function GlobalError({ error }) { + return ( + + + +

Global Error

+

{`Global error: ${error?.message}`}

+ {error?.digest &&

{error?.digest}

} + + + ) +} diff --git a/test/e2e/app-dir/global-error/layout-error/app/layout.js b/test/e2e/app-dir/global-error/layout-error/app/layout.js new file mode 100644 index 0000000000000..6fc4fecd68e10 --- /dev/null +++ b/test/e2e/app-dir/global-error/layout-error/app/layout.js @@ -0,0 +1,5 @@ +export default function layout() { + throw new Error('Global error: layout error') +} + +export const revalidate = 0 diff --git a/test/e2e/app-dir/global-error/layout-error/app/page.js b/test/e2e/app-dir/global-error/layout-error/app/page.js new file mode 100644 index 0000000000000..a5192c0ea9cfa --- /dev/null +++ b/test/e2e/app-dir/global-error/layout-error/app/page.js @@ -0,0 +1,3 @@ +export default function page() { + return
Page
+} diff --git a/test/e2e/app-dir/global-error/layout-error/index.test.ts b/test/e2e/app-dir/global-error/layout-error/index.test.ts new file mode 100644 index 0000000000000..707094fdc3176 --- /dev/null +++ b/test/e2e/app-dir/global-error/layout-error/index.test.ts @@ -0,0 +1,30 @@ +import { getRedboxHeader, hasRedbox } from 'next-test-utils' +import { createNextDescribe } from 'e2e-utils' + +async function testDev(browser, errorRegex) { + expect(await hasRedbox(browser, true)).toBe(true) + expect(await getRedboxHeader(browser)).toMatch(errorRegex) +} + +createNextDescribe( + 'app dir - global error - layout error', + { + files: __dirname, + skipDeployment: true, + }, + ({ next, isNextDev }) => { + it('should render global error for error in server components', async () => { + const browser = await next.browser('/') + + if (isNextDev) { + await testDev(browser, /Global error: layout error/) + } else { + expect(await browser.elementByCss('h1').text()).toBe('Global Error') + expect(await browser.elementByCss('#error').text()).toBe( + 'Global error: An error occurred in the Server Components render. The specific message is omitted in production builds to avoid leaking sensitive details. A digest property is included on this error instance which may provide additional details about the nature of the error.' + ) + expect(await browser.elementByCss('#digest').text()).toMatch(/\w+/) + } + }) + } +)