diff --git a/packages/next/build/webpack/loaders/next-view-loader.ts b/packages/next/build/webpack/loaders/next-view-loader.ts index db07acd21f0d..4374b9ff3673 100644 --- a/packages/next/build/webpack/loaders/next-view-loader.ts +++ b/packages/next/build/webpack/loaders/next-view-loader.ts @@ -22,24 +22,34 @@ async function resolveLayoutPathsByPage({ }) { const layoutPaths = new Map() const parts = pagePath.split('/') - // if a new root is being created we shouldn't include `views/layout.js` - const shouldIncludeRootLayout = !parts.some( - (part) => part.startsWith('(') && part.endsWith(')') - ) - - for (let i = 1; i < parts.length; i++) { - if (i === 1 && !shouldIncludeRootLayout) continue + const isNewRootLayout = + parts[1]?.length > 2 && parts[1]?.startsWith('(') && parts[1]?.endsWith(')') + for (let i = parts.length; i >= 0; i--) { const pathWithoutSlashLayout = parts.slice(0, i).join('/') - const layoutPath = `${pathWithoutSlashLayout}/layout` - - const resolvedLayoutPath = await resolve(layoutPath) + if (!pathWithoutSlashLayout) { + continue + } + const layoutPath = `${pathWithoutSlashLayout}/layout` + let resolvedLayoutPath = await resolve(layoutPath) let urlPath = pathToUrlPath(pathWithoutSlashLayout) + // if we are in a new root views/(root) and a custom root layout was + // not provided or a root layout views/layout is not present, we use + // a default root layout to provide the html/body tags + const isCustomRootLayout = isNewRootLayout && i === 2 + + if ((isCustomRootLayout || i === 1) && !resolvedLayoutPath) { + resolvedLayoutPath = await resolve('next/dist/lib/views-layout') + } layoutPaths.set(urlPath, resolvedLayoutPath) - } + // if we're in a new root layout don't add the top-level view/layout + if (isCustomRootLayout) { + break + } + } return layoutPaths } diff --git a/packages/next/server/view-render.tsx b/packages/next/server/view-render.tsx index 8dfbe7ac1522..8952c5f493aa 100644 --- a/packages/next/server/view-render.tsx +++ b/packages/next/server/view-render.tsx @@ -19,7 +19,6 @@ import { import { FlushEffectsContext } from '../shared/lib/flush-effects' import { isDynamicRoute } from '../shared/lib/router/utils' import { tryGetPreviewData } from './api-utils/node' -import DefaultRootLayout from '../lib/views-layout' const ReactDOMServer = process.env.__NEXT_REACT_ROOT ? require('react-dom/server.browser') @@ -241,20 +240,8 @@ export async function renderToHTML( return mod }) - // we need to add the default root layout when - // not rendering a sub-tree (flightRouterPath) - // and a root layout isn't already present views/layout.js or - // views/(new-root)/layout.js - let hasRootLayout = componentPaths.some( - (path) => path === '/' || path.match(/\/\(.*?\)$/) - ) const isSubtreeRender = components.length < componentPaths.length - if (!hasRootLayout && !isSubtreeRender) { - components.unshift({ Component: DefaultRootLayout }) - hasRootLayout = true - } - // Reads of this are cached on the `req` object, so this should resolve // instantly. There's no need to pass this data down from a previous // invoke, where we'd have to consider server & serverless. @@ -340,7 +327,7 @@ export async function renderToHTML( } // if this is the root layout pass children as bodyChildren prop - if (hasRootLayout && i === 0) { + if (!isSubtreeRender && i === 0) { return React.createElement(layout.Component, { ...props, headChildren: props.headChildren, @@ -369,7 +356,7 @@ export async function renderToHTML( // } } - const headChildren = hasRootLayout + const headChildren = !isSubtreeRender ? buildManifest.rootMainFiles.map((src) => (