DEV Community

Cover image for Advanced page transitions with Next.js and Framer Motion
Matt Angelosanto for LogRocket

Posted on • Originally published at blog.logrocket.com

Advanced page transitions with Next.js and Framer Motion

Written by Francois Brill✏️

The web has become increasingly more interactive. Users have come to expect a higher level of interactivity to grab their attention and induce them to engage with the information on the page. A key way to capture attention is to use movement and page transitions. This may sound like a PowerPoint slide deck with inbuilt clipart-type page transitions, but I can assure you that’s not what I’m referring to!

This article will demonstrate how to use Next.js and Framer Motion to apply subtle, elegant page transitions that add personality and style to your site.

Jump ahead:

Indicating a loading state

Page transitions are just one of several tools in a frontend developer’s toolbox. When used appropriately, page transitions can increase user engagement, even holding a user’s attention during page load.

Webpage content generally content loads quickly, but if you’re dependent on fetching data from a server, a page transition with just the right level of motion applied could help keep the user engaged.

One option is to add a quick loading bar; this provides feedback to a user and informs them to stay put and not navigate away when content takes an extra couple of seconds to load.

Anything faster than 200ms is perceived by the brain to be instant. However, it’s really hard to get a page to load that fast. By adding a .5-2s page transition, you’re essentially buying yourself time to load data and get things ready, so that when the next page is revealed you can just deliver the content that the user requested.

Determining when to use page transitions

In order to provide the best website UX, you’ll need to put the user at the center of the experience and consider things from their perspective. Consider the context of the user’s needs when visiting your website and use this information to evaluate what level of page transitions are acceptable.

The loading bar mentioned previously could be used on nearly any type of website. The architecture of the loading bar could range from a full-page experience to a slim component that is only visible at the top of the webpage. But, regardless of the loading bar’s appearance, its purpose is to indicate the system status as the next page is loading.

When users visit an information-rich website to consume content, they do not want to be slowed down by page transitions. This is especially true of sites where users browse several pages. So, don’t get too excited and add page transitions to your corporate website! More flamboyant page transitions should be reserved for websites that are more creative in nature since their site visitors likely expect more entertainment.

I’ve added some page transitions, am I done?

In general, animations are most successful when they are subtle, feel natural, and mesh well with the entire overall user experience. Simply adding page transitions would probably feel out of place, and you’d need to consider the overall on-page experience. You might want to add some movement to elements to enter the page, interactive hover effects, and so forth to keep the user engaged throughout their visit to your website.

Building page transitions

Page transitions can be built in just about any frontend framework or technology, but in this demonstration, we’ll use Next.js to provide the cue for when pages transition to trigger the animation and Framer Motion to actually perform the page transitions.

Framer Motion has dubbed itself “the production-ready motion library for React”, and it’s a real treat to use. The thing I like most about this library is its declarative way of achieving animations. With Framer Motion, you declare what you want the start and end to look like, and the library fills in the gaps.

Creating animations from scratch is really difficult. If you’ve ever attempted to use CSS, or almost any other language, to animate something in React, it becomes very tricky. React immediately unmounts the element that is exiting the DOM and, because the element is dropped, you can’t animate it on the way “out”. A page transition would not feel right if there was an abrupt jump when the page changed. This is part of the magic that Framer Motion automatically takes care of for us, although there is a trick to implementing it that I’ll cover below.

OK, let’s jump in and put this all to use!

Page transitions demo

To demonstrate creating page transitions, we’ll build a Next.js site with Framer Motion. We’ll style the site with my preferred method: Tailwind CSS.

Here’s what we’ll end up with; each photo page is a new (dynamic) page in Next.js and you can see the page transitions as we navigate between the list and detail pages: Dynamic Page Transitions Example in Next.js

If you’d like, you can also grab the source code for the above example to browse and follow along.

Setting the scene

To set up our page transitions demo, we need to understand how the internal workings of Next.js function:

  • First, there is an _app.js file that is persisted between page loads
  • Second, we need to use the Next.js <Link> component to link to pages. With this, Next.js performs a shallow render and essentially mounts the new page component while it unmounts the previous component, giving us an SPA-like feel and the ability to change between pages without having to reload the page. We need this functionality in order to achieve page transitions
  • As mentioned previously, the most difficult part of trying to animate React components that leave the DOM is that they are simply just gone, making them nearly impossible to animate. Framer Motion solves this with an <AnimatePresence> component that does some magic to make it possible to declare an exit state that can be animated

Starting a new Next.js site

To showcase how we can achieve animated page transitions, let’s create a quick Next.js site with the Tailwind CSS starter to handle our styling.

Next, we’ll need to install Framer Motion, like so:

yarn add framer-motion
Enter fullscreen mode Exit fullscreen mode

Adding AnimatePresence

Now, we’ll work on setting up the page transitions. First, we add the AnimatePresence to _app.js:

// _app.js

import { AnimatePresence } from 'framer-motion'

function MyApp({ Component, pageProps, router }) {
  return (
    <AnimatePresence mode="wait" initial={false}>
      <Component {...pageProps} key={router.asPath} />
    </AnimatePresence>
  );
}
Enter fullscreen mode Exit fullscreen mode

