Framer Motion and the Next.js App Router fight each other more than you'd expect. The default AnimatePresence pattern assumes a single root that owns the route key, but App Router renders layouts and pages as separate RSC subtrees. Wire it up naively and you get a visible layout shift on every route change — elements jump before the exit animation runs, CLS spikes, and your Lighthouse score takes the hit. Here is exactly how I solved it on a production site.
Why framer motion causes layout shift in Next.js App Router
Classic Pages Router page transitions worked because _app.tsx was a single client component that wrapped every page. You'd pass router.pathname as the AnimatePresence key and the library handled mount/unmount cleanly.
App Router breaks that assumption in two ways:
layout.tsx is a server component by default, and it persists across navigations — it does not remount when the route changes.






