Skip to content

Commit e4e5c16

Browse files
authored
Fix css applying for root not found (#47992)
### What This issue is introduced in #47688, we need to do the same work for rendering single component which collecting the assets and then render with root layout + root not found Fix #47970 Related #47862 (partially fix the css issue but not link issue) ### How This PR encapsulates the preload and stylesheets assets collection and rendering process, and move them into a helper, and share between the component rendering and the root not found rendering
1 parent 9b0af04 commit e4e5c16

File tree

6 files changed

+131
-78
lines changed

6 files changed

+131
-78
lines changed

packages/next/src/build/index.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2541,11 +2541,14 @@ export default async function build(
25412541
const updatedRelativeDest = path
25422542
.join('pages', '404.html')
25432543
.replace(/\\/g, '/')
2544-
await promises.copyFile(
2545-
orig,
2546-
path.join(distDir, 'server', updatedRelativeDest)
2547-
)
2548-
pagesManifest['/404'] = updatedRelativeDest
2544+
2545+
if (await fileExists(orig)) {
2546+
await promises.copyFile(
2547+
orig,
2548+
path.join(distDir, 'server', updatedRelativeDest)
2549+
)
2550+
pagesManifest['/404'] = updatedRelativeDest
2551+
}
25492552
})
25502553
}
25512554

packages/next/src/client/components/app-router.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ type AppRouterProps = Omit<
7575
assetPrefix: string
7676
// Top level boundaries props
7777
notFound: React.ReactNode | undefined
78-
notFoundStyles: React.ReactNode | undefined
78+
notFoundStyles?: React.ReactNode | undefined
7979
asNotFound?: boolean
8080
}
8181

packages/next/src/server/app-render/app-render.tsx

Lines changed: 106 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,89 @@ export async function renderToHTMLOrFlight(
459459
return [Comp, styles]
460460
}
461461

