Animating Next.js page transitions with Framer Motion
Tags: nextjs, react, webdev, showdev
Published: 2021-07-05 21:18:44

---
description: Bring your Next.js app to life by adding page transition animations with Framer Motion.
---

A few months ago [I rebuilt my Dev.to powered Next.js website](https://dev.to/jameswallis/i-completely-rewrote-my-personal-website-using-dev-to-as-a-cms-2pje) from scratch. While building it, I decided adding animations would bring its simple design to life. Previously, I'd used CSS transitions and JavaScript to achieve animations on a webpage. This time I wanted to use an animation library built for React.js that I could use in future projects. 

*Enter Framer Motion.*

---

## Framer Motion

> A production-ready motion library for React.

*- https://www.framer.com/motion*

It's a library that enables the animations of React components on a page and while the component is entering and also leaving.

Framer Motion can do all of the following:
* Spring animations
* Simple keyframes syntax
* Gestures (drag/tap/hover)
* Layout and shared layout animations
* SVG paths
* Exit animations
* Server-side rendering
* Variants for orchestrating animations across components
* CSS variables

And can bring a static page to life:

<figure>
  <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uy6922sbnmec4lsg0qvq.gif">
  <figcaption>Various animations powered by Framer Motion</figcaption>
</figure>

Read more about Framer Motion and view examples on [their website](https://www.framer.com/motion/).

---

## Animating Next.js page transitions

As well as making user triggered animations, Framer Motion can animate a component when it is mounting (entering) and unmounting (leaving). I use this capability to animate the components that come and go when the page changes. In Next.js terms, this is everything apart from [`_app.js`](https://nextjs.org/docs/advanced-features/custom-app) - so all pages and other components. Where possible, using `_app.js` to persist layouts between page changes will reduce the amount of rendering that React has to do each time the page changes - potentially improving your app performance.

### Preparing the codebase

Before I added any animations to my website I did two pieces of refactoring:

1. **Moved common components that shouldn't animate on every page change into `_app.js`**. In my case this meant moving the `Header` and `Footer` which you can [see on GitHub](https://github.com/james-wallis/wallis.dev/blob/master/pages/_app.tsx#L57-L65).

2. **Added a wrapper component to control the animation states within pages**. On my website it is the [`Layout`](https://github.com/james-wallis/wallis.dev/blob/master/components/Layout.tsx#L20) component. Note the `<motion.main>` component which is specific to Framer Motion. In the rendered HTML output this will be a HTML `main` element, however, adding the `motion.` supplied by Framer Motion provides the ability to pass certain animation props such as `transition`, `initial` and `animate`.

### Entry animations

Looking at the [`Layout`](https://github.com/james-wallis/wallis.dev/blob/master/components/Layout.tsx) component you will see an object named `variants` (see below). Variants promote cleaner code by removing the requirement to add the animation object to the `motion.main` component. You can read more about them on [the Framer Motion website](https://www.framer.com/api/motion/animation/#variants).

```jsx
const variants = {
    hidden: { opacity: 0, x: -200, y: 0 },
    enter: { opacity: 1, x: 0, y: 0 },
    exit: { opacity: 0, x: 0, y: -100 },
}
```

Now focussing on the `motion.main` component:
```jsx
<motion.main
    variants={variants} // Pass the variant object into Framer Motion 
    initial="hidden" // Set the initial state to variants.hidden
    animate="enter" // Animated state to variants.enter
    exit="exit" // Exit state (used later) to variants.exit
    transition={{ type: 'linear' }} // Set the transition to linear
    className=""
>
    {children}
</motion.main>
```

The `initial` and `animate` states will control the entry animation for this component. When you change the page on my website, you should see the content change from having an opacity of `0` and `x` position of `-200px` to having an opacity of `1` and being in the center of the screen. This gives the effect of the content fading in from the left. By the way, "A Transition is an object that defines how values animate from one state to another" - [from the Framer Motion website](https://www.framer.com/api/motion/types/#transition).

An entry animation is great but let's go a little further and animate components when they leave the page.

### Adding [`AnimatePresence`](https://www.framer.com/api/motion/animate-presence/) and exit animations

One feature of Framer Motion is that it can animate components after they've left the React DOM. To activate this feature you can use the [`AnimatePresence`](https://www.framer.com/api/motion/animate-presence/) component. For my website, I use the optional `exitBeforeEnter` prop which tells the entrance animation to wait until the exit animation has ended before starting - without this the content would mount on top of the unmounting content, looking messy.

You'll need to add the `AnimatePresence` component to the [`_app.js` file](https://github.com/james-wallis/wallis.dev/blob/master/pages/_app.tsx#L58) so that it never unmounts (unmounting would disable the exit animations). Note also the `initial={false}` prop which disables the entry animation when you first visit the website. Disabling it is just a personal preference, remove that line if you want to enable it.

Once `AnimatePresence` is added to `_app.js`, you can add an `exit` animation to your `motion.main` component. See this in the two code blocks above.

We're almost finished but we just need to fix an issue with Next.js scrolling to the top of the page when the route changes.

### Solving the scroll on link change issue

When adding page navigation to a Next.js application you should be using the [`Link`](https://nextjs.org/docs/api-reference/next/link) component. By default, when the `Link` component is clicked it scrolls to the top of the page before animating, making the page transitions look a bit clunky. See below:

<figure>
  <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1xxv0ezfw8k6xxqq3k4a.gif">
  <figcaption>Scrolling to the top before animating</figcaption>
</figure>

Fortunately the fix for this is pretty easy. For each `Link` component that is used around your codebase, add the [`scroll={false}`](https://nextjs.org/docs/api-reference/next/link#disable-scrolling-to-the-top-of-the-page) prop. This will disable the scrolling when its clicked. To make this easier and maintain clean code, I created a component that wraps `Link` but disables the scroll. I called it `NoScrollLink` and you can [view it on GitHub](https://github.com/james-wallis/wallis.dev/blob/master/components/NoScrollLink.tsx). 

After disabling the `Link` component's scroll, it's a good idea to scroll to the top of the page after the Framer Motion exit animation has completed. This gives the effect of content leaving at the current scroll height but the new content entering at the top of the page. Again this is easy, you can use the `onExitComplete` prop on the `AnimatePresence` component in `_app.js`. The following code snippet will scroll to the top once the exit animation has completed.

```javascript
onExitComplete={() => window.scrollTo(0, 0)}
```

[View on GitHub](https://github.com/james-wallis/wallis.dev/blob/master/pages/_app.tsx#L61)

Having added that, when you change page Framer Motion should unmount the old content, scroll to the top and mount the new content.

---

### The finished product

If you've been following along or want to see it live [on my website](http://wallis.dev) you'll see the following page transitions:

<figure>
  <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gctmesek0r4ibpopdmeq.gif">
  <figcaption>The finished page animation</figcaption>
</figure>

---

## Summary

In this article I wanted to help others add page transitions to their Next.js app with the help of Framer Motion. I overcame some obstacles when adding them to my website such as realising `AnimatePresence` needed to be in `_app.js` and how to stop the scroll to the top of the page after a `Link` is clicked.

If you've anything to add or just want to show some appreciation, leave a comment or react! 

Thanks for reading!