vercel / next.js

The React Framework
https://nextjs.org
MIT License
127.26k stars 27.02k forks source link

Stylesheet needed by next/dynamic component removed during client-side navigation via next/link #61111

Closed copperwall closed 7 months ago

copperwall commented 10 months ago

Link to the code that reproduces this issue

https://github.com/copperwall/mini-css-nextjs-repro

To Reproduce

  1. Start the application in production mode

  2. Navigate to http://localhost:3000, observe that the element with "Some text" has styling including background-color and a border.

    image
  3. Click on the "To other page" link below to navigate to http://localhost:3000/client_side_navigation using client-side navigation via next/link.

  4. Observe that the element with "Some text" no longer has any styling associated with it. This component is rendered using the same component from the index page, except wrapped in a next/dynamic call, so in this case we'd expect to see the same background-color and border styles as the index page.

    image
  5. If you reload http://localhost:3000/client_side_navigation you can observe the style it should have

    image

Current vs. Expected behavior

Following the steps from the previous section, I'd expect the /client_side_navigation page to have the same style applied as the / page, since they use the same component and share the same stylesheet.

However, the StyledBoxLazy on the /client_side_navigation page is unstyled when the user does a client-side navigation from / to /client_side_navigation.

Provide environment information

Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 23.2.0: Wed Nov 15 21:55:06 PST 2023; root:xnu-10002.61.3~2/RELEASE_ARM64_T6020
Binaries:
  Node: 18.19.0
  npm: 10.2.3
  Yarn: 1.22.21
  pnpm: 8.14.2
Relevant Packages:
  next: 14.1.1-canary.9 // Latest available version is detected (14.1.1-canary.9).
  eslint-config-next: N/A
  react: 18.2.0
  react-dom: 18.2.0
  typescript: 5.1.3
Next.js Config:
  output: N/A

Which area(s) are affected? (Select all that apply)

Dynamic imports (next/dynamic), Routing (next/router, next/navigation, next/link)

Which stage(s) are affected? (Select all that apply)

next dev (local), next build (local), next start (local), Other (Deployed)

Additional context

We're running Next via Docker (outside Vercel) but we've been able to reproduce this both in prod and locally. Happy to answer any questions or investigate possible workarounds! From the repro repo I added what seems to be happening and some potential ways to fix it, but I'll include them here too


Explanation

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.

Possible Fixes

  1. mini-css-extract-plugin could be patched to not look for link tags with the data-n-p tag. This means that mini-css-extract-plugin would include another link tag, which would not be cleaned up via onHeadCommit when client-side navigation completes.
  2. mini-css-extract-plugin could be patched to remove the data-n-p tag for any stylesheets that it looks up, which would stop the stylesheet from being removed by onHeadCommit when client-side navigation completes.

NEXT-2849

github-actions[bot] commented 7 months ago

This closed issue has been automatically locked because it had no new activity for 2 weeks. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.