React Server Components Explained
What are 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, enabling new patterns for data fetching and reducing client-side JavaScript.
Key Benefits
Reduced Bundle Size
Server Components don't ship JavaScript to the client, which means:
- Smaller bundle sizes
- Faster initial page loads
- Better performance on low-powered devices
Direct Backend Access
Server Components can directly access backend resources:
// This runs only on the server!
async function UserProfile({ userId }: { userId: string }) {
// Direct database access
const user = await db.users.findUnique({
where: { id: userId },
});
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}Zero Client Runtime
Dependencies used in Server Components don't add to the client bundle:
import { marked } from 'marked'; // Only runs on server
import { format } from 'date-fns'; // Won't increase client bundle
export default async function BlogPost({ slug }: { slug: string }) {
const post = await getPost(slug);
const html = marked(post.content);
const date = format(new Date(post.date), 'MMMM dd, yyyy');
return (
<article>
<h1>{post.title}</h1>
<time>{date}</time>
<div dangerouslySetInnerHTML={{ __html: html }} />
</article>
);
}Server vs Client Components
When to Use Server Components
Use Server Components when:
- You need to fetch data
- You're accessing backend resources
- You want to keep large dependencies on the server
- The component doesn't need interactivity
When to Use Client Components
Use Client Components when:
'use client'; // This directive marks it as a Client Component
import { useState } from 'react';
export function Counter() {
const [count, setCount] = useState(0);
return (
<button type="button" onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
}You need:
- Interactive features (onClick, onChange)
- Browser APIs (localStorage, geolocation)
- React hooks (useState, useEffect)
- Class components
Composition Patterns
Passing Server Components to Client Components
You can pass Server Components as children to Client Components:
// ClientWrapper.tsx
'use client';
export function ClientWrapper({ children }: { children: React.ReactNode }) {
return <div className="interactive-wrapper">{children}</div>;
}
// page.tsx (Server Component)
export default function Page() {
return (
<ClientWrapper>
<ServerComponent /> {/* This stays on the server! */}
</ClientWrapper>
);
}Data Fetching Patterns
Parallel data fetching with Server Components:
async function UserData({ userId }: { userId: string }) {
const user = await fetchUser(userId);
return <div>{user.name}</div>;
}
async function Posts({ userId }: { userId: string }) {
const posts = await fetchPosts(userId);
return <ul>{posts.map((p) => <li key={p.id}>{p.title}</li>)}</ul>;
}
export default function Profile({ userId }: { userId: string }) {
return (
<div>
<Suspense fallback={<Skeleton />}>
<UserData userId={userId} />
</Suspense>
<Suspense fallback={<Skeleton />}>
<Posts userId={userId} />
</Suspense>
</div>
);
}Best Practices
Keep Server-Only Code Secure
Use the server-only package to ensure server code never leaks to the client:
import 'server-only';
export async function getSecretKey() {
return process.env.SECRET_KEY;
}Optimize Data Fetching
Use React's built-in caching:
import { cache } from 'react';
export const getPost = cache(async (slug: string) => {
const post = await db.posts.findUnique({ where: { slug } });
return post;
});Handle Loading States
Use Suspense boundaries for better UX:
<Suspense fallback={<LoadingSpinner />}>
<AsyncComponent />
</Suspense>Conclusion
React Server Components are a powerful addition to the React ecosystem. They enable new patterns for building faster, more efficient applications while maintaining the component model we love.
Start experimenting with Server Components in your Next.js projects today!