
React Server Components: A Deep Dive
The mental model shift behind RSC β where components live, how they compose with Client Components, and what this means for Next.js architecture.
Table of Contents
Table of Contents (4 sections)
What Are Server Components?
React Server Components (RSC) run exclusively on the server. They have zero JavaScript bundle impact on the client, can directly access server resources like databases and file systems, and are async by default. They are NOT the same as SSR β with SSR, the same component code runs on both server and client; with RSC, Server Components never run on the client at all.
1// No "use client" directive = Server Component by default in Next.js App Router23async function PostsPage() {4 // Direct database access β no API call needed, no bundle cost5 const posts = await db.query(6 'SELECT id, title, excerpt FROM posts WHERE published = true'7 );89 return (10 <ul>11 {posts.map((post) => (12 <li key={post.id}>13 <h2>{post.title}</h2>14 <p>{post.excerpt}</p>15 </li>16 ))}17 </ul>18 );19}2021export default PostsPage;Caching in Server Components
Next.js automatically caches fetch() calls in Server Components with the same URL. Use { cache: 'no-store' } for real-time data, or { next: { revalidate: 60 } } for ISR-style revalidation on a per-request basis.
ββWe want React to be the foundation for any UI you build, regardless of where it runs β browser, server, native. Server Components are our bet on how to make that real.β
β Dan Abramov
Client vs Server: Side-by-Side
The decision rule is simple: if a component needs interactivity, browser APIs, or event listeners β it's a Client Component. Everything else is a Server Component by default.
// No "use client" β runs on server, zero bundle cost
import { getUserById } from '@/lib/db';
export async function UserProfile({ userId }: { userId: string }) {
const user = await getUserById(userId); // Direct DB call
return (
<div>
<h1>{user.name}</h1>
<p>{user.bio}</p>
</div>
);
}Common Pitfalls
RSC has a few sharp edges that catch developers off guard. These three mistakes account for most of the confusion when teams first adopt the App Router.
You cannot import a Server Component directly inside a Client Component. The fix is to pass Server Components as children props instead.
// β Breaks β can't import server component into client component
'use client';
import ServerComp from './ServerComp'; // Build error
// β
Correct β compose via children
'use client';
export function ClientWrapper({ children }: { children: React.ReactNode }) {
return <div className="wrapper">{children}</div>;
}
// In a Server Component above:
<ClientWrapper>
<ServerComp /> {/* Works β composed at server render time */}
</ClientWrapper>TypeScript Integration
// Page props in Next.js 15+ β params and searchParams are Promises
interface PageProps {
params: Promise<{ slug: string }>;
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
}Starting from Next.js 15, params and searchParams in page.tsx are Promises β always await them. TypeScript will surface this as a type error if you skip the await.
Related Content
Explore related articles, projects, and tools.