CSS module styling is removed too early on route changes
See original GitHub issueBug report
Describe the bug
CSS module styling is removed immediately after clicking a next/link
, instead of after the DOM is removed on production builds. This causes the components to have no styling at all during a page transition. This issue does not happen on dev mode.
I believe this is a bug with CSS modules specifically because components styled with styled-jsx
don’t have this problem.
Really would love to be able to use Sass via CSS modules here instead of re-writing the entire app I’m working on using styled-jsx
. If Sass modules can’t work in this scenario, I think I would be forced to use styled-jsx
, which is not my preferred method of styling my components for this project.
To Reproduce
I have created repos, and deployed these repos to demonstrate the problem using framer-motion for page transitions. If you were to pull these repos and run them locally using npm run dev
, you will see that the flash of unstyled content does not happen on any one of them in dev mode. However, on their deployed sites, you can see the flash of unstyled content with CSS modules and Sass modules.
styled-jsx
Behavior: correct, no flash of unstyled content Deployed site on Vercel Repo
CSS modules
Behavior: buggy, there is a flash of unstyled content immediately after clicking the link Deployed site on Vercel Repo
Sass via CSS modules (additional)
Behavior: buggy, there is a flash of unstyled content immediately after clicking the link (same as CSS modules) Deployed site on Vercel Repo
Expected behavior
Styling for components that come from CSS modules should not be removed immediately on route changes, and instead, are removed when the markup is removed (the component unmounts?). The expected behavior is the behavior we can see on the styled-jsx
deployment above.
System information
- OS: macOS
- Browser (if applies): N/A
- Version of Next.js: 9.5.3
- Version of Node.js: 12.14.1
Issue Analytics
- State:
- Created 3 years ago
- Reactions:221
- Comments:92 (7 by maintainers)
Top GitHub Comments
I’ve encounter this issue as well and tried @scriptify 's suggested woraround but—as other have mentioned—it didn’t seem to really help with the first navigation.
After looking deeper, I realized this is a hard problem to solve for the framework. I don’t really know how pre-v10 versions of Next.js worked in this regard since the project I’m working on started with the v10, but I can assume any page transition solution relied on a bug or an unoptimized behavior.
When the page changes you want to remove styles (and maybe other kind of resources) from the DOM to 1) prevent style clashes (in the case of global CSS) and to 2) prevent memory leaks as the CSSOM would otherwise increase on every page navigation.
Next.js handles this well.
The problem with page transitions arises from the fact that any solution (being it with Framer motion, ReactTransitionGroup, ReactFlipToolkit, etc) relies on holding to the previous render’s element tree (its
children
) until the animation has finished.Next.js cannot know when such animation finishes, instead, it has to unlink styles from the DOM as soon as the current (old) page
Component
is replaced by the next (new) pageComponent
.I don’t think here’s any “fix” Next.js can apply for this. The only solution I can think of is for Next.js to provide a new set of APIs to hook to and manipulate its resource management system on the client-side. Think of providing callbacks on the
_app
interface or making theRoute
events an async middleware that allows one to delay the next step in the route change process.tl;dr: a workaround
I’ve come up with a workaround that 1) seems to be working even for the first navigation and 2) cleans up the DOM after the page transition has finished.
There’s 2 variations depending on the animation system you use. Note you would use one or the other depending on your case, not both.
Spring-based
The first one is for spring-based animations which provide a completion callback. Place this hook anywhere you prefer:
Then you can use it in your
app
, e.g.:Timeout-based
The second one is for solutions that use fixed-duration transitions:
Which you would use outside of of your
app
component:So far I haven’t found issues with these. Hope it helps you all.
Hi, experiencing the very same issue as reported.