Add App Router Framer Motion shared layout example to prevent hydration issues #84954
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
…This PR adds a new example at examples/with-framer-motion-app-router that demonstrates shared element transitions with Framer Motion using the Next.js App Router, without hydration warnings.
What’s included:
app/template.tsx as a client transition boundary with LayoutGroup + AnimatePresence.
All motion-rendering surfaces are client components ('use client').
Route transitions keyed by usePathname() and initial={false} to avoid SSR/CSR diffs.
Shared element via matching layoutId across routes.
Minimal pages (/ grid → /[slug] detail), with a concise README.
Why:
Addresses common pitfalls when combining App Router and Framer Motion shared layout transitions that can cause hydration mismatch warnings or janky transitions.
Provides a canonical setup developers can copy into their projects.
Test plan:
cd examples/with-framer-motion-app-router
pnpm install && pnpm dev
Navigate between / and /[slug] and verify:
Smooth shared element transitions
No hydration warnings in dev or prod build
Notes:
Add three images under public/photos/1.jpg, public/photos/2.jpg, public/photos/3.jpg (or adjust the example data) to see the visuals.
Example depends on framer-motion@^11 and Next canary.
References:
Upstream repo: https://github.com/vercel/next.js
Your fork/branch: https://github.com/Samii2383/next.js (branch: feat/examples-with-framer-motion-app-router)
Open PR link: https://github.com/vercel/next.js/compare/canary...Samii2383:feat/examples-with-framer-motion-app-router?expand=1
Logic explained (how the example avoids hydration issues)
Client transition boundary: app/template.tsx is a client component and wraps page content in LayoutGroup + AnimatePresence. This ensures the transition logic only runs on the client, preventing SSR-side animation state from leaking into the server render.
Stable route key: We key the transition container by usePathname(), so React tears down and mounts the motion container predictably for each navigation. This keeps Framer Motion’s enter/exit lifecycle consistent.
initial={false}: AnimatePresence uses initial={false} so the first client paint doesn’t replay an “enter” animation over the server-rendered markup. That’s the most common cause of hydration diffs with animations.
Shared element (layoutId): The grid and detail views use identical layoutId values for the same visual element, letting Framer Motion interpolate geometry between routes.
Client-only motion surfaces: Grid, Detail, and SharedImage are marked 'use client'. Framer Motion’s DOM measurement and animation occur only on the client, avoiding any server vs client markup divergence.
Next/Image sizing: The shared image uses sizes and fixed width/height to reduce layout shifts, which further cuts down on noisy DOM diffs during hydration.