Creating seamless and visually engaging transitions between pages has become an integral aspect of enhancing user experience. Next.js, a popular React framework, offers a powerful combination with Framer Motion, a declarative motion library for React, to bring life to page transitions, let's get started.
Setup The Project
Create a nextjs project by running this command
npx create-next-app@latest
This will fire the nextjs project with the latest release (in my case is version 14.1.0), feel free to set up your next project as you want, I'm going to use typescript with app router, and tailwinds for styling, clean up the root page app/page.tsx
from the nextjs starter page, mine is like this
app/page.tsx
export default function Home() {
return (
<section className="p-4">
<div className="container">
<h1 className="text-2xl font-bold">Home Page</h1>
<p className="w-1/2 font-medium py-2">
Lorem ipsum dolor sit amet consectetur adipisicing elit. Molestias
natus minima exercitationem perferendis hic maxime nihil officia
fugit, illo asperiores fugiat odit harum, dicta cumque ad atque
assumenda! Saepe dolores repellendus animi harum ex voluptatibus
quaerat unde obcaecati quo consequuntur sequi, autem deleniti?
Eligendi ipsa impedit cupiditate accusantium delectus quidem.
</p>
</div>
</section>
);
}
Also, I'm going to add two more pages with the same content to the animation to look cleaner, app/about/page.tsx
and app/contact/page.tsx
, and add a header component for simple navigation between the pages header.tsx
and use it in the layout
header.tsx
import Link from "next/link";
export default function Header() {
return (
<header>
<nav className="p-4">
<ul className="container flex gap-8">
<li>
<Link href="/">Home</Link>
</li>
<li>
<Link href="/about">About</Link>
</li>
<li>
<Link href="/contact">Contact</Link>
</li>
</ul>
</nav>
</header>
);
}
Add Framer Motion
To install framer-motion simply run the command below
npm install framer-motion
Now after installing the framer-motion we gonna create a Transition.tsx
component to define our base page transition using framer-motion, hers is the code for that component
"use client";
import { motion } from "framer-motion";
export default function Transition({
children,
}: {
children: React.ReactNode;
}) {
return (
<motion.div
initial={{ y: 20, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{ ease: "easeInOut", duration: 0.75 }}
>
{children}
</motion.div>
);
}
As you can see this component is a client component so we marked it as a client component using use client
on top of the component declaration, basically we're using motion
from framer-motion
and using initial, animate, and transition props we can define the page animation.
Using Nextjs Template To Render Animation Pages
You may ask why we can't just use Layout.tsx
to wrap our pages with the Transition.tsx
component, Just like this
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
import Header from "./header";
import Transition from "./trasnition";
const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = {
title: "Page Transition",
description: "Using framer-motion to add trasition between pages",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body className={inter.className}>
<Header />
<Transition>{children}</Transition>
</body>
</html>
);
}
The problem is you can only see the animation for the first render (initial load), then, if you navigate to other pages, Nextjs will not rerender those pages from the layout
, so that the Transition will not render for that page, So, for that case Template.tsx
comes as a handy solution, here is the definition of Template
from Nextjs doc
Unlike layouts that persist across routes and maintain state, templates create a new instance for each of their children on navigation. This means that when a user navigates between routes that share a template, a new instance of the component is mounted, DOM elements are recreated, state is not preserved, and effects are re-synchronized.
Now create template.tsx
under the app directory, and copy paste the code from the Transition.tsx
component, and rename the component name to Template(), like below
"use client";
import { motion } from "framer-motion";
export default function Template({ children }: { children: React.ReactNode }) {
return (
<motion.div
initial={{ y: 20, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{ ease: "easeInOut", duration: 0.75 }}
>
{children}
</motion.div>
);
}
And here you go! we have added the page transition for the project, you can customize the transition the way you want, and have a better experience, if you have any questions feel free to ask.
Top comments (14)
nice
My plesure,
Exactly what I have done in my latest project, that is reassuring 👍
I had to rely on ugly hacks to make this working properly before the Next 14 release (animatePresence + mode attr.)
Exactly, we had to deal with those kinds of weird hacks, but now have such a clean way to get the animation done, All Thanks to Nextjs Team
well documented
Thanks, hope it helps
well done and keep growing bro🙌🙌
You're welcome!
This method doesn't cover the exit animations. I did fork your repo and the transition between routes looks like the current route is simply disappearing and then the animation of next route starts (opacity etc). Do you have any idea how to fix this issue?
This is something I just noticed while trying this. Did you end up finding a solution for this?
How to add page transitions when using page router?
i have followed the tutorial closely but the transition only occur on initial page load, it does not persist on when i make subsequent navigation to different pages
Did you use
template.ts
orlayout.ts
make sure to usetemplate.ts
otherwise the layout effect only appear once, for working example look at my source code heregithub
Exit animation is still not working that way