React Server Components: What They Are and When to Use Them
React Server Components (RSC) run exclusively on the server — no hydration, no JavaScript sent to the client. They fundamentally change how you think about data fetching.
Server vs Client Components
| Server Component | Client Component | |
|---|---|---|
| Runs on | Server only | Server + Client |
| JS bundle | Not included | Included |
| Can use hooks | No | Yes |
| Can access DB | Yes | No (use API routes) |
| Default in Next.js 14 | Yes | No (needs 'use client') |
Server Component (Default)
// This is a Server Component by default in Next.js 14 App Router
// No 'use client' directive needed
async function UserDashboard({ userId }: { userId: string }) {
// Direct database access — runs only on server
const user = await prisma.user.findUnique({ where: { id: userId } });
const posts = await prisma.post.findMany({ where: { userId }, take: 5 });
// No useEffect, no loading state, no API call needed
return (
<div>
<h1>Welcome, {user?.name}</h1>
<PostList posts={posts} />
</div>
);
}
Client Component
'use client';
import { useState } from 'react';
// Only add 'use client' when you need:
// - useState, useEffect, or other hooks
// - Browser APIs (window, localStorage)
// - Event handlers (onClick, onChange)
// - Third-party client-only libraries
export function SearchInput({ onSearch }: { onSearch: (q: string) => void }) {
const [value, setValue] = useState('');
return (
<input
value={value}
onChange={e => { setValue(e.target.value); onSearch(e.target.value); }}
placeholder='Search...'
/>
);
}
Composing Server and Client
// Server Component wraps Client Component
async function Page() {
const initialData = await fetchData(); // Server-side
return (
<div>
<h1>Dashboard</h1>
{/* Pass server data as props to client component */}
<InteractiveChart data={initialData} />
{/* Server component renders alongside client component */}
<StaticSidebar />
</div>
);
}
Streaming with Suspense
import { Suspense } from 'react';
async function Page() {
return (
<div>
{/* Fast: renders immediately */}
<Header />
{/* Slow query: streams in when ready */}
<Suspense fallback={<Skeleton />}>
<SlowDataComponent />
</Suspense>
</div>
);
}
Server Actions (Mutations)
// No API route needed — server action runs on server
async function updateName(formData: FormData) {
'use server';
const name = formData.get('name') as string;
await prisma.user.update({ where: { id: session.user.id }, data: { name } });
revalidatePath('/dashboard');
}
export function NameForm() {
return (
<form action={updateName}>
<input name='name' />
<button type='submit'>Update</button>
</form>
);
}
When to Use Each
Default to Server Components. Add 'use client' only when you need interactivity, hooks, or browser APIs. The goal: push the client boundary as far down the component tree as possible.
Next.js App Router with Server Components ships pre-configured in the AI SaaS Starter Kit — proper RSC patterns throughout. $99 at whoffagents.com.
Top comments (0)