Serializable Props — What Can Cross the Server-Client Boundary
In this tutorial, you will learn about Serializable Props. We cover key concepts, practical examples, and best practices to help you master this topic.
Props passed from Server Components to Client Components must be serializable — plain data types that can be converted to JSON and back without losing information.
What You'll Learn
You will understand what types are serializable, what types cause errors, how to handle dates and special types, and patterns for preparing data for client consumption.
Why It Matters
Passing a function or class instance from a Server to a Client Component throws a runtime error. Understanding Serialization prevents bugs and keeps your data flow predictable.
Real-World Use
DodaZIP's file manager serializes file metadata (name, size, date, permissions) as plain objects before passing them to the Client Component for rendering.
flowchart LR
A[Server Component] --> B{Data to pass}
B --> C[Plain Object]
B --> D[Array]
B --> E[String/Number/Boolean]
B --> F[Date]
B --> X[Function]
B --> Y[Class Instance]
B --> Z[Symbol]
C --> G[Client Component]
D --> G
E --> G
F --> H[Serialized as ISO string]
X --> I[RUNTIME ERROR]
Y --> I
Z --> I
style B fill:#1e293b,color:#fff
style I fill:#dc2626,color:#fff
style G fill:#0f172a,color:#fff
Serializable Types
These types are safe to pass from Server to Client Components.
// Server Component passing serializable props
async function Dashboard() {
const user = await db.users.findById(id);
const recentOrders = await db.orders.findRecent(id);
return (
<ClientDashboard
userName={user.name} // string
age={user.age} // number
isActive={user.status === 'active'} // boolean
tags={user.tags} // array
metadata={{ // plain object
lastLogin: user.lastLogin.toISOString(), // date as string
role: user.role
}}
preferences={null} // null
id={undefined} // undefined
/>
);
}
Expected output: All props pass safely from the Server Component to the Client Component. The date is converted to an ISO string before passing.
Non-Serializable Types That Cause Errors
These types throw a runtime error when passed across the boundary.
async function Page() {
function handleClick() { // Function
console.log('clicked');
}
class UserModel { // Class instance
constructor(data) { this.data = data; }
}
const userInstance = new UserModel({ name: 'Alice' });
const mySymbol = Symbol('unique'); // Symbol
return (
<ClientComponent
onClick={handleClick} // ERROR
userModel={userInstance} // ERROR
symbol={mySymbol} // ERROR
/>
);
}
Expected output: Each of these props causes a runtime error: "Only plain objects, arrays, strings, numbers, booleans, null, and undefined are allowed as props from Server to Client Components. Functions, class instances, and symbols are not serializable."
Handling Dates and Complex Objects
Convert non-serializable types to their serializable equivalents before passing.
// BAD: Passing Date object directly
async function EventPage() {
const event = await db.events.findById(id);
return <EventCard
startDate={event.startDate} // Date object — not serializable
endDate={event.endDate} // Date object — not serializable
/>;
}
// GOOD: Convert to ISO strings
async function EventPage() {
const event = await db.events.findById(id);
return <EventCard
startDate={event.startDate.toISOString()}
endDate={event.endDate.toISOString()}
/>;
}
// Client Component receives ISO strings
'use client';
function EventCard({ startDate, endDate }) {
const start = new Date(startDate); // Parse locally
const end = new Date(endDate);
return (
<div>
<p>Start: {start.toLocaleDateString()}</p>
<p>End: {end.toLocaleDateString()}</p>
</div>
);
}
Expected output: The EventCard receives date strings and converts them to Date objects client-side for formatting.
Preparing Data for Client Components
Transform complex data into serializable shapes before passing.
async function UsersPage() {
const users = await db.users.findAll();
// Transform data for client consumption
const serializableUsers = users.map(user => ({
id: user.id.toString(), // Convert ObjectId to string
name: user.name,
email: user.email,
role: user.role,
createdAt: user.createdAt.toISOString(),
permissions: user.permissions.map(p => ({
resource: p.resource,
action: p.action
})),
isAdmin: user.role === 'admin'
}));
return <UserTable users={serializableUsers} />;
}
Expected output: The user data is fully serializable. ObjectId fields are converted to strings. Nested permission objects are plain objects. Boolean flags are computed server-side.
Passing Callbacks via Server Actions
Instead of passing functions as props, use Server Actions for server-side callbacks.
// Instead of passing a function prop, use a Server Action
'use server';
export async function deleteUser(userId) {
await db.users.delete(userId);
revalidatePath('/users');
return { success: true };
}
// Client Component calls the Server Action
'use client';
function UserRow({ user }) {
const [pending, setPending] = useState(false);
async function handleDelete() {
setPending(true);
await deleteUser(user.id);
setPending(false);
}
return (
<tr>
<td>{user.name}</td>
<td>{user.email}</td>
<td>
<button onClick={handleDelete} disabled={pending}>
{pending ? 'Deleting...' : 'Delete'}
</button>
</td>
</tr>
);
}
Expected output: The deleteUser Server Action handles the server-side logic. The Client Component calls it without needing a function prop to cross the boundary.
Common Mistakes
Passing Date objects directly: Date objects are not serializable. Convert them to ISO strings or timestamps before passing to Client Components.
Passing Mongoose or Prisma model instances: Database models contain non-serializable methods and circular references. Convert them to plain objects with
.toJSON()or.lean().Trying to pass event handlers as props: Functions cannot cross the boundary. Use Server Actions or client-side event handlers defined in the Client Component.
Passing large objects unnecessarily: Large serialized objects increase the HTML size. Pass only the data the Client Component needs.
Forgetting that Map and Set are not serializable: Convert Map and Set to plain objects or arrays before passing them across the boundary.
Practice Questions
- What types can be passed as props from Server to Client Components?
Strings, numbers, booleans, null, undefined, plain objects, arrays, and Date objects converted to strings.
- Why can't functions cross the server-client boundary?
Functions cannot be serialized to JSON. They contain closure scope and executable code that cannot be transferred as data.
- How do you pass a date to a Client Component?
Convert it to an ISO string using toISOString() on the server, then parse it back to a Date object on the client.
- What happens if you pass a non-serializable value?
React throws a runtime error during rendering. The error message specifies which prop caused the issue.
- How do you handle MongoDB ObjectId across the boundary?
Convert ObjectId to a string using .toString() before passing it as a prop.
Challenge
Create a function that recursively transforms a complex database result (with nested objects, dates, and ObjectIds) into a fully serializable object safe for passing to Client Components.
Frequently Asked Questions
Mini Project
Build a Server Component that queries a database with complex nested data, transforms it into a serializable shape, and passes it to a Client Component that renders an interactive data table with sorting.
What's Next
Learn about Context Server and how to manage shared state patterns.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro