From c8c15e017eb0a39d3b335cb3bc49c2e15ae5a22e Mon Sep 17 00:00:00 2001 From: Josh Story Date: Sun, 22 Sep 2024 18:59:50 -0700 Subject: [PATCH] Update root not found boundary to not introduce Server Component (#70328) The server component is going to be updated to be passed to a client component in a future update which is not possible. This change relocates where the root not found boundary is added to avoid creating an intermediate server component when the layout is a client component --- .../app-render/create-component-tree.tsx | 118 +++++++++--------- 1 file changed, 60 insertions(+), 58 deletions(-) diff --git a/packages/next/src/server/app-render/create-component-tree.tsx b/packages/next/src/server/app-render/create-component-tree.tsx index 9c13877cc0974..2fe7dc2092a14 100644 --- a/packages/next/src/server/app-render/create-component-tree.tsx +++ b/packages/next/src/server/app-render/create-component-tree.tsx @@ -276,52 +276,13 @@ async function createComponentTreeInternal({ /** * The React Component to render. */ - let Component = LayoutOrPage - const parallelKeys = Object.keys(parallelRoutes) - const hasSlotKey = parallelKeys.length > 1 - - // TODO-APP: This is a hack to support unmatched parallel routes, which will throw `notFound()`. - // This ensures that a `NotFoundBoundary` is available for when that happens, - // but it's not ideal, as it needlessly invokes the `NotFound` component and renders the `RootLayout` twice. - // We should instead look into handling the fallback behavior differently in development mode so that it doesn't - // rely on the `NotFound` behavior. - if (hasSlotKey && rootLayoutAtThisLevel && LayoutOrPage) { - Component = (componentProps: { params: Params }) => { - const NotFoundComponent = NotFound - const RootLayoutComponent = LayoutOrPage - return ( - - {layerAssets} - {/* - * We are intentionally only forwarding params to the root layout, as passing any of the parallel route props - * might trigger `notFound()`, which is not currently supported in the root layout. - */} - - {notFoundStyles} - - - - ) : undefined - } - > - - - ) - } - } + let MaybeComponent = LayoutOrPage if (process.env.NODE_ENV === 'development') { const { isValidElementType } = require('next/dist/compiled/react-is') if ( - (isPage || typeof Component !== 'undefined') && - !isValidElementType(Component) + (isPage || typeof MaybeComponent !== 'undefined') && + !isValidElementType(MaybeComponent) ) { errorMissingDefaultExport(pagePath, 'page') } @@ -387,7 +348,7 @@ async function createComponentTreeInternal({ // prefetch everything up to the first route segment that defines a // loading.tsx boundary. (We do the same if there's no loading // boundary in the entire tree, because we don't want to prefetch too - // much) The rest of the tree is defered until the actual navigation. + // much) The rest of the tree is deferred until the actual navigation. // It does not take into account whether the data is dynamic — even if // the tree is completely static, it will still defer everything // inside the loading boundary. @@ -496,7 +457,7 @@ async function createComponentTreeInternal({ : null // When the segment does not have a layout or page we still have to add the layout router to ensure the path holds the loading component - if (!Component) { + if (!MaybeComponent) { return [ actualSegment, 1 + + let serverSegment = + + let segmentNode: React.ReactNode + if (isRootLayoutWithChildrenSlotAndAtLeastOneMoreSlot) { + // TODO-APP: This is a hack to support unmatched parallel routes, which will throw `notFound()`. + // This ensures that a `NotFoundBoundary` is available for when that happens, + // but it's not ideal, as it needlessly invokes the `NotFound` component and renders the `RootLayout` twice. + // We should instead look into handling the fallback behavior differently in development mode so that it doesn't + // rely on the `NotFound` behavior. + segmentNode = ( + + + {layerAssets} + + {notFoundStyles} + + + + ) : undefined + } + > + {layerAssets} + {serverSegment} + + + ) + } else { + segmentNode = ( + + {layerAssets} + {serverSegment} + + ) + } + // For layouts we just render the component return [ actualSegment, - // It is critical that this tree render something other than `null` because the client router uses - // null to represent an lazy hole. The current implementation satisfies this because the two inner slots - // ensure there is a fragment even if both slots render null. If we ever refactor this to only render the component - // or similar we need to ensure there is a fragment. Long term we should move to using a Symbol to communicate - // a lazy hole rather than null - - {layerAssets} - - , + segmentNode, parallelRouteCacheNodeSeedData, loadingData, ]