React Server Components
Answer
React Server Components (RSC) are components that render on the server and send HTML to the client, reducing JavaScript bundle size and enabling direct backend access.
Client vs Server Components
Basic Usage
// Server Component (default in Next.js 13+ app router)
// Can directly access databases, file systems, etc.
async function ProductList() {
const products = await db.query("SELECT * FROM products");
return (
<ul>
{products.map((product) => (
<li key={product.id}>{product.name}</li>
))}
</ul>
);
}
// Client Component (needs 'use client' directive)
("use client");
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount((c) => c + 1)}>Count: {count}</button>;
}
When to Use Which
| Use Case | Component Type |
|---|---|
| Database queries | Server |
| API tokens/secrets | Server |
| Static content | Server |
| Large dependencies | Server |
| onClick, onChange | Client |
| useState, useEffect | Client |
| Browser APIs | Client |
| Custom hooks with state | Client |
Composing Server and Client
// Server Component
import InteractiveButton from "./InteractiveButton";
async function BlogPost({ id }) {
const post = await fetchPost(id); // Server-side fetch
return (
<article>
<h1>{post.title}</h1>
<div>{post.content}</div>
{/* Client component nested in server component */}
<InteractiveButton postId={id} />
</article>
);
}
// Client Component
("use client");
function InteractiveButton({ postId }) {
const [liked, setLiked] = useState(false);
return (
<button onClick={() => setLiked(!liked)}>
{liked ? "❤️ Liked" : "🤍 Like"}
</button>
);
}
Passing Server Data to Client
// ✅ Pass serializable data as props
async function UserProfile() {
const user = await fetchUser(); // Server
return (
<div>
<h1>{user.name}</h1>
<ClientSettings initialSettings={user.settings} />
</div>
);
}
// ❌ Cannot pass functions, classes, Dates directly
// Must serialize first
Rendering Flow
Benefits
// 1. Zero client-side JavaScript for server components
// Large dependencies don't increase bundle size
// Server Component - marked won't be in client bundle
import { parse } from "marked"; // 50kb library
async function MarkdownPost({ content }) {
const html = parse(content); // Runs on server
return <div dangerouslySetInnerHTML={{ __html: html }} />;
}
// 2. Direct data access - no API endpoints needed
async function Dashboard() {
const [users, orders, revenue] = await Promise.all([
db.users.count(),
db.orders.findRecent(),
db.orders.calculateRevenue(),
]);
return (
<div>
<Stat label="Users" value={users} />
<Stat label="Orders" value={orders.length} />
<Stat label="Revenue" value={revenue} />
</div>
);
}
// 3. Secrets stay on server
async function ProtectedData() {
const data = await fetch("https://api.example.com", {
headers: {
Authorization: `Bearer ${process.env.API_SECRET}`,
},
});
// API_SECRET never sent to client
}
Patterns
// Pattern: Fetch then render (parallel fetches)
async function Page() {
const [posts, comments] = await Promise.all([fetchPosts(), fetchComments()]);
return (
<>
<PostList posts={posts} />
<CommentSection comments={comments} />
</>
);
}
// Pattern: Streaming with Suspense
async function Page() {
return (
<>
<Header /> {/* Immediate */}
<Suspense fallback={<PostsSkeleton />}>
<PostList /> {/* Streamed when ready */}
</Suspense>
<Suspense fallback={<CommentsSkeleton />}>
<Comments /> {/* Streamed when ready */}
</Suspense>
</>
);
}
Limitations
// ❌ No hooks that use state
// ❌ No browser-only APIs (window, localStorage)
// ❌ No event handlers
// ❌ Cannot be dynamically imported on client
// Server component cannot:
function ServerComponent() {
const [state, setState] = useState(); // ❌ Error
useEffect(() => {}); // ❌ Error
return <button onClick={() => {}}> // ❌ Error
}
Key Points
- Server Components run on server, zero bundle impact
- Use 'use client' directive for interactive components
- Server components can't use hooks or event handlers
- Pass only serializable props to client components
- Direct database/API access in server components
- Combine with Suspense for streaming
- Default in Next.js 13+ app router