This repository servers as a minimal reproduction of an error that occurs in Next that occurs around loading stylesheets with next/dyanmic
.
The bug occurs under the following conditions on a production build (using the pages router)
- User loads a page that contains a stylesheet which is loaded with a
data-n-p
tag because it is included for a component that was server side rendered. The style shows up correctly. - User clicks on a
Link
component fromnext/link
which will use client-side navigation to load a second page. - The second page includes a component which is rendered via
next/dynamic
which also has a dependency on the stylesheet that is loaded from the first page. - The lazy component on the second page is missing the styles from the stylesheet
$ npm run build && npm run start
- Go to http://localhost:3000. Observe that the main component has
border
andbackground-color
styles.
- Click the
To other page
link to client-side navigate to http://localhost:3000/client-side-navigation. Observe that the styles on the page are no longer applied. The component on this page is a lazy version of the one on the previous page, so it should have the styles associated with its CSS Module applied to it.
- Navigate to http://localhost:3000/client-side-navigation directly, observe that the styles are showing up now.
The index page includes a component called StyledBox
, which has a dependency on a CSS Module. The client-side-navigation page has a dependency on StyledBoxLazy
, which is StyledBox
wrapped within next/dyanmic
.
The stylesheet is initially included when loading the server-side rendered version of the index page. Because the StyledBox component is not using next/dyanmic
on the index page, the stylesheet link tag has a data-n-p
attribute.
When the user navigates to client-side-navigation via clicking the Link
component, the client-side-navigation page renders StyledBoxLazy
, which attempts to load the CSS chunk associated with StyledBoxLazy
. In the webpack runtime chunk, mini-css-extract-plugin
looks up the file path via the chunk/module id and first checks to see if there is a link
tag with a rel=stylesheet
and an href
that matches the filepath for the stylesheet it needs to load. In this case it finds the stylesheet that was loaded via SSR on the initial index pageload, and does not add an additional link tag for that stylesheet (because one already exists in the document). This happens in this section of the plugin https://github.com/webpack-contrib/mini-css-extract-plugin/blob/master/src/index.js#L924.
After the render phase for client-side-navigation finishes, the onHeadCommit
method runs in a useLayoutEffect
which cleans up all elements that match the link[data-n-p]
selector here https://github.com/vercel/next.js/blob/9de7705c9919aae57b7e79794bf0c9c9e67636e0/packages/next/src/client/index.tsx#L760. This removes the stylesheet element that mini-css-extract
had just assumed was going to be in the document, which results in the component that uses that stylesheet to be unstyled when the client-side navigation completes.
mini-css-extract-plugin
could be patched to not look forlink
tags with thedata-n-p
tag. This means thatmini-css-extract-plugin
would include anotherlink
tag, which would not be cleaned up viaonHeadCommit
when client-side navigation completes.mini-css-extract-plugin
could be patched to remove thedata-n-p
tag for any stylesheets that it looks up, which would stop the stylesheet from being removed byonHeadCommit
when client-side navigation completes.