What Are Server Components?
React Server Components (RSC) render on the server and send HTML to the client. They can access backend resources directly without exposing APIs to the client.
Server vs Client Components
// Server Component (default in App Router)
// app/products/page.tsx
async function ProductsPage() {
// Direct database access - no API needed!
const products = await db.product.findMany();
return (
{products.map(product => (
))}
);
}
// Client Component
// components/AddToCartButton.tsx
'use client';
import { useState } from 'react';
export function AddToCartButton({ productId }: { productId: string }) {
const [loading, setLoading] = useState(false);
const addToCart = async () => {
setLoading(true);
await fetch('/api/cart', {
method: 'POST',
body: JSON.stringify({ productId })
});
setLoading(false);
};
return (
);
}When to Use Each
Server Components (Default)
- Fetching data
- Accessing backend resources
- Keeping sensitive info on server
- Large dependencies
Client Components ('use client')
- Interactivity (onClick, onChange)
- useState, useEffect, useContext
- Browser APIs
- Custom hooks with state
Data Fetching Patterns
// Parallel data fetching
async function Dashboard() {
// These run in parallel
const [user, orders, recommendations] = await Promise.all([
getUser(),
getOrders(),
getRecommendations()
]);
return (
);
}
// Sequential with Suspense
async function ProductPage({ id }: { id: string }) {
const product = await getProduct(id);
return (
{product.name}
}>
);
}
async function Reviews({ productId }: { productId: string }) {
const reviews = await getReviews(productId); // Loads independently
return ;
} Composition Patterns
// Server Component wrapping Client Component
// app/posts/[id]/page.tsx
import { CommentForm } from './CommentForm'; // client component
async function PostPage({ params }: { params: { id: string } }) {
const post = await getPost(params.id);
const comments = await getComments(params.id);
return (
{post.title}
{post.content}
{/* Pass server data to client component */}
{/* Server-rendered comments */}
);
}
// Client Component receiving children
'use client';
export function Modal({ children }: { children: React.ReactNode }) {
const [open, setOpen] = useState(false);
return (
<>
{open && (
{children} {/* Can be Server Components! */}
)}
);
}Server Actions
// Direct server mutations
// app/actions.ts
'use server';
export async function createPost(formData: FormData) {
const title = formData.get('title') as string;
const content = formData.get('content') as string;
await db.post.create({
data: { title, content }
});
revalidatePath('/posts');
}
// Use in component
import { createPost } from './actions';
function NewPostForm() {
return (
Caching and Revalidation
// Time-based revalidation
async function Products() {
const products = await fetch('https://api.example.com/products', {
next: { revalidate: 3600 } // Revalidate every hour
}).then(r => r.json());
return ;
}
// On-demand revalidation
'use server';
export async function updateProduct(id: string, data: ProductData) {
await db.product.update({ where: { id }, data });
revalidatePath('/products');
revalidateTag('products');
} Best Practices
- Start with Server Components, add 'use client' when needed
- Keep client components small and focused
- Pass serializable props between server and client
- Use Suspense for loading states
- Colocate data fetching with components
Conclusion
Server Components fundamentally change React development. They simplify data fetching, reduce bundle size, and improve performance. Embrace the server-first mindset.