React Server Components (RSC), introduced experimentally in 2020 and stabilised in Next.js 13’s App Router in 2023, are the most significant architectural change to React since Hooks. They shift rendering back toward the server without sacrificing the component model — and the mental model takes time to adjust to.
The Core Idea
In the App Router, every component is a Server Component by default. Server Components run only on the server — they can access databases, file systems, and environment variables directly. They never run in the browser, so they add zero JavaScript to the client bundle.
// app/posts/page.tsx — Server Component (default)
// This function runs on the server, never in the browser
async function PostsPage() {
// Direct database access — no API route needed
const posts = await db.query('SELECT * FROM posts ORDER BY created_at DESC');
return (
<ul>
{posts.map(post => (
<li key={post.id}><PostCard post={post} /></li>
))}
</ul>
);
}
Client Components: The Explicit Exception
When you need interactivity, browser APIs, or React state, add "use client" at the top of the file:
"use client";
import { useState } from 'react';
export function LikeButton({ initialLikes }: { initialLikes: number }) {
const [likes, setLikes] = useState(initialLikes);
return <button onClick={() => setLikes(l => l + 1)}>♥ {likes}</button>;
}
Composing Server and Client Components
Server Components can render Client Components. Client Components cannot render Server Components (because Client Components run in the browser where there is no server). The boundary flows one way: server → client.
Streaming with Suspense
Wrap slow server components in <Suspense> to stream their content progressively — the shell renders immediately and slow parts stream in as they resolve:
export default function Dashboard() {
return (
<>
<h1>Dashboard</h1>
<Suspense fallback={<Skeleton />}>
<SlowAnalyticsWidget /> {/* streams in when ready */}
</Suspense>
</>
);
}
Server Actions
Server Actions (stable in Next.js 14) allow form submissions and mutations to call server functions directly, without writing API route boilerplate:
"use server";
export async function createPost(formData: FormData) {
const title = formData.get('title') as string;
await db.insert({ title, createdAt: new Date() });
revalidatePath('/posts');
}
When to Use RSC vs Traditional API Routes
- RSC for reads (fetching data for rendering)
- Server Actions for mutations (forms, button clicks that change data)
- API routes for public APIs consumed by mobile apps or third parties
React Server Components require a new mental model, but the payoff — smaller client bundles, simpler data fetching, and better performance by default — makes them the right architecture for most Next.js applications in 2023.