This method may not be the best way for NextJS v12+ since new API is provided.
Before we start building any component for the transition, let's briefly talk about how NextJS renders pages.
First, let's take a look at _app.js
:
export default function MyApp({ Component, pageProps }) {
return (
<Component {...pageProps} />
);
}
The "_app.js" is the entry point for NextJS to start render page. When you navigation to a different page, the page component pass to MyApp as Component
.
Therefore, in order to make a transition effect, we need to prevent
NextJS from rendering the new page before transition effect is done.
Now, let's create the layout component with some navigation links:
export default function TransitionLayout({ children }) {
return (
<div>
<nav>
<Link href="/">Home</Link>
<Link href="/about">About</Link>
</nav>
<div>
{children}
</div>
</div>
);
}
And add to _app.js
function MyApp({ Component, pageProps }) {
return (
<TransitionLayout>
<Component {...pageProps} />
</TransitionLayout>
);
}
Now, let's start working on the TransitionLayout
First, we need to prevent the rendering the new page
We can add a state to hold the current children, and only render the displayChildren
.
We use children
as the default value for displayChildren
.
export default function TransitionLayout({ children }) {
const [displayChildren, setDisplayChildren] = useState(children);
return (
<div>
...
<div>
{displayChildren}
</div>
</div>
);
}
Now, if you click the link, the content of the page will not change.
Next, we add css and transition stage
.content {
opacity: 0;
background-color: cornflowerblue;
transition: 1s;
}
.fadeIn {
opacity: 1;
}
export default function TransitionLayout({ children }) {
const [displayChildren, setDisplayChildren] = useState(children);
const [transitionStage, setTransitionStage] = useState("fadeOut");
...
return (
<div>
...
<div
className={`${styles.content} ${styles[transitionStage]}`}
>
{displayChildren}
</div>
</div>
);
}
Now, the component will by default at 'fadeOut' stage, and we want to let it enter the 'fadeIn' stage at first time render, so let's add:
useEffect(() => {
setTransitionStage("fadeIn");
}, []);
Next, we want the component to enter 'fadeOut' when new children is received.
useEffect(() => {
if (children !== displayChildren) setTransitionStage("fadeOut");
}, [children, setDisplayChildren, displayChildren]);
And, render new children when 'fadeOut'is done, then re-enter 'fadeIn' stage.
...
return(
...
<div
onTransitionEnd={() => {
if (transitionStage === "fadeOut") {
console.log("fading out");
setDisplayChildren(children);
setTransitionStage("fadeIn");
}
}}
className={`${styles.content} ${styles[transitionStage]}`}
>
{displayChildren}
</div>
)
And, here is the demo and the completed code for the layout component:
Node: The demo will take sometime for CodeSandbox to start.
import Link from "next/link";
import { useState, memo, useEffect } from "react";
import styles from "./Layout.module.css";
export default function TransitionLayout({ children }) {
const [displayChildren, setDisplayChildren] = useState(children);
const [transitionStage, setTransitionStage] = useState("fadeOut");
useEffect(() => {
setTransitionStage("fadeIn");
}, []);
useEffect(() => {
if (children !== displayChildren) setTransitionStage("fadeOut");
}, [children, setDisplayChildren, displayChildren]);
return (
<div>
<nav>
<Link href="/">Home</Link>
<Link href="/about">About</Link>
</nav>
<div
onTransitionEnd={() => {
if (transitionStage === "fadeOut") {
console.log("fading out");
setDisplayChildren(children);
setTransitionStage("fadeIn");
}
}}
className={`${styles.content} ${styles[transitionStage]}`}
>
{displayChildren}
</div>
</div>
);
}
Thank you all!!
Top comments (15)
Very cool idea! I am trying to implement it in one of the websites. However, one problem is that no matter how much you scroll in the page, the fade out transition will occur at the top of the page when changing routes. Do you think there is a way to fade out from the current scroll position?
I have updated my codesandbox example.
You can set a height for the div that contains the content from different page, and set the overflow to auto.
Hope this will help your problem.
Thanks
Thanks for replying. I will try to take a look.
Cool idea, there are just two issues that could case problems. One is if you click the same link as the page still transitions. So if your on the home page and you click the home link it will fade out and fade back in. This is because comparing the children object always returns false as the reference is always different. One way to fix this is to compare the children.type.name instead which is the name of the component. The other issue was that the useEffect would run twice. This is also fixed by comparing the function name.
Great suggestion! Thanks for pointing that out.
That was lovely to end up here AnxinYan!
I do have a question, is it possible to have a "crossfade" solution in NextJS? Having both "current view" and "next view" accessible at the same time?
I have tested barba.js and highway.js - and they are not made compatible with nextjs. And Highway.js have also declared that they not intend to make it nextjs-compatible. Both these lovely transition-tools are out of scope for using with Route-changes in NextJS.
I have tested Framer Motion - but only as a way to enable this "hold on to the currrent view and transition it out" - but what I want is to trigger the GSAP instead of Framer Motion ;)
I can not do better explaining - since I am not a react-star.
Am working on something similar to this (using - and GSAP.... so I should really ditch Framer Animation for this solution here)...
BUT.
If some of you experience some problems, then try to use the useLayoutEffect instead of useEffect.
This since the useLayoutEffect hooks into before the very first paint to the browser, which useEffect isnt.
I also noticed something strange when compiled with SSR. between pages you get a flash of unstyled content. when you are running the dev server this is not visible
I ran into this issue as well but from a different direction. I was working on page transitions using React Transition Group and the
<SwitchTransition>
component, with GSAP to create page transitions and had this problem of the styles. As you mention everything works great on development but in production the problems start.The only solution I found so far is not use modules as the styling approach and have a single CSS file, which is not ideal.
I made this repo:
github.com/rhernandog/next-gsap-tr...
Here is live:
cranky-heyrovsky-83f0a9.netlify.app/
looks like the stylesheets get unloaded right in between the transitions..
Having same issue !
Having the same issue. anyone have a solution?
I have not running into this issue so far. Do you mind share how you compiled your files?
Really cool!
Could I make some code from this idea?
Great article!! Small typo in 2nd paragraph "When you navigation to a different page"