Skip to main content

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 CaseComponent Type
Database queriesServer
API tokens/secretsServer
Static contentServer
Large dependenciesServer
onClick, onChangeClient
useState, useEffectClient
Browser APIsClient
Custom hooks with stateClient

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