Skip to content
C.W.K.
Stream
Lesson 06 of 08 · published

The Serialization Boundary

~22 min · serialization, props, boundary

Level 0Curious
0 XP0/68 lessons0/11 achievements
0/120 XP to next level120 XP to go0% complete

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, booleansFunctions
Arrays, plain objectsClass instances
Dates (serialized as strings)Symbols
null / undefinedDOM nodes / streams
FormDataCircular 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.

Code

Pass serializable props·tsx
// app/page.tsx — Server
import { LikeButton } from '@/components/LikeButton';

export default async function Page() {
  const post = await getPost();
  return (
    <article>
      <h1>{post.title}</h1>
      {/* ✅ string + number — serializable */}
      <LikeButton postId={post.id} initialCount={post.likes} />

      {/* ❌ Functions can't cross — won't compile */}
      {/* <LikeButton onLike={() => handleLike()} /> */}
    </article>
  );
}
Client receives plain props·tsx
'use client';
import { useState } from 'react';

export function LikeButton({
  postId,
  initialCount,
}: {
  postId: string;
  initialCount: number;
}) {
  const [count, setCount] = useState(initialCount);
  return (
    <button onClick={() => setCount(c => c + 1)}>♥ {count}</button>
  );
}
Pass server JSX as children·tsx
// app/page.tsx — Server
import { Accordion } from '@/components/Accordion';
import { ProductDetails } from '@/components/ProductDetails';

export default async function Page() {
  const product = await getProduct();
  return (
    <Accordion>
      {/* Server-rendered subtree handed to a Client wrapper */}
      <ProductDetails product={product} />
    </Accordion>
  );
}

// components/Accordion.tsx — Client
'use client';
import { useState } from 'react';

export function Accordion({ children }: { children: React.ReactNode }) {
  const [open, setOpen] = useState(false);
  return (
    <>
      <button onClick={() => setOpen(!open)}>Toggle</button>
      {open && children}
    </>
  );
}

External links

Exercise

Take a Client Component that currently receives a callback prop. Replace the callback with a Server Action passed in (covered later) or with the children pattern. Document why the rewrite improved the boundary.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.