Serializing Props

Serializing Props Across the Boundary

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.

What React can serialize from a Server Component to a Client Component
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:

Two props that cannot cross the boundaryJavaScript
// 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)} />;
}
Output
⨯ 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.