Easily add animated transitions between pages using Next.js App Router and your favorite animation library.
- Live Demo using GSAP (source code: /example).
- Stackblitz Demo using Framer Motion.
- Automatically detect internal links to handle page transitions (optional
autoflag). - Use a custom
Linkcomponent to manually handle page transitions (whenautois disabled). - Exclusively to be used with Next.js App Router (v14.0.0 or higher).
- Quickly add animated transitions between pages using JavaScript or CSS.
- Integrate seamlessly with GSAP or any other animation library of your choice (see minimal GSAP example).
- If JavaScript is disabled, the router's accessibility is not compromised.
- It's really lightweight; the bundle size is less than 8 KB.
- Focused on customizable animations, not targeting the View Transitions API.
If you're looking to use the View Transitions API, check next-view-transitions.
Warning
This project is currently in Beta. Please note that the API may change as features are enhanced and refined.
Install the package using your preferred package manager:
pnpm add next-transition-routeryarn add next-transition-routernpm install next-transition-routerbun add next-transition-routerCreate a client component (e.g.: app/providers.tsx) to use the TransitionRouter provider:
"use client";
import { TransitionRouter } from "next-transition-router";
export function Providers({ children }: { children: React.ReactNode }) {
return (
<TransitionRouter
leave={(next) => {
someAnimation().then(next);
}}
enter={(next) => {
anotherAnimation().then(next);
}}
>
{children}
</TransitionRouter>
);
}Note
It should be a client component because you have to pass DOM functions as props to the provider.
After that, you should import that component in the layout component (e.g.: app/layout.tsx).
The leave and enter callbacks support async functions.
"use client";
import { TransitionRouter } from "next-transition-router";
export function Providers({ children }: { children: React.ReactNode }) {
return (
<TransitionRouter
leave={async (next) => {
await someAsyncAnimation();
next();
}}
enter={async (next) => {
await anotherAsyncAnimation();
next();
}}
>
{children}
</TransitionRouter>
);
}The leave callback receives the from and to parameters, which are strings with the previous and next page paths. Useful if you want to animate the transition conditionally based on the page.
const onLeave = (next, from, to) => {
someAnimation(from, to).then(next);
};Note
When using router.back() method, the to parameter will be undefined. See programmatic navigation.
To determine how to handle links, TransitionRouter can receive an auto prop (boolean).
Use the custom Link component instead of the native Link component from Next.js to trigger transitions.
import { Link } from "next-transition-router";
export function Example() {
return <Link href="/about">About</Link>;
}Tip
Use import { Link as TransitionLink } from "next-transition-router" to avoid naming conflicts.
When auto is enabled, the TransitionRouter intercepts click events on internal links, except anchor links, and triggers page transitions. In this case you don't need to use the custom Link component.
To ignore a link in this mode, simply add the data-transition-ignore attribute to the link.
Use the useTransitionRouter hook to manage navigation (push, replace, back).
It's similar to Next.js useRouter with added transition support.
"use client";
import { useTransitionRouter } from "next-transition-router";
export function Programmatic() {
const router = useTransitionRouter();
return (
<button
onClick={() => {
alert("Do something before navigating away");
router.push("/about");
}}
>
Go to /about
</button>
);
}Important
Back and Forward browser navigation doesn't trigger page transitions, and this is intentional.
Use the useTransitionState hook to determine the current stage of the transition.
Possible stage values: 'entering' | 'leaving' | 'none'.
Aditionally, you have the isReady state (boolean).
"use client";
import { useTransitionState } from "next-transition-router";
export function Example() {
const { stage, isReady } = useTransitionState();
return (
<div>
<p>Current stage: {stage}</p>
<p>Page ready: {isReady ? "Yes" : "No"}</p>
</div>
);
}Tip
This is useful, for example, if you want to trigger a reveal animation after the page transition ends.
TransitionRouter manages cleanup functions for leave and enter callbacks, to prevent memory leaks.
Similar to React's useEffect hook, you can return a cleanup function to cancel the animation.
"use client";
import { gsap } from "gsap";
import { TransitionRouter } from "next-transition-router";
export function Providers({ children }: { children: React.ReactNode }) {
return (
<TransitionRouter
leave={(next) => {
const tween = gsap.fromTo("main", { autoAlpha: 1 }, { autoAlpha: 0, onComplete: next });
return () => tween.kill();
}}
enter={(next) => {
const tween = gsap.fromTo("main", { autoAlpha: 0 }, { autoAlpha: 1, onComplete: next });
return () => tween.kill();
}}
>
{children}
</TransitionRouter>
);
}When overlapping exit animations with page loading (common for smooth transitions), React rendering can cause animation jank. Use requestAnimationFrame and startTransition to prioritize animation performance:
import { startTransition } from "react";
enter={(next) => {
const tl = gsap.timeline()
.to(".overlay", { y: "-100%", duration: 0.5 })
.call(() => {
requestAnimationFrame(() => startTransition(next));
}, undefined, "<50%"); // Overlap timing preserved
return () => tl.kill();
}}This prevents React updates from interfering with your animation timeline while maintaining visual timing.
| Prop | Type | Default Value | Description |
|---|---|---|---|
leave |
function |
next => next() |
Function to handle the leaving animation |
enter |
function |
next => next() |
Function to handle the entering animation |
auto |
boolean |
false |
Flag to enable/disable auto-detection of links |
| Property | Type | Description |
|---|---|---|
stage |
'entering' | 'leaving' | 'none' |
Indicates the current stage of the transition. |
isReady |
boolean |
Indicates if the new page is ready to be animated. |
This package may not cover every use case. If you require a specific scenario, please open an issue, and we can explore the possibility of extending the functionality.
MIT.