DEV Community

Cover image for Animating Next.js page transitions with Framer Motion
James Wallis
James Wallis

Posted on • Originally published at wallis.dev

Animating Next.js page transitions with Framer Motion

A few months ago I rebuilt my Dev.to powered Next.js website 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:


Various animations powered by Framer Motion

Read more about Framer Motion and view examples on their website.


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 - 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.

  2. Added a wrapper component to control the animation states within pages. On my website it is the Layout 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 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.

const variants = {
    hidden: { opacity: 0, x: -200, y: 0 },
    enter: { opacity: 1, x: 0, y: 0 },
    exit: { opacity: 0, x: 0, y: -100 },
}
Enter fullscreen mode Exit fullscreen mode

Now focussing on the motion.main component:

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

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.

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

Adding AnimatePresence 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 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 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 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:


Scrolling to the top before animating

Fortunately the fix for this is pretty easy. For each Link component that is used around your codebase, add the scroll={false} 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.

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.

onExitComplete={() => window.scrollTo(0, 0)}
Enter fullscreen mode Exit fullscreen mode

View on GitHub

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 you'll see the following page transitions:


The finished page animation


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!

Top comments (14)

Collapse
 
valentinhervieu profile image
Valentin Hervieu

Thanks for this post, this was super helpful to me to start using framer-motion for page transitions. This library is amazing!

Collapse
 
raycaballero profile image
Ray Caballero

Thank you!

Collapse
 
jameswallis profile image
James Wallis

🙌

Collapse
 
ugglr profile image
Carl-W

Thanks for adding the part of the no scrolling prop to the Link element. It was just what I was looking for, great post.

Collapse
 
imcorfitz profile image
Corfitz

Great post! One quick Q though - how would you come about triggering transitions between routes on the same page? Imagine between two posts both located on /blog/[slug].tsx. In my case, I have a headless CMS, fetching all pages in a [[...params]].tsx route, which means that majority of route changes all happen within the same page file. It works fine when going from the static 404.tsx page, but not between pages using the same [[...params]].tsx route. Any great ideas?

Collapse
 
jameswallis profile image
James Wallis

Don't forget to add the key value to your Next.js Component tag inside _app.js. It needs to be unique for every page (you can just use the page url).

github.com/james-wallis/wallis.dev...

I'll update the post with this information at some point.

Collapse
 
beazer profile image
Dave • Edited

Great timing on this post! I've spent so long today struggling to get my animation to work between pages... I was using an <a tag instead of the <Link tag! Thanks for pointing that out in this post, it steered me back from the edge!

Collapse
 
jameswallis profile image
James Wallis

Ha amazing, took me ages to get around to writing it so the timing is very lucky!

Collapse
 
heybett profile image
Luiz Bett

You didn't import your Layout to app.tsx. How you get to animate the entrance this way? In my case, it only animates on full refresh and when putting the components on app.tsx inside a after import it from /components.

Collapse
 
jameswallis profile image
James Wallis

Hi Luiz, I didn't add it to _app.tsx because the elements are animated when they enter and leave the DOM.
Adding them into _app.tsx would make them only animate on a refresh like you've described.

Collapse
 
heybett profile image
Luiz Bett

inside a Layout*

Collapse
 
jrrio profile image
João Rodrigues • Edited

Congratulations for your work and for sharing all this with the community. However, I've discovered the hard way that AnimatePresence makes the app messy in production (npm run build && npm run start) if the pages / components utilise CSS Modules. I think your site is performing okay because you're using Tailwind CSS.
Bug: github.com/framer/motion/issues/948

Collapse
 
cannon303 profile image
cannon303 • Edited

Hi I noticed with your website that the page transitions work great but I wanted to ask if this is achievable: When you go to your home page and scroll down to, for example: Featured Projects section, if you then click the Portfolio link in the top navigation the transition to that page works fine but if you then click the back button on the browser what I would expect to see is the home page scrolled down to the Featured Projects section where I last left the page but instead you end up back at the top of the home page with another transition. I probably wouldn't even expect to see the transition again either but the more important task is to be able to go back to the point you last scrolled the page when you click the browser back / forward buttons. Is that achievable with Framer Motion?

Collapse
 
ex3cut3 profile image
exe.cute

Hi, how to achieve animation of switching bottom border in navigation? Tried with new v5, but it only somehow snaps, not smooth and predictable as on your site, even tried copypaste from git