CI / quality (push) Canceled after 0s
A template.tsx is re-created on every navigation, resetting AnimatePresence so neither enter nor exit ran. Move the transition into a PageTransition client component rendered in the persistent (site) layout, so AnimatePresence survives navigations and the old page animates out before the new one in.
48 lines
1.8 KiB
TypeScript
48 lines
1.8 KiB
TypeScript
"use client";
|
|
|
|
import { AnimatePresence, motion, useReducedMotion } from "framer-motion";
|
|
import { LayoutRouterContext } from "next/dist/shared/lib/app-router-context.shared-runtime";
|
|
import { usePathname } from "next/navigation";
|
|
import { useContext, useState, type ReactNode } from "react";
|
|
|
|
/**
|
|
* Pins the layout-router context captured when this instance mounted. Each
|
|
* keyed page gets its own FrozenRouter, so the *outgoing* page keeps rendering
|
|
* its old route data while it animates out.
|
|
*/
|
|
function FrozenRouter({ children }: { children: ReactNode }) {
|
|
const context = useContext(LayoutRouterContext);
|
|
const [frozen] = useState(context);
|
|
|
|
if (!frozen) {
|
|
return <>{children}</>;
|
|
}
|
|
|
|
return <LayoutRouterContext.Provider value={frozen}>{children}</LayoutRouterContext.Provider>;
|
|
}
|
|
|
|
/**
|
|
* One unified page transition on every navigation. Lives in the (persistent)
|
|
* site layout — NOT in a `template.tsx`, which Next re-creates on each
|
|
* navigation and would reset AnimatePresence so nothing animates. The old page
|
|
* animates out (rise + blur), then the new page animates in from below.
|
|
*/
|
|
export function PageTransition({ children }: { children: ReactNode }) {
|
|
const reducedMotion = useReducedMotion();
|
|
const pathname = usePathname();
|
|
|
|
return (
|
|
<AnimatePresence mode="wait">
|
|
<motion.div
|
|
key={pathname}
|
|
initial={reducedMotion ? false : { opacity: 0, y: 20, filter: "blur(10px)" }}
|
|
animate={{ opacity: 1, y: 0, filter: "blur(0px)" }}
|
|
exit={reducedMotion ? { opacity: 0 } : { opacity: 0, y: -20, filter: "blur(10px)" }}
|
|
transition={reducedMotion ? { duration: 0 } : { duration: 0.45, ease: [0.22, 1, 0.36, 1] }}
|
|
>
|
|
<FrozenRouter>{children}</FrozenRouter>
|
|
</motion.div>
|
|
</AnimatePresence>
|
|
);
|
|
}
|