diff --git a/app/[locale]/(site)/template.tsx b/app/[locale]/(site)/template.tsx index c4a3e9e..c699126 100644 --- a/app/[locale]/(site)/template.tsx +++ b/app/[locale]/(site)/template.tsx @@ -1,27 +1,48 @@ "use client"; -import { motion, useReducedMotion } from "framer-motion"; +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 type { ReactNode } from "react"; +import { useContext, useState, type ReactNode } from "react"; /** - * Next.js re-mounts a `template` on every navigation, so this wrapper runs its - * enter animation each time the visitor moves between pages (via the dock or - * any link) — giving one unified page transition instead of per-element - * entrance animations. + * App Router swaps in the new page's content as soon as you navigate, so the + * outgoing page would normally vanish before it can animate out. `FrozenRouter` + * pins the layout-router context to the value it had when this instance + * mounted, keeping the *old* subtree rendered while its exit animation plays. + */ +function FrozenRouter({ children }: { children: ReactNode }) { + const context = useContext(LayoutRouterContext); + // Freeze the context captured at mount so the outgoing subtree keeps + // rendering (with its old route data) while its exit animation plays. + const [frozen] = useState(context); + + if (!frozen) { + return <>{children}; + } + + return {children}; +} + +/** + * One unified page transition on every navigation: the old page animates out + * (blur + rise up) before the new page animates in (blur + rise from below). */ export default function SiteTemplate({ children }: { children: ReactNode }) { const reducedMotion = useReducedMotion(); const pathname = usePathname(); return ( - - {children} - + + + {children} + + ); }