DEV Community

loading...

Page Transition Effect in NextJS

AnxinYang
A front-end developer.
Updated on ・2 min read

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} />
  );
}

Enter fullscreen mode Exit fullscreen mode

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>
  );
}

Enter fullscreen mode Exit fullscreen mode

And add to _app.js

function MyApp({ Component, pageProps }) {
  return (
    <TransitionLayout>
      <Component {...pageProps} />
    </TransitionLayout>
  );
}
Enter fullscreen mode Exit fullscreen mode

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>
  );
}

Enter fullscreen mode Exit fullscreen mode

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;
}

Enter fullscreen mode Exit fullscreen mode
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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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");
  }, []);

Enter fullscreen mode Exit fullscreen mode

Next, we want the component to enter 'fadeOut' when new children is received.

  useEffect(() => {
    if (children !== displayChildren) setTransitionStage("fadeOut");
  }, [children, setDisplayChildren, displayChildren]);

Enter fullscreen mode Exit fullscreen mode

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>
  )
Enter fullscreen mode Exit fullscreen mode

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>
  );
}


Enter fullscreen mode Exit fullscreen mode

Thank you all!!

Discussion (10)

Collapse
nizardaou96 profile image
Nizar Daou

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?

Collapse
anxinyang profile image
AnxinYang Author

Thanks for replying. I will try to take a look.

Collapse
anxinyang profile image
AnxinYang Author

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

Collapse
quintisimo profile image
Quintus Cardozo

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.

Collapse
anxinyang profile image
AnxinYang Author

Great suggestion! Thanks for pointing that out.

Collapse
koraysels profile image
Koray S.

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

Collapse
anxinyang profile image
AnxinYang Author

I have not running into this issue so far. Do you mind share how you compiled your files?

Collapse
koraysels profile image
Koray S.

looks like the stylesheets get unloaded right in between the transitions..

Collapse
shaan71845 profile image
Shaan Alam

Having same issue !

Collapse
damain profile image
Damain Joseph

Great article!! Small typo in 2nd paragraph "When you navigation to a different page"