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
This commit is contained in:
Jiachi Liu 2023-04-06 13:37:55 +02:00 committed by GitHub
parent 9b0af04649
commit e4e5c1674a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 131 additions and 78 deletions

View file

@ -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
}
})
}

View file

@ -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
}

View file

@ -459,6 +459,89 @@ export async function renderToHTMLOrFlight(
return [Comp, styles]
}
const createStaticAssets = async ({
layoutOrPagePath,
injectedCSS: injectedCSSWithCurrentLayout,
injectedFontPreloadTags: injectedFontPreloadTagsWithCurrentLayout,
}: {
layoutOrPagePath: string | undefined
injectedCSS: Set<string>
injectedFontPreloadTags: Set<string>
}) => {
const stylesheets: string[] = layoutOrPagePath
? getCssInlinedLinkTags(
clientReferenceManifest,
serverCSSManifest!,
layoutOrPagePath,
serverCSSForEntries,
injectedCSSWithCurrentLayout,
true
)
: []
const preloadedFontFiles = layoutOrPagePath
? getPreloadedFontFilesInlineLinkTags(
serverCSSManifest!,
nextFontManifest,
serverCSSForEntries,
layoutOrPagePath,
injectedFontPreloadTagsWithCurrentLayout
)
: []
return (
<>
{preloadedFontFiles?.length === 0 ? (
<link
data-next-font={
nextFontManifest?.appUsingSizeAdjust ? 'size-adjust' : ''
}
rel="preconnect"
href="/"
crossOrigin="anonymous"
/>
) : null}
{preloadedFontFiles
? preloadedFontFiles.map((fontFile) => {
const ext = /\.(woff|woff2|eot|ttf|otf)$/.exec(fontFile)![1]
return (
<link
key={fontFile}
rel="preload"
href={`${assetPrefix}/_next/${fontFile}`}
as="font"
type={`font/${ext}`}
crossOrigin="anonymous"
data-next-font={
fontFile.includes('-s') ? 'size-adjust' : ''
}
/>
)
})
: null}
{stylesheets
? stylesheets.map((href, index) => (
<link
rel="stylesheet"
// In dev, Safari will wrongly cache the resource if you preload it:
// - https://github.com/vercel/next.js/issues/5860
// - https://bugs.webkit.org/show_bug.cgi?id=187726
// We used to add a `?ts=` query for resources in `pages` to bypass it,
// but in this case it is fine as we don't need to preload the styles.
href={`${assetPrefix}/_next/${href}`}
// `Precedence` is an opt-in signal for React to handle
// resource loading and deduplication, etc:
// https://github.com/facebook/react/pull/25060
// @ts-ignore
precedence="next.js"
key={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(
) : (
<Component {...props} />
)}
{preloadedFontFiles?.length === 0 ? (
<link
data-next-font={
nextFontManifest?.appUsingSizeAdjust ? 'size-adjust' : ''
}
rel="preconnect"
href="/"
crossOrigin="anonymous"
/>
) : null}
{preloadedFontFiles
? preloadedFontFiles.map((fontFile) => {
const ext = /\.(woff|woff2|eot|ttf|otf)$/.exec(fontFile)![1]
return (
<link
key={fontFile}
rel="preload"
href={`${assetPrefix}/_next/${fontFile}`}
as="font"
type={`font/${ext}`}
crossOrigin="anonymous"
data-next-font={
fontFile.includes('-s') ? 'size-adjust' : ''
}
/>
)
})
: null}
{stylesheets
? stylesheets.map((href, index) => (
<link
rel="stylesheet"
// In dev, Safari will wrongly cache the resource if you preload it:
// - https://github.com/vercel/next.js/issues/5860
// - https://bugs.webkit.org/show_bug.cgi?id=187726
// We used to add a `?ts=` query for resources in `pages` to bypass it,
// but in this case it is fine as we don't need to preload the styles.
href={`${assetPrefix}/_next/${href}`}
// `Precedence` is an opt-in signal for React to handle
// resource loading and deduplication, etc:
// https://github.com/facebook/react/pull/25060
// @ts-ignore
precedence="next.js"
key={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<string>()
const injectedFontPreloadTags = new Set<string>()
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 ? (
<RootLayout params={{}}>
<NotFound />
</RootLayout>
<>
{assets}
<RootLayout params={{}}>
{notFoundStyles}
<NotFound />
</RootLayout>
</>
) : undefined
}
notFoundStyles={notFoundStyles}
asNotFound={props.asNotFound}
>
<ComponentTree />

View file

@ -0,0 +1,3 @@
export default function RootNotFound() {
return <h1 className="not-found">Root not found</h1>
}

View file

@ -1,3 +1,7 @@
body {
font-size: large;
}
.not-found {
color: rgb(210, 105, 30); /* chocolate */
}

View file

@ -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()