What can cross the line
When a Server Component passes props to a Client Component, the data must serialize. The boundary is roughly "JSON-shaped, plus a few React extras."
| Allowed ✅ | Blocked ❌ |
|---|---|
| Strings, numbers, booleans | Functions |
| Arrays, plain objects | Class instances |
| Dates (serialized as strings) | Symbols |
null / undefined | DOM nodes / streams |
FormData | Circular references |
| React elements (JSX) and Promises |
The big consequence
You can't pass a function from server to client. If you want the client to call back into server logic, define a Server Action and pass that down (the framework wraps it in a serialized POST endpoint reference).
The composition trick
JSX elements serialize. That means a Server Component can pass other Server Components as children to a Client Component — the Client Component renders them but doesn't import them. This is the cleanest way to wrap a server-rendered subtree in client-side interactivity.
Design client props as deliberate transfer objects containing only what the interaction needs. Convert ORM models and SDK results to plain data. When the client only needs to place server-rendered content, pass a React element as a slot instead of serializing the entire source object. Boundary tests should include large arrays, empty values, and unsupported objects as well as the happy path. A value that appears fine in development must not become a serialization failure or an oversized transfer after deployment. TypeScript cannot guarantee the runtime value is serializable. A harmless-looking interface can contain a class instance or circular reference. Treat a boundary failure as useful evidence and normalize external results explicitly. Send a class instance and then a plain DTO through the same prop. Compare the failure and payload size. Replace a large data prop with server-rendered children where appropriate, and confirm the client file no longer imports the content implementation.