React Server Components: Zero-JS Components
React Server Components let you render parts of your tree ahead of time in a separate server environment, before bundling them into client JavaScript. The biggest win is not that every page becomes “static,” but that only truly interactive code must cross the server-client boundary.
Quick Decision Guide
React Server Components keep non-interactive UI on the server and out of the client bundle.
- Use Server Components for data fetching, server-only code, and non-interactive UI - Use Client Components for state, effects, event handlers, and browser APIs - In Next.js App Router, components are server by default unless a 'use client' boundary opts them into client behavior
Interview signal: RSC is not just about faster HTML. It is about reducing how much JavaScript the browser must download, parse, and hydrate.
What are React Server Components?
React Server Components (RSC) are components that render in a server environment ahead of time, before bundling, and do not themselves become hydrated client code.
🔥 Insight
RSC is a bundle-boundary feature as much as a rendering feature. The question is not only where code runs, but whether that code needs to ship to the browser at all.
Key Characteristics
Important Clarification
Server Components are not the same thing as classic SSR components.
These often work together, but they solve different problems.
What Problem They Solve
Without RSC, many components that only fetch and display data still end up in the client-side React tree.
That leads to:
RSC reduces that waste by keeping non-interactive logic on the server.
Server Components vs Client Components
In frameworks that support RSC, the tree is server-first by default, and 'use client' creates a client boundary in the module dependency graph.
Server Components
// app/page.jsx
export default async function Page() {
const res = await fetch('https://api.example.com/data');
const data = await res.json();
return <div>{data.content}</div>;
}Good fit for Server Components
Not allowed / not appropriate in Server Components
useStateuseEffectwindow, document, localStorageonClickClient Components
'use client';
import { useState } from 'react';
export default function Button() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}Good fit for Client Components
🔥 Mental Model
Important Clarification
'use client' does not mark just one component function. It creates a client boundary for that module and the client-side subtree it pulls in.
Data Fetching with Server Components
One of the biggest RSC benefits is that data fetching can happen directly in the server component itself.
Direct Database or ORM Access
import { db } from '@/lib/db';
export default async function ProductsPage() {
const products = await db.products.findMany();
return (
<div>
{products.map(product => (
<ProductCard key={product.id} product={product} />
))}
</div>
);
}Fetch API
export default async function PostsPage() {
const res = await fetch('https://api.example.com/posts', {
cache: 'no-store'
});
const posts = await res.json();
return <PostsList posts={posts} />;
}Parallel Fetching
export default async function Page() {
const [users, posts, comments] = await Promise.all([
fetchUsers(),
fetchPosts(),
fetchComments()
]);
return (
<div>
<UsersList users={users} />
<PostsList posts={posts} />
<CommentsList comments={comments} />
</div>
);
}Why This Matters
Fetching on the server helps avoid pushing unnecessary request orchestration into the client. It also makes server-side parallelism much easier to express.
Important Nuance
RSC does not mean “never call an API.” It means you can fetch in the server environment directly, using the best server-side primitive for your stack: fetch, a database/ORM, or filesystem access in supported runtimes.
Composition Patterns
The common pattern is: keep the outer structure and data-heavy parts on the server, then insert small interactive Client Components where needed.
Server Component Composing a Client Component
// Server Component
import InteractiveButton from '@/components/Button';
export default async function Page() {
const product = await fetchProduct();
return (
<div>
<h1>{product.name}</h1>
<ProductDescription product={product} />
<InteractiveButton />
</div>
);
}// Client Component
'use client';
export default function InteractiveButton() {
return <button>Add to cart</button>;
}Passing Server Data to a Client Component
// Server Component
export default async function Page() {
const data = await fetchData();
return <ClientComponent data={data} />;
}'use client';
export default function ClientComponent({ data }) {
return <div>{data.title}</div>;
}Common Mistake
Trying to use client-only hooks inside a Server Component:
// ❌ Invalid in a Server Component
export default function Page() {
const [state, setState] = useState(0);
return <div>{state}</div>;
}Better Pattern
Move only the interactive part behind a client boundary:
'use client';
export function ClientPart() {
const [state, setState] = useState(0);
return <div>{state}</div>;
}export default function Page() {
return <ClientPart />;
}🔥 Insight
A strong RSC architecture does not make the whole page client-side just because one button needs onClick.
Benefits
Benefits of React Server Components
Performance Benefits
Developer Benefits
Senior-Level Insight
The biggest win is usually not “zero JS everywhere.” The biggest win is reducing unnecessary client code for components that never needed to be interactive in the first place.
Trade-offs and Misconceptions
Misconception: “RSC means no JavaScript at all”
Not true. Client Components still ship JavaScript, hydrate, and run in the browser.
Misconception: “RSC replaces SSR”
Not true. Server Components and SSR often work together. SSR affects HTML delivery; RSC affects what code becomes part of the client bundle.
Real Trade-offs
Interview Takeaway
A strong answer sounds like this:
> React Server Components keep non-interactive rendering and data access on the server, while Client Components handle state, events, and browser APIs. The real benefit is smaller bundles and less hydration, not just server rendering.
Key Takeaways
'use client' creates a client boundary