Next.js page transition with entry and exit animations using GSAP
Page transitions can instantly make your Next.js app feel smoother and more professional.In this guide, we’ll build a clean and simple overlay-based page transition:
- Exit animation runs before navigation
- Entry animation runs after navigation
- Works across all routes
- No styling — pure logic only
1.How it Works
1. A Transition Provider
- Renders overlay
- Runs GSAP animations
- Exposes a navigate() function through React Context
2. A Transition Link
- Calls navigate() instead of directly routing
3. Wrapping the entire app
- So transitions run between all page
This approach works with the App Router.
2.The Transition Provider (Core Logic)
This component controls:
- Initial GSAP setup
- Exit animation
- Navigation
- Entry animation
Transition Provider:
"use client";import { useRouter, usePathname } from "next/navigation";import gsap from "gsap";import React, {createContext,useRef,useLayoutEffect,useEffect,} from "react";export const TransitionContext = createContext<{ navigate: (href: string) => void }>({ navigate: () => {} });const TransitionProvider = ({ children }: { children: React.ReactNode }) => {const router = useRouter();const pathname = usePathname();const containerRef = useRef<HTMLDivElement>(null);const isAnimating = useRef(false);const firstLoad = useRef(true);// Start off-screenuseLayoutEffect(() => {gsap.set(containerRef.current, { y: "100%" });}, []);// Exit animation → navigateconst navigate = (href: string) => {if (isAnimating.current) return;isAnimating.current = true;gsap.fromTo(containerRef.current,{ y: "100%" },{y: "0%",duration: 0.6,ease: "power2.inOut",onComplete: () => {router.push(href);if (href === pathname) {requestAnimationFrame(() => enter());}},});};// Entry animationconst enter = () => {gsap.fromTo(containerRef.current,{ y: "0%" },{y: "100%",duration: 0.6,ease: "power2.inOut",onComplete: () => {isAnimating.current = false;},});};// Run entry on every route change except first loaduseEffect(() => {if (firstLoad.current) {firstLoad.current = false;return;}enter();}, [pathname]);return (<TransitionContext.Provider value={{ navigate }}>{children}{/* Overlay*/}<div ref={containerRef} className="fixed inset-0 flex pointer-events-none bg-zinc-900 min-h-screen w-full" /></TransitionContext.Provider>);};export default TransitionProvider;
3.Transition Link Component
This replaces <Link> so we can run animations before navigating.
Transition Link:
"use client";import { useContext } from "react";import { TransitionContext } from "./TransitionProvider";const TransitionLink = ({ href, children }: { href: string; children: React.ReactNode }) => {const { navigate } = useContext(TransitionContext);return (<button onClick={() => navigate(href)}>{children}</button>);};export default TransitionLink;
4.Wrap Your App with the Provider
Wrap your app with the provider. This makes transitions work globally.
layout.tsx:
import TransitionProvider from "@/components/Transition/TransitionProvider";export default function RootLayout({ children }) {return (<html lang="en"><body><TransitionProvider>{children}</TransitionProvider></body></html>);}
5.Example Usage on a Page
page.tsx:
import TransitionLink from "@/components/Transition/TransitionLink";export default function Home() {return (<div><h1>Home Page</h1><TransitionLink href="/about">Go to About</TransitionLink></div>);}
Final Result
With just a small setup, you now have:
✔ Simple overlay page transitions
✔ Smooth exit + entry animations
✔ Reusable <TransitionLink>
✔ No complex styling required
✔ Automatically works across all routes
This is a minimal, clean, production-ready GSAP transition system for Next.js App Router.

MALAY
Crafted at 2AM by MalayPatel
“Powered by creativity, fueled by caffeine and created in dark mode.”
. All rights reserved.