Next, we need to wrap our <Component> with <AnimatePresence>.

Here are two additional, but optional, settings that I enabled for this demo:

  1. mode="wait": This just tells Framer Motion to complete any exit animations (exiting page) before starting a new animation (new page) on the new component.
  2. initial: Setting this to false means it’s not going to play the animation on the first page load, which just feels better.

One key point here is to make sure your animated elements are direct children of AnimatePresence so that it can take over and animate any exit events before removing the element from the React tree.

Because we’re declaring AnimatePresence in the_app.js and AnimatePresence animates the direct children, we need to provide the <Component> that we’re returning a unique key to. Initially, this tripped me up. I resolved this issue by adding the page path as a key to ensure it’s always unique. This way, React will register each page as a different component and can animate the exit before animating the entry of a new component.

Creating a shared layout component

Once the wrapper is in place in the _app.js, we’ll need to create the child page element that is actually animated. Instead of doing this on each and every page, we can create a shared <Layout> component that can be used to wrap all the pages we want to animate:

// components/Layout/index.js

import { motion } from "framer-motion";

const Layout = ({ children }) => (
  <motion.div
    initial={{ x: 300, opacity: 0 }}
    animate={{ x: 0, opacity: 1 }}
    exit={{ x: 300, opacity: 0 }}
    transition={{
      type: "spring",
      stiffness: 260,
      damping: 20,
    }}
  >
    {children}
  </motion.div>
);
export default Layout;
Enter fullscreen mode Exit fullscreen mode

These are some great default settings to start off with, but you can explore other declarative properties too. For example, you could use initial to specify a transition starting point, where the element should “come from”, while animate would specify the “end state” of where you want things to end up, exit is used to specify the target of where the animated component should end up.

These properties can be used to fine-tune any page transitions, as well as the transition properties itself. If these names sound confusing, you can also define your own variants to make it easier to follow.

Using the layout component

Next, we need to use the layout component on all the pages we want to animate; these could be static pages or dynamic routes - it really does not matter.

Make this is the first component wrapping any child component to ensure it’s a direct descendant of <AnimatePresence>:

// pages/index.js

import Layout from "../components/Layout";

export default function Home() {
  return (
    <Layout>
        // ....
        // Page content goes here
        // ....
    </Layout>
  );
}
Enter fullscreen mode Exit fullscreen mode

With the above stripped-down markup of the homepage, I’m just showing you what is required to get the page transitions working. Performing this on multiple pages and linking to them will result in a page transition, and any content in the actual component will animate with our page transition.

Scrolling back to top

With my example, it was hard to see if the transitions were working at first as the pages weren’t that long. But in looking at the mobile experience, it is clear what is happening. Once we scroll down and transition to the new page, Next.js persists the position and we land in the middle of the new page. This is clearly not a great user experience: Next.js Page Transitions on Mobile

Fixing this issue is easy with the attribute on our root <AnimatePresence> where we can add any onExitComplete function. All we have to do is scroll the window back to the top once the exit animation is complete and, regardless of the page length, the new page will start from the top:

// _app.js

<AnimatePresence
  mode="wait"
  initial={false}
  onExitComplete={() => window.scrollTo(0, 0)}
>
Enter fullscreen mode Exit fullscreen mode

Bonus tip: Loading page transitions

As I alluded to in the beginning of this guide, page transitions that also serve as a page loader are helpful for keeping a user’s attention while you’re fetching data.

Next.js makes this easy by providing us with some Router events that we can use to create this:

// _app.js

import { useState, useEffect } from "react"
import Router from "next/router"
import PageLoader from "../components/PageLoader"

const App = ({ Component, pageProps }) => {
  const [loading, setLoading] = useState(false)
  useEffect(() => {
    // Used for page transition
    const start = () => {
      setLoading(true)
    }
    const end = () => {
      setLoading(false)
    }
    Router.events.on("routeChangeStart", start)
    Router.events.on("routeChangeComplete", end)
    Router.events.on("routeChangeError", end)
    return () => {
      Router.events.off("routeChangeStart", start)
      Router.events.off("routeChangeComplete", end)
      Router.events.off("routeChangeError", end)
    }
  }, [])

  return loading ? <PageLoader /> : <Component {...pageProps} />
}
export default App
Enter fullscreen mode Exit fullscreen mode

We use the Next.js Router events to set a local state variable to indicate the loading state. From there we can decide what to do with that indicator. In this example, there is a different component that will be rendered as a whole page loader, which could be styled and animated separately.

Conclusion

In this article, we looked at when and why you may want to consider adding page transitions on your site. We demonstrated how to create and add page transitions using Next.js and Framer Motion. We also looked at a different approach for using a page loader as an interstitial loading state that serves as a page transition. If you try out this approach, use the comments below to let me know how you find it!


LogRocket: Full visibility into production Next.js apps

Debugging Next applications can be difficult, especially when users experience issues that are hard to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket signup

LogRocket is like a DVR for web and mobile apps, recording literally everything that happens on your Next app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your Next.js apps — start monitoring for free.

Top comments (0)