motiondivision / motion

A modern animation library for React and JavaScript
https://motion.dev
MIT License
25.84k stars 849 forks source link

Fix usage with React Server Components #2778

Closed mattgperry closed 2 months ago

mattgperry commented 2 months ago

This PR fixes Framer Motion with React Server Components.

Currently, to use Motion components with RSC (Next.js 14 App Router) we have to make a new file with the "use client" directive:

// motion.js
"use client"
export { AnimatePresence } from "framer-motion"

And then import that from the file you want to use it in:

import { AnimatePresence } from "./motion"

function Component() {
  return <AnimatePresence>

This PR fixes that by ensuring "use client" is added to each component file and output along with the final ES modules.

However, the situation continues with components that have a . notation, like Reorder.Group

// motion.js
"use client"
export { Reorder } from "framer-motion"
import { Reorder } from "framer-motion"

function Component() {
  return <Reorder.Group>

This leads to the following error:

[!CAUTION] Error: Could not find the module "/node_modules/framer-motion/dist/es/components/Reorder/index.mjs#Reorder#Group" in the React Client Manifest. This is probably a bug in the React Server Components bundler.

The reason this breaks is React server components refer to client-only components using their serialised name, and . breaks the way this is parsed.

So to fix this, Reorder and motion components are now exported statically in a way that they have serialisable component names and remain tree-shakable, following this approach: https://ivicabatinic.from.hr/posts/multipart-namespace-components-addressing-rsc-and-dot-notation-issues#update-insights-on-bundler-limitations-with-dot-notation.

However as a final problem, this approach is incompatible with the motion(Component) pattern, as with a motion function then props like div, span etc would have to be assigned dynamically and are therefore not tree-shakable.

As removing this outright could break code in Framer projects, a new entry point has been made for React Server Component users.

import * as motion from "framer-motion/client"
kurtextrem commented 2 months ago

Does it make sense to make the motion(...) a deprecated call and only use the better tree-shakeable variant from now on?