useEffect → fetch → loading spinner → another useEffect → another fetch → another spinner. React Server Components end this waterfall permanently.
What Are React Server Components?
RSCs run exclusively on the server. They can directly access databases, file systems, and APIs — then send the rendered HTML to the client. No loading spinners. No client-side fetch calls.
Why RSCs Change Everything
1. Direct Database Access in Components
// This component runs on the server — never ships to the browser
async function UserProfile({ userId }: { userId: string }) {
const user = await db.users.findUnique({ where: { id: userId } });
const posts = await db.posts.findMany({ where: { authorId: userId } });
return (
<div>
<h1>{user.name}</h1>
<p>{user.bio}</p>
<PostList posts={posts} />
</div>
);
}
No API route. No useEffect. No loading state. The HTML arrives ready.
2. Zero Client-Side JavaScript
// Server Component — 0 KB shipped to browser
async function ProductCatalog() {
const products = await db.products.findMany({
where: { active: true },
orderBy: { createdAt: 'desc' },
});
return (
<div className="grid grid-cols-3 gap-4">
{products.map(p => (
<div key={p.id}>
<img src={p.imageUrl} alt={p.name} />
<h3>{p.name}</h3>
<p>${p.price}</p>
</div>
))}
</div>
);
}
This component could render 10,000 products and ship 0 bytes of JavaScript.
3. Mix Server and Client Components
// Server Component (default in Next.js App Router)
async function Dashboard() {
const stats = await getStats(); // Server-side data fetch
return (
<div>
<h1>Dashboard</h1>
<StatsDisplay stats={stats} /> {/* Server — no JS */}
<InteractiveChart data={stats.chart} /> {/* Client — has JS */}
<RecentActivity items={stats.recent} /> {/* Server — no JS */}
</div>
);
}
// Client Component (opt-in with "use client")
"use client";
import { useState } from 'react';
function InteractiveChart({ data }) {
const [timeRange, setTimeRange] = useState('7d');
// ... interactive chart logic
}
4. Streaming and Suspense
async function Page() {
return (
<div>
<Header /> {/* Renders immediately */}
<Suspense fallback={<Skeleton />}>
<SlowDataSection /> {/* Streams in when ready */}
</Suspense>
<Suspense fallback={<Skeleton />}>
<AnotherSlowSection /> {/* Streams in independently */}
</Suspense>
<Footer /> {/* Renders immediately */}
</div>
);
}
5. Server Actions (Mutations)
async function CreatePost() {
async function createPost(formData: FormData) {
"use server";
const title = formData.get('title') as string;
const content = formData.get('content') as string;
await db.posts.create({ data: { title, content } });
revalidatePath('/posts');
redirect('/posts');
}
return (
<form action={createPost}>
<input name="title" required />
<textarea name="content" required />
<button type="submit">Publish</button>
</form>
);
}
RSC vs Traditional React
| Server Components | Client Components | |
|---|---|---|
| Runs on | Server only | Browser |
| JS shipped | 0 KB | Full component code |
| Data access | Direct DB/fs/API | Via fetch/API |
| Interactivity | None (static HTML) | Full (state, events) |
| Use for | Data display, layout | Forms, charts, modals |
Getting Started
RSCs are built into Next.js App Router (13.4+):
npx create-next-app@latest --app
# All components are Server Components by default
# Add "use client" only when you need interactivity
The Bottom Line
Server Components eliminate the client-server waterfall. Fetch data where it lives (the server), render HTML, send it to the browser. Less JavaScript, faster pages, simpler code.
Need data tools? I build scraping solutions. Check my Apify actors or email spinov001@gmail.com.
Top comments (0)