React Server Components (RSC)
React Server Components (RSC) represent a paradigm shift in how React applications are built and rendered. They introduce a new type of component that executes exclusively on the server, allowing for smaller client bundles, direct access to backend resources, and improved performance.
RSC vs Client Components
Historically, all React components were what we now call Client Components. They run on the client (browser), meaning their code is bundled and sent over the network.
Server Components, on the other hand, stay on the server. Their code is never included in the JavaScript bundle downloaded by the browser. Instead, they render into a special serialization format that describes the UI, which is then sent to the client.
Key Differences
| Feature | Server Components | Client Components |
| :--- | :--- | :--- |
| Execution | Server only | Client (and SSR on server) |
| Bundle Size | Zero impact on client bundle | Adds to client bundle |
| State / Effects | Cannot use useState, useEffect | Can use all hooks |
| Interactivity | Non-interactive (static output) | Fully interactive |
| Data Fetching | Direct access to database/backend | Requires network requests |
Directives: use client and use server
To differentiate between the two paradigms, React relies on directives.
"use client"
By default in modern frameworks like Next.js App Router, components are Server Components. To make a component a Client Component, you place the "use client" directive at the very top of the file.
"use client";
import { useState } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>Count: {count}</button>;
}
"use server"
The "use server" directive is primarily used for Server Actions—functions that can be called from client components but execute on the server.
// actions.js
"use server";
export async function saveUser(data) {
// Direct database access!
await db.users.insert(data);
}
Data Fetching
One of the most powerful features of Server Components is the ability to use async/await directly within the component to fetch data. You no longer need useEffect or complex data-fetching libraries for initial render data.
// This is a Server Component (no directive needed by default in Next.js)
export default async function UserProfile({ userId }) {
// Secure, direct database call on the server
const user = await db.query(`SELECT * FROM users WHERE id = ${userId}`);
return (
<div>
<h1>{user.name}</h1>
<p>Email: {user.email}</p>
</div>
);
}
Serialization and Passing Props
When a Server Component renders a Client Component, it can pass props. However, because the server must send this data over the network to the client, props passed from server to client must be serializable.
You can pass:
- Strings, Numbers, Booleans
- Arrays and Objects containing serializable data
- Promises (handled nicely by React's
usehook)
You cannot pass:
- Functions (callbacks)
- Classes instances
The Composition Pattern
To get around serialization limits, you can pass Server Components as children to Client Components. The client component doesn't need to know what the child is; it just receives the rendered React nodes.
// ServerComponent.jsx
import ClientLayout from './ClientLayout';
export default function Page() {
return (
<ClientLayout>
{/* This Server Component renders on the server and is passed as a child */}
<HeavyServerWidget />
</ClientLayout>
);
}
React Server Components allow developers to build the interactive parts of an app with Client Components while keeping heavy dependencies and data logic on the server, resulting in faster and more secure applications.