Solution: CSS Styles are Removed Too Early on Page Transitions with Framer Motion
I’ve only started really digging into the heart of NextJS, but I came across an amazing animations library called Framer Motion that allows for easy-to-implement microinteractions and full page transitions that you write directly into your React components.
This is all fine and good, but with both of these being relatively new technologies, it’s not unusual to run into some bugs and quirks. It wasn’t long until I found a pretty serious challenge that needed to be hurdled.
Let me outline the issue: While using traditional CSS modules in my NextJS app, when clicking a link to another route, Framer Motion performs a transition animation. In production, on child components of the main pages, the styling from my CSS is thrown out during the animation, blinking an unstyled version of the previous page, and making the transition look ugly.
I came across a number of threads ruminating over the same issue, including a hot issue on the github repo for NextJS:
Clearly, this issue is much more complicated than I initially assumed, and has deeper roots in the way NextJS minifies and holds on to its CSS when switching routes. I kept an example of this non-working repo on the main branch:
And the deployment here:
next-css-demo-7d9bo5e0w-dirklo.vercel.app
Here’s a gif of the issue:
As you can see, changing routes does trigger the animation, but the positioning and background color of the <h1>
gets thrown out immediately when we click the link. Again, this looks fine in development, and only exhibits the issue when deployed in production.
So what’s really going on here? Without really digging into the NextJS code, that may be difficult to figure out quickly, however, we can localize the problem and come up with a couple of solutions. Here’s what I came up with.
Solution 1
First, this issue only seems to pop up when we are importing a second CSS module into a child component, separate from the CSS module of the page. If all of the CSS stays in the main module for the page, Next will hang on to it through the transition.
Here’s the branch with the first solution implemented:
And the deployment:
Well there we go, our pages are holding on to their CSS and transitioning correctly.
For this solution, I have the 2 pages, each with a child component and those 4 components (2 pages and 2 children), each reference the same singular CSS module in the styles folder: Home.module.css
This doesn’t feel like a great solution. The whole purpose of CSS modules is to be modular and scoped, and even though this works, we still need to write all of our CSS is one file. Let’s keep hunting for another solution shall we?
Solution 2
I came across another Stack Overflow thread with a solution, that I’ll outline here:
NextJS comes bundled with StyledJSX, that we can use to try this again.
And the deployment:
Alright, so we’ve set up 4 CSS files, giving us modularity, and it’s all being compiled through styled JSX, so our deployed app is working bug free! This solution does come with a couple of caveats though:
First, we have to write our CSS in a JS file, resolve and export the CSS:
// styles/ChildComponent.styles.jsimport css from 'styled-jsx/css'export default css.resolve`
.main {
padding: 5rem 0;
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
} h1 {
background-color: red;
font-size: 38px;
}
`
Notice we imported css
from styles-jsx/css
. This package is included, so no need to install it. We use the css.resolve
function with back-ticks to enclose our CSS. Now we import this file into our component:
// components/ChildComponent.js
import Image from 'next/image'
import styledJsx from '../styles/ChildComponent.styles'export default function ChildComponent() {
return (
<div>
<main className={`${styledJsx.className} main`}>
<h1 className={styledJsx.className}>
I AM THE FIRST PAGE
</h1>
<Image
src='/image1.jpg'
alt='Image 1'
layout='fixed'
width={4797/10}
height={3026/10}
/>
</main>
{styledJsx.styles}
</div>
)
}
The css.resolve
function will return an object with two properties:
className
— This generates a unique class name for the styles, giving us that modularity.styles
— The styles themselves that needs to be executed in the render method of the component.
The main problem I ran into using this method, is that your standard CSS nested selectors, i.e. .main h1 {...}
, don’t seem to work, so I just apply a class name to each DOM element I want to select. However, if you need to reference a DOM element that is generated by a component (like the <div>
generated by the <Image>
component), you can simply reference it with the className property from that object:
<Image
className={styledJsx.className}
src='/image1.jpg'
alt='Image 1'
layout='fixed'
width={4797/10}
height={3026/10}
/>
Then in your CSS, you can use the div {…}
selector to reach the image div.
Conclusion
I would love any feedback from anyone reading this if they have run into this problem previously and have any cleaner solutions. Thank you for reading!