Designly Blog

Animating the Web: Route Transitions in Next.js with Framer Motion

Animating the Web: Route Transitions in Next.js with Framer Motion

Posted in Front End Development by Jay Simons
Published on May 17, 2023

In today's dynamic digital landscape, user experience takes the center stage, and the fluidity of navigating between different parts of a web application plays a crucial role in that experience. When it comes to creating seamless transitions during route changes, combining the power of Next.js with the animation capabilities of Framer Motion opens up endless possibilities.

This article aims to be your comprehensive guide on how to enhance your Next.js applications with smooth, compelling route transitions using Framer Motion. Whether you're an experienced developer or just getting started with Next.js and Framer Motion, this guide will provide step-by-step instructions to elevate your web applications to the next level. So, let's dive in and start animating!

Demo
Demo

Ok, let's get started with the code!

The first thing we need to do is install framer-motion:

npm i framer-motion

Next, we need to import <AnimatePresense> from framer into our main _app.tsx file and wrap it around our main component.

AnimatePresence is a component that enables exit animations. It is designed to be the parent of components that may or may not be in the React component tree at any point in time. When children of AnimatePresence are removed, it allows them to animate out before they are unmounted.

// @/pages/_app.tsx
import '@/styles/globals.scss'
import localFont from 'next/font/local'
import { IBM_Plex_Sans } from 'next/font/google';
import NextNProgress from "nextjs-progressbar";
import { AnimatePresence } from 'framer-motion'
import { createTheme, ThemeProvider } from '@mui/material/styles';

const ibm = IBM_Plex_Sans({ subsets: ['latin'], weight: '400' });
const dsFont = localFont({ src: '../fonts/deathstar/death_star-webfont.woff' });

export default function App({ Component, pageProps, router }) {
  const theme = createTheme({
    palette: {
      mode: 'dark'
    },
  });

  return (
    <>
      <ThemeProvider theme={theme}>
        <NextNProgress />
        <main className={ibm.className}>
          <AnimatePresence mode="wait" initial={false}>
            <Component {...pageProps} key={router.asPath} />
          </AnimatePresence>
        </main>
      </ThemeProvider>
    </>
  )
}

The wait attribute tells framer-motion to wait until the animation is done before unmounting the component. It also works for changing routes! When you navigate to another page using a Next.js <Link> component or router.push(), the exit animation will complete before the navigation takes place. The initial={false} prop prevents the initial animation from firing the first time the component loads. That's typically the behavior that is desired.

Ok, we're almost there! The last thing we need to do is wrap {children} in our main layout file with a motion.div component:

// @/components/Layout/index.tsx
import React, { useState } from 'react'
import Head from 'next/head'
import Sidebar from './Sidebar';
import { motion } from "framer-motion";
import MenuButton from './MenuButton';

export default function Layout({ pageTitle, children }) {
    let titleConcat = "Jay Simons";
    if (pageTitle) titleConcat += " | " + pageTitle;

    const [showSidebar, setShowSidebar] = useState(false);

    return (
        <>
            <Head>
                <title>{titleConcat}</title>
            </Head>
            <div className="text-white min-h-screen">
                <div className="flex">
                    <MenuButton setter={setShowSidebar} />
                    <Sidebar show={showSidebar} setter={setShowSidebar} />
                    <motion.div
                        className="flex flex-col flex-grow w-screen md:w-full min-h-screen"
                        initial={{ y: 300, opacity: 0 }}
                        animate={{ y: 0, opacity: 1 }}
                        exit={{ y: 300, opacity: 0 }}
                        transition={{
                            type: "spring",
                            stiffness: 100,
                            damping: 20,
                        }}
                    >
                        {children}
                    </motion.div>
                </div>
            </div>
        </>
    )
}

That's it, easy peasy, lemon squeezy! Feel free to check out my portfolio site for a full demonstration. This is a quick and easy way to impress any client or employer. 🤗


Thank you for taking the time to read my article and I hope you found it useful (or at the very least, mildly entertaining). For more great information about web dev, systems administration and cloud computing, please read the Designly Blog. Also, please leave your comments! I love to hear thoughts from my readers.

I use Hostinger to host my clients' websites. You can get a business account that can host 100 websites at a price of $3.99/mo, which you can lock in for up to 48 months! It's the best deal in town. Services include PHP hosting (with extensions), MySQL, Wordpress and Email services.

Looking for a web developer? I'm available for hire! To inquire, please fill out a contact form.


Loading comments...