Why AnimatePresence no longer works in the app directory
With the app directory, Next.js moves toward server components by default, rendering the entire component tree on the server and allowing better performance optimizations. However, this impacts client-only libraries like Framer Motion. Specifically, AnimatePresence
often struggles to retain the component hierarchy it needs, causing the transitions to be interrupted or not play at all.
- The App Router uses React Server Components by default
- Page transitions are handled differently in the new routing system
AnimatePresence
expects components to remain mounted during the animation
To work around this limitation, we can use Next.js’s next/navigation
to control the routing directly on the client. This approach offers several advantages:
- Works smoothly with the App Router
- Lighter weight than Framer Motion
- More control over the transition timing
- Better performance
Create the Transition Component
First, let's create a new file called Transition.jsx
in your components directory:
Transition.jsx
jsx
1"use client";
2
3import Link from "next/link";
4import { useRouter } from "next/navigation";
5
6const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
7
8export const Transition = ({ children, href, className = "", ...props }) => {
9 const router = useRouter();
10
11 const handleTransition = async (e) => {
12 e.preventDefault();
13
14 // Start the transition
15 document.body.classList.add("page-transition");
16
17 // Wait for the animation to play
18 await sleep(500);
19
20 // Navigate to the new page
21 router.push(href);
22
23 // Wait for the page to load and remove the transition class
24 await sleep(500);
25 document.body.classList.remove("page-transition");
26 };
27
28 return (
29 <Link
30 className={className}
31 href={href}
32 onClick={handleTransition}
33 {...props}
34 >
35 {children}
36 </Link>
37 );
38};
Add the CSS Transitions
Create or update your global CSS file:
global.css
css
1/* Base styles */
2body {
3 opacity: 1;
4 transition: opacity 0.5s ease-in-out;
5}
6
7/* Transition styles */
8body.page-transition {
9 opacity: 0;
10}
11
12/* Optional: Add different transition effects */
13@keyframes fadeOut {
14 from { opacity: 1; }
15 to { opacity: 0; }
16}
17
18@keyframes fadeIn {
19 from { opacity: 0; }
20 to { opacity: 1; }
21}
22
Using the TransitionLink Component
Now you can use the TransitionLink component in your pages:
page.js
jsx
1import { Transition } from "@/components/Transition";
2
3export default function HomePage() {
4 return (
5 <div className="p-4">
6 <h1>Welcome to My Site</h1>
7 <Transition href="/about">
8 Go to About Page
9 </Transition>
10 </div>
11 );
12}
13
How It Works
Let's break down how this solution works:
Initialization: When a user clicks a
Transition
, thehandleTransition
function is triggered instead of the default Link behavior.Transition Start:
- The function adds a
page-transition
class to the body - This triggers the CSS opacity transition, fading out the current page
- The function adds a
Navigation:
- After waiting for the fade-out animation (500ms)
- The router pushes the new route
- Another wait ensures the new page has time to load
Transition End:
- The
page-transition
class is removed - The page fades back in due to the CSS transition
- The
Best Practices
- Performance: Keep transitions short (300-500ms) to maintain a snappy feel
- Accessibility: Ensure transitions respect user preferences (
prefers-reduced-motion
) - Error Handling: Add try/catch blocks for navigation errors
- Loading States: Show loading indicators for slower transitions
Common Issues and Solutions
- Flickering: If you notice flickering, try adding
will-change: opacity
to the transitioning elements - Multiple Clicks: Disable the link during transition
- Memory Leaks: Clean up any animations if the component unmounts
This solution provides a way to implement page transitions in Next.js App Router without relying on heavy animation libraries. It's customizable, performant, and works well with the new routing system. Remember to test your transitions across different devices and network conditions to ensure a smooth user experience.