Detail pages on their own route
Animate a card on one route into its detail page on another in the Next.js App Router with MatchCut. Going back shrinks the detail into the card again.
MatchCut works across a client-side route change, because it only needs the same id to leave and arrive within a second.
import Link from "next/link"
import { MatchCut } from "@/components/keyframery/match-cut"
export default async function Orders() {
const orders = await getOrders()
return (
<ul className="grid gap-2">
{orders.map((o) => (
<li key={o.id}>
<MatchCut id={`order-${o.id}`}>
<Link href={`/orders/${o.id}`} className="block rounded-xl border p-3">{o.customer}</Link>
</MatchCut>
</li>
))}
</ul>
)
}import { MatchCut } from "@/components/keyframery/match-cut"
export default async function Order({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params
const order = await getOrder(id)
return (
<MatchCut id={`order-${order.id}`} className="rounded-2xl border p-6">
<OrderDetail order={order} />
</MatchCut>
)
}MatchCut is a client component, and the pages around it can stay server components.
Things to know
- Navigate on the client. A
<Link>orrouter.pushkeeps the page alive, so the match can play. A full page load starts fresh and plays nothing. - Back works too. Going back mounts the card again with the same
id, so the detail shrinks into it. - Slow routes. If the detail page takes longer than a second to render, there's nothing left to match, and it simply appears. Add a
loading.tsxthat renders the sameMatchCutwith a skeleton inside, so the match plays at once and the content dissolves in.
Loading data after a click
Animate data loading in React with LoadCut and ValueCut, using React Query, SWR or plain fetch, so a click never makes the page jump.
Live data
Animate live data in React: websocket, polling and server-push updates where lists don't jump and changed numbers get noticed, with ListCut and ValueCut.