462+
const createStaticAssets = async ({
463+
layoutOrPagePath,
464+
injectedCSS: injectedCSSWithCurrentLayout,
465+
injectedFontPreloadTags: injectedFontPreloadTagsWithCurrentLayout,
466+
}: {
467+
layoutOrPagePath: string | undefined
468+
injectedCSS: Set<string>
469+
injectedFontPreloadTags: Set<string>
470+
}) => {
471+
const stylesheets: string[] = layoutOrPagePath
472+
? getCssInlinedLinkTags(
473+
clientReferenceManifest,
474+
serverCSSManifest!,
475+
layoutOrPagePath,
476+
serverCSSForEntries,
477+
injectedCSSWithCurrentLayout,
478+
true
479+
)
480+
: []
481+
482+
const preloadedFontFiles = layoutOrPagePath
483+
? getPreloadedFontFilesInlineLinkTags(
484+
serverCSSManifest!,
485+
nextFontManifest,
486+
serverCSSForEntries,
487+
layoutOrPagePath,
488+
injectedFontPreloadTagsWithCurrentLayout
489+
)
490+
: []
491+
492+
return (
493+
<>
494+
{preloadedFontFiles?.length === 0 ? (
495+
<link
496+
data-next-font={
497+
nextFontManifest?.appUsingSizeAdjust ? 'size-adjust' : ''
498+
}
499+
rel="preconnect"
500+
href="/"
501+
crossOrigin="anonymous"
502+
/>
503+
) : null}
504+
{preloadedFontFiles
505+
? preloadedFontFiles.map((fontFile) => {
506+
const ext = /\.(woff|woff2|eot|ttf|otf)$/.exec(fontFile)![1]
507+
return (
508+
<link
509+
key={fontFile}
510+
rel="preload"
511+
href={`${assetPrefix}/_next/${fontFile}`}
512+
as="font"
513+
type={`font/${ext}`}
514+
crossOrigin="anonymous"
515+
data-next-font={
516+
fontFile.includes('-s') ? 'size-adjust' : ''
517+
}
518+
/>
519+
)
520+
})
521+
: null}
522+
{stylesheets
523+
? stylesheets.map((href, index) => (
524+
<link
525+
rel="stylesheet"
526+
// In dev, Safari will wrongly cache the resource if you preload it:
527+
// - https://github.com/vercel/next.js/issues/5860
528+
// - https://bugs.webkit.org/show_bug.cgi?id=187726
529+
// We used to add a `?ts=` query for resources in `pages` to bypass it,
530+
// but in this case it is fine as we don't need to preload the styles.
531+
href={`${assetPrefix}/_next/${href}`}
532+
// `Precedence` is an opt-in signal for React to handle
533+
// resource loading and deduplication, etc:
534+
// https://github.com/facebook/react/pull/25060
535+
// @ts-ignore
536+
precedence="next.js"
537+
key={index}
538+
/>
539+
))
540+
: null}
541+
</>
542+
)
543+
}
544+
462545
/**
463546
* Use the provided loader tree to create the React Component tree.
464547
*/
@@ -498,29 +581,15 @@ export async function renderToHTMLOrFlight(
498581
const layoutOrPagePath = layout?.[1] || page?.[1]
499582

500583
const injectedCSSWithCurrentLayout = new Set(injectedCSS)
501-
const stylesheets: string[] = layoutOrPagePath
502-
? getCssInlinedLinkTags(
503-
clientReferenceManifest,
504-
serverCSSManifest!,
505-
layoutOrPagePath,
506-
serverCSSForEntries,
507-
injectedCSSWithCurrentLayout,
508-
true
509-
)
510-
: []
511-
512584
const injectedFontPreloadTagsWithCurrentLayout = new Set(
513585
injectedFontPreloadTags
514586
)
515-
const preloadedFontFiles = layoutOrPagePath
516-
? getPreloadedFontFilesInlineLinkTags(
517-
serverCSSManifest!,
518-
nextFontManifest,
519-
serverCSSForEntries,
520-
layoutOrPagePath,
521-
injectedFontPreloadTagsWithCurrentLayout
522-
)
523-
: []
587+
588+
const assets = createStaticAssets({
589+
layoutOrPagePath,
590+
injectedCSS: injectedCSSWithCurrentLayout,
591+
injectedFontPreloadTags: injectedFontPreloadTagsWithCurrentLayout,
592+
})
524593

525594
const [Template, templateStyles] = template
526595
? await createComponentAndStyles({
@@ -855,53 +924,7 @@ export async function renderToHTMLOrFlight(
855924
) : (
856925
<Component {...props} />
857926
)}
858-
{preloadedFontFiles?.length === 0 ? (
859-
<link
860-
data-next-font={
861-
nextFontManifest?.appUsingSizeAdjust ? 'size-adjust' : ''
862-
}
863-
rel="preconnect"
864-
href="/"
865-
crossOrigin="anonymous"
866-
/>
867-
) : null}
868-
{preloadedFontFiles
869-
? preloadedFontFiles.map((fontFile) => {
870-
const ext = /\.(woff|woff2|eot|ttf|otf)$/.exec(fontFile)![1]
871-
return (
872-
<link
873-
key={fontFile}
874-
rel="preload"
875-
href={`${assetPrefix}/_next/${fontFile}`}
876-
as="font"
877-
type={`font/${ext}`}
878-
crossOrigin="anonymous"
879-
data-next-font={
880-
fontFile.includes('-s') ? 'size-adjust' : ''
881-
}
882-
/>
883-
)
884-
})
885-
: null}
886-
{stylesheets
887-
? stylesheets.map((href, index) => (
888-
<link
889-
rel="stylesheet"
890-
// In dev, Safari will wrongly cache the resource if you preload it:
891-
// - https://github.com/vercel/next.js/issues/5860
892-
// - https://bugs.webkit.org/show_bug.cgi?id=187726
893-
// We used to add a `?ts=` query for resources in `pages` to bypass it,
894-
// but in this case it is fine as we don't need to preload the styles.
895-
href={`${assetPrefix}/_next/${href}`}
896-
// `Precedence` is an opt-in signal for React to handle
897-
// resource loading and deduplication, etc:
898-
// https://github.com/facebook/react/pull/25060
899-
// @ts-ignore
900-
precedence="next.js"
901-
key={index}
902-
/>
903-
))
904-
: null}
927+
{assets}
905928
</>
906929
)
907930
},
@@ -1201,13 +1224,15 @@ export async function renderToHTMLOrFlight(
12011224
async (props) => {
12021225
// Create full component tree from root to leaf.
12031226
const injectedCSS = new Set<string>()
1227+
const injectedFontPreloadTags = new Set<string>()
1228+
12041229
const { Component: ComponentTree } = await createComponentTree({
12051230
createSegmentPath: (child) => child,
12061231
loaderTree,
12071232
parentParams: {},
12081233
firstItem: true,
12091234
injectedCSS,
1210-
injectedFontPreloadTags: new Set(),
1235+
injectedFontPreloadTags,
12111236
rootLayoutIncluded: false,
12121237
asNotFound: props.asNotFound,
12131238
})
@@ -1229,6 +1254,12 @@ export async function renderToHTMLOrFlight(
12291254
? [DefaultNotFound]
12301255
: []
12311256

1257+
const assets = createStaticAssets({
1258+
layoutOrPagePath: layout?.[1],
1259+
injectedCSS,
1260+
injectedFontPreloadTags,
1261+
})
1262+
12321263
const initialTree = createFlightRouterStateFromLoaderTree(
12331264
loaderTree,
12341265
getDynamicParamFromSegment,
@@ -1255,12 +1286,15 @@ export async function renderToHTMLOrFlight(
12551286
globalErrorComponent={GlobalError}
12561287
notFound={
12571288
NotFound && RootLayout ? (
1258-
<RootLayout params={{}}>
1259-
<NotFound />
1260-
</RootLayout>
1289+
<>
1290+
{assets}
1291+
<RootLayout params={{}}>
1292+
{notFoundStyles}
1293+
<NotFound />
1294+
</RootLayout>
1295+
</>
12611296
) : undefined
12621297
}
1263-
notFoundStyles={notFoundStyles}
12641298
asNotFound={props.asNotFound}
12651299
>
12661300
<ComponentTree />
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default function RootNotFound() {
2+
return <h1 className="not-found">Root not found</h1>
3+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
body {
22
font-size: large;
33
}
4+
5+
.not-found {
6+
color: rgb(210, 105, 30); /* chocolate */
7+
}

test/e2e/app-dir/app-css/index.test.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,15 @@ createNextDescribe(
194194
).toBe('rgb(255, 0, 0)')
195195
})
196196

197+
it('should include root layout css for root not-found.js', async () => {
198+
const browser = await next.browser('/this-path-does-not-exist')
199+
expect(
200+
await browser.eval(
201+
`window.getComputedStyle(document.querySelector('h1')).color`
202+
)
203+
).toBe('rgb(210, 105, 30)')
204+
})
205+
197206
it('should include css imported in error.js', async () => {
198207
const browser = await next.browser('/error/client-component')
199208
await browser.elementByCss('button').click()

0 commit comments

Comments
 (0)