diff --git a/packages/next/src/build/index.ts b/packages/next/src/build/index.ts index 29f4cc928e5aef..cc6a6b18f39923 100644 --- a/packages/next/src/build/index.ts +++ b/packages/next/src/build/index.ts @@ -2541,11 +2541,14 @@ export default async function build( const updatedRelativeDest = path .join('pages', '404.html') .replace(/\\/g, '/') - await promises.copyFile( - orig, - path.join(distDir, 'server', updatedRelativeDest) - ) - pagesManifest['/404'] = updatedRelativeDest + + if (await fileExists(orig)) { + await promises.copyFile( + orig, + path.join(distDir, 'server', updatedRelativeDest) + ) + pagesManifest['/404'] = updatedRelativeDest + } }) } diff --git a/packages/next/src/client/components/app-router.tsx b/packages/next/src/client/components/app-router.tsx index ca0269393e17c1..e2c4acceeac378 100644 --- a/packages/next/src/client/components/app-router.tsx +++ b/packages/next/src/client/components/app-router.tsx @@ -75,7 +75,7 @@ type AppRouterProps = Omit< assetPrefix: string // Top level boundaries props notFound: React.ReactNode | undefined - notFoundStyles: React.ReactNode | undefined + notFoundStyles?: React.ReactNode | undefined asNotFound?: boolean } diff --git a/packages/next/src/server/app-render/app-render.tsx b/packages/next/src/server/app-render/app-render.tsx index 56882423bb9076..bd69be4fd12c7d 100644 --- a/packages/next/src/server/app-render/app-render.tsx +++ b/packages/next/src/server/app-render/app-render.tsx @@ -459,6 +459,89 @@ export async function renderToHTMLOrFlight( return [Comp, styles] } + const createStaticAssets = async ({ + layoutOrPagePath, + injectedCSS: injectedCSSWithCurrentLayout, + injectedFontPreloadTags: injectedFontPreloadTagsWithCurrentLayout, + }: { + layoutOrPagePath: string | undefined + injectedCSS: Set + injectedFontPreloadTags: Set + }) => { + const stylesheets: string[] = layoutOrPagePath + ? getCssInlinedLinkTags( + clientReferenceManifest, + serverCSSManifest!, + layoutOrPagePath, + serverCSSForEntries, + injectedCSSWithCurrentLayout, + true + ) + : [] + + const preloadedFontFiles = layoutOrPagePath + ? getPreloadedFontFilesInlineLinkTags( + serverCSSManifest!, + nextFontManifest, + serverCSSForEntries, + layoutOrPagePath, + injectedFontPreloadTagsWithCurrentLayout + ) + : [] + + return ( + <> + {preloadedFontFiles?.length === 0 ? ( + + ) : null} + {preloadedFontFiles + ? preloadedFontFiles.map((fontFile) => { + const ext = /\.(woff|woff2|eot|ttf|otf)$/.exec(fontFile)![1] + return ( + + ) + }) + : null} + {stylesheets + ? stylesheets.map((href, index) => ( + + )) + : null} + + ) + } + /** * Use the provided loader tree to create the React Component tree. */ @@ -498,29 +581,15 @@ export async function renderToHTMLOrFlight( const layoutOrPagePath = layout?.[1] || page?.[1] const injectedCSSWithCurrentLayout = new Set(injectedCSS) - const stylesheets: string[] = layoutOrPagePath - ? getCssInlinedLinkTags( - clientReferenceManifest, - serverCSSManifest!, - layoutOrPagePath, - serverCSSForEntries, - injectedCSSWithCurrentLayout, - true - ) - : [] - const injectedFontPreloadTagsWithCurrentLayout = new Set( injectedFontPreloadTags ) - const preloadedFontFiles = layoutOrPagePath - ? getPreloadedFontFilesInlineLinkTags( - serverCSSManifest!, - nextFontManifest, - serverCSSForEntries, - layoutOrPagePath, - injectedFontPreloadTagsWithCurrentLayout - ) - : [] + + const assets = createStaticAssets({ + layoutOrPagePath, + injectedCSS: injectedCSSWithCurrentLayout, + injectedFontPreloadTags: injectedFontPreloadTagsWithCurrentLayout, + }) const [Template, templateStyles] = template ? await createComponentAndStyles({ @@ -855,53 +924,7 @@ export async function renderToHTMLOrFlight( ) : ( )} - {preloadedFontFiles?.length === 0 ? ( - - ) : null} - {preloadedFontFiles - ? preloadedFontFiles.map((fontFile) => { - const ext = /\.(woff|woff2|eot|ttf|otf)$/.exec(fontFile)![1] - return ( - - ) - }) - : null} - {stylesheets - ? stylesheets.map((href, index) => ( - - )) - : null} + {assets} ) }, @@ -1201,13 +1224,15 @@ export async function renderToHTMLOrFlight( async (props) => { // Create full component tree from root to leaf. const injectedCSS = new Set() + const injectedFontPreloadTags = new Set() + const { Component: ComponentTree } = await createComponentTree({ createSegmentPath: (child) => child, loaderTree, parentParams: {}, firstItem: true, injectedCSS, - injectedFontPreloadTags: new Set(), + injectedFontPreloadTags, rootLayoutIncluded: false, asNotFound: props.asNotFound, }) @@ -1229,6 +1254,12 @@ export async function renderToHTMLOrFlight( ? [DefaultNotFound] : [] + const assets = createStaticAssets({ + layoutOrPagePath: layout?.[1], + injectedCSS, + injectedFontPreloadTags, + }) + const initialTree = createFlightRouterStateFromLoaderTree( loaderTree, getDynamicParamFromSegment, @@ -1255,12 +1286,15 @@ export async function renderToHTMLOrFlight( globalErrorComponent={GlobalError} notFound={ NotFound && RootLayout ? ( - - - + <> + {assets} + + {notFoundStyles} + + + ) : undefined } - notFoundStyles={notFoundStyles} asNotFound={props.asNotFound} > diff --git a/test/e2e/app-dir/app-css/app/not-found.js b/test/e2e/app-dir/app-css/app/not-found.js new file mode 100644 index 00000000000000..97dccf012279e7 --- /dev/null +++ b/test/e2e/app-dir/app-css/app/not-found.js @@ -0,0 +1,3 @@ +export default function RootNotFound() { + return

Root not found

+} diff --git a/test/e2e/app-dir/app-css/app/style.css b/test/e2e/app-dir/app-css/app/style.css index 3a94c07339f95a..e1eb536640378f 100644 --- a/test/e2e/app-dir/app-css/app/style.css +++ b/test/e2e/app-dir/app-css/app/style.css @@ -1,3 +1,7 @@ body { font-size: large; } + +.not-found { + color: rgb(210, 105, 30); /* chocolate */ +} diff --git a/test/e2e/app-dir/app-css/index.test.ts b/test/e2e/app-dir/app-css/index.test.ts index 77927f69e72f72..2d29b814b0caa0 100644 --- a/test/e2e/app-dir/app-css/index.test.ts +++ b/test/e2e/app-dir/app-css/index.test.ts @@ -194,6 +194,15 @@ createNextDescribe( ).toBe('rgb(255, 0, 0)') }) + it('should include root layout css for root not-found.js', async () => { + const browser = await next.browser('/this-path-does-not-exist') + expect( + await browser.eval( + `window.getComputedStyle(document.querySelector('h1')).color` + ) + ).toBe('rgb(210, 105, 30)') + }) + it('should include css imported in error.js', async () => { const browser = await next.browser('/error/client-component') await browser.elementByCss('button').click()