Understanding React Server Components
A deep dive into React Server Components, how they work, and when to use them in your Next.js applications.
Understanding React Server Components
React Server Components (RSC) represent a paradigm shift in how we build React applications. They allow components to run exclusively on the server, reducing bundle size and improving performance.
What Are Server Components?
Server Components are React components that:
- Run only on the server
- Can directly access backend resources (databases, file systems)
- Don't add to your JavaScript bundle
- Cannot use hooks like
useStateoruseEffect
Server vs Client Components
Understanding when to use each is crucial:
Server Components (Default in Next.js App Router)
// This runs on the server
async function ArticleList() {
const articles = await db.article.findMany();
return (
<ul>
{articles.map(article => (
<li key={article.id}>{article.title}</li>
))}
</ul>
);
}
Client Components
'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 Each
Use Server Components For:
- Fetching data
- Accessing backend resources directly
- Keeping sensitive information on the server
- Large dependencies that don't need client-side interactivity
Use Client Components For:
- Interactivity (onClick, onChange, etc.)
- State management (useState, useReducer)
- Browser APIs (localStorage, geolocation)
- Custom hooks that use state or effects
Composition Patterns
You can nest Client Components inside Server Components:
// Server Component
async function BlogPost({ id }: { id: string }) {
const post = await getPost(id);
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
{/* Client Component for interactivity */}
<LikeButton postId={id} />
<CommentSection postId={id} />
</article>
);
}
Data Fetching Patterns
Parallel Data Fetching
async function Dashboard() {
// These run in parallel!
const [user, posts, analytics] = await Promise.all([
getUser(),
getPosts(),
getAnalytics(),
]);
return (
<div>
<UserProfile user={user} />
<PostList posts={posts} />
<AnalyticsChart data={analytics} />
</div>
);
}
Sequential When Needed
async function UserPosts({ userId }: { userId: string }) {
const user = await getUser(userId);
// This depends on the user
const posts = await getPostsByAuthor(user.id);
return <PostList posts={posts} author={user.name} />;
}
Performance Benefits
- Reduced Bundle Size: Server Components aren't included in the client bundle
- Faster Initial Load: HTML is rendered on the server
- Better SEO: Content is available immediately for crawlers
- Reduced Waterfall: Data fetching happens on the server, close to your database
Common Pitfalls
❌ Don't Import Server-Only Code in Client Components
// This will fail!
'use client';
import { db } from './database'; // Server-only!
✅ Do Pass Data as Props
// Server Component
async function Page() {
const data = await db.getData();
return <ClientComponent data={data} />;
}
Conclusion
React Server Components are a powerful tool for building performant applications. By understanding when to use server vs client components, you can create applications that are both fast and interactive.
The key is to keep as much as possible on the server while only adding client-side JavaScript where interactivity is truly needed.