Framer Motion layout and Presence with React 18 concurrent rendering and SSR
0 reputation · 13 Mar 2020, 12:00 UTC
0 reputation · 13 Mar 2020, 12:00 UTC
Goal is to use Framer Motion layout animations and Presence mount and unmount handling in a React 18 application that server renders pages and hydrates on the client. The intent is to keep declarative animation props on motion components while relying on React 18 concurrent mode for rendering.
Constraints involve the integration boundary between Framer Motion’s measurement and scheduling and React’s rendering model. The library uses an isomorphic layout effect that falls back during SSR, Presence renders a static placeholder on the server and defers removal until exit completes, and layout animations measure bounding boxes before and after render. Reduced motion detection depends on window.matchMedia, which is unavailable in non-browser environments.
With these boundaries, the behavior of measurement timing and placeholder stability under concurrent commits is unclear.
Does the Presence placeholder rendered on the server guarantee identical DOM node count and key mapping after hydration when React 18 concurrently commits updates? When layout animations are enabled, under what conditions does client measurement occur relative to a concurrent render that is interrupted and resumed? How should reduced motion preference be evaluated during SSR when window.matchMedia is unavailable?
33775 reputation · 13 Mar 2020, 15:07 UTC
1. Presence placeholder on the server does not guarantee identical DOM node count or key mapping after hydration. The placeholder is rendered as static markup and hydration will only succeed if the client’s first render produces the same tree, keys, and node count as the server output. React 18’s concurrent commits affect only updates after hydration; the initial reconciliation follows the same rules as React 17.
2. Measurement timing for layout animations occurs inside Framer Motion’s isomorphic useLayoutEffect. A concurrent render that is interrupted before it commits will not run this effect, so no measurement happens for that intermediate tree. When the render finally commits, measurement runs once on the committed DOM, not on any discarded intermediate frames.
3. Reduced‑motion preference during SSR cannot be detected with window.matchMedia because window is undefined on the server. The usual safe pattern is to render with a neutral default (e.g., animations enabled) and then apply the user’s preference in a client‑only effect or via a server‑side flag that is passed to the page.
useEffect on the server to avoid React warnings, but it still only runs after the DOM is committed on the client.startTransition and other concurrent features affect only updates that occur after hydration; the initial hydration step follows the same reconciliation rules as React 17.matchMedia will break hydration.AnimatePresence is identical on the server and the client, including keys and the set of children that may be conditionally rendered. If you need to vary the tree based on user preference, move that logic into a client‑only effect or use a server‑side flag that is passed as a prop.startTransition only if you want them to be deferred. Remember that the layout effect will not run until the update is committed.useEffect on the client that reads window.matchMedia('(prefers-reduced-motion: reduce)') and updates a MotionConfig or local state accordingly. Do not let this effect change the component tree that was rendered on the server.StrictMode and watch for hydration warnings. If a warning appears, the tree differs and you need to adjust the server or client rendering logic.startTransition). Verify that the log appears only after the final committed render.Do you currently set any AnimatePresence child keys or component types based on a value that is only available on the client (e.g., reading window or a cookie during the first render)? If so, that difference would cause a hydration mismatch and would require moving that logic out of the initial render.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.