Props from a Server Component to a Client Component do not get passed; they get written into the payload and read back in the browser. The constraint is therefore a transport rule, not a style rule: if React 7,897 cannot serialize a value, the render throws.
| Crosses the boundary | Does not cross |
|---|---|
| Strings, numbers, bigints, booleans, null, undefined | Plain functions, including event handlers |
| Globally registered symbols (Symbol.for) | Classes and instances of your own classes |
| Array, Map, Set, TypedArray, ArrayBuffer, Date | Objects with a null prototype |
| Plain objects whose properties also cross | Symbols from Symbol('x') |
| JSX elements, pending promises, 'use server' functions | Anything reachable only through the above |
The two failures you will meet are an event handler defined on the server, and a domain object with methods — the shape a MongoDB 1,815 or ORM layer hands you:
// app/bad-prop/page.js — an event handler created on the server
const badProp = <Counter start={0} onDone={() => console.log("done")} />;
// app/bad-class/page.js — a domain object with methods
class Money {
constructor(cents) { this.cents = cents; }
format() { return `$${(this.cents / 100).toFixed(2)}`; }
}
export default function BadClass() {
return <Show price={new Money(1999)} />;
}⨯ Error: Event handlers cannot be passed to Client Component props.
<... start={0} onDone={function onDone}>
^^^^^^^^^^^^^^^^^
If you need interactivity, consider converting part of this to a Client Component.
⨯ Error: Only plain objects, and a few built-ins, can be passed to Client Components
from Server Components. Classes or null prototypes are not supported.
<... price={{cents: 1999}}>
^^^^^^^^^^^^^^^Both requests end in a 500. Read the second echoed value carefully: React shows {cents: 1999} — the data survived, format() did not. That points at the fix. Serialize deliberately at the boundary: call price.format() on the server, or pass { cents: 1999 } and keep the formatter in the client module.
The one deliberate exception to "no functions" is a Server Function, declared with 'use server' (Server Actions and Mutations). It crosses as a reference rather than as code: the browser receives an id and calls back over the network. Because the two are indistinguishable by type, the Next.js 10,514 TypeScript plugin uses a naming convention — a function prop is accepted when it is named action or ends in Action. Watch the size of props that do serialize, too: their bytes appear twice, once in the HTML and once in the payload.