Server Components & Next.js
React Server Components, the use client boundary, streaming, and Next.js App Router with server actions.
🧑🏫 Sabse pehle — simple mein samjho#
Socho Zomato ke restaurant page ki tarah — thali kitchen (server) mein ban ke, ready plate ban ke aapke table pe aati hai, aap ko har cheez khud banani nahi padti. Waise hi Server Component kitchen (server) pe render hota hai aur ready HTML bhej deta — fast khulta hai aur Google pe SEO bhi achhi aati. Par jahan button dabana, typing, ya live cricket score jaise interaction chahiye, wahan Client Component lagta — us file ke top pe 'use client' likhna padta. Next.js ka App Router in dono ko manage karta, kaun server pe aur kaun browser pe chale. Yaad rakho: state/effect chahiye = client, warna default server.
// app/page.jsx — Server Component (default, no 'use client')
async function Page() {
const dishes = await getDishes(); // seedha DB/API, server pe
return <Menu items={dishes} />; // ready HTML client ko jaata
}
// app/LikeButton.jsx
'use client'; // interaction chahiye
function LikeButton() {
const [liked, setLiked] = useState(false);
return <button onClick={() => setLiked(true)}>{liked ? '❤️' : '🤍'}</button>;
}
Yaad rakho: state/button/typing chahiye toh 'use client'; warna default server component (fast + SEO).
React Server Components (RSC)#
Server Components render on the server and ship their result (a serialized RSC payload) to the client — zero client JS for that component. They can read the filesystem, hit a database, or await directly, but cannot use state, effects, refs, or browser APIs.
| Server Component | Client Component | |
|---|---|---|
| Runs | Server (build/request) | Server (SSR) + browser (hydrate) |
useState/useEffect |
❌ | ✅ |
Event handlers (onClick) |
❌ | ✅ |
async/await in body |
✅ | ❌ |
| Access DB/filesystem/secrets | ✅ | ❌ |
| Ships JS to client | No | Yes |
| Default in App Router | ✅ | Opt-in via "use client" |
// app/page.jsx — Server Component by default
import db from "@/lib/db";
export default async function Page() {
const posts = await db.post.findMany(); // runs on the server only
return (
<main>
{posts.map((p) => <Article key={p.id} post={p} />)}
<LikeButton /> {/* a Client Component for interactivity */}
</main>
);
}
The "use client" boundary 🎯#
"use client" at the top of a file marks it (and its import subtree) as a Client Component. It's a boundary, not a per-component tag: everything imported into a client module is also client. Server Components can render Client Components and pass serializable props; the reverse (importing a Server Component into a client one) isn't allowed, but you can pass a server component as children/props.
"use client";
import { useState } from "react";
export default function LikeButton() {
const [likes, setLikes] = useState(0);
return <button onClick={() => setLikes((l) => l + 1)}>👍 {likes}</button>;
}
⚠️ Props crossing the server→client boundary must be serializable — you can't pass functions, class instances, or Dates-as-methods. Keep client components at the leaves to minimize shipped JS. 🟢
flowchart TD
A["Server Component tree (no client JS)"] --> B["Fetches data, renders HTML/RSC payload"]
B --> C["Reaches 'use client' boundary"]
C --> D["Client Component (hydrated in browser)"]
D --> E["useState / onClick / effects work here"]
Streaming & Suspense#
The server can stream HTML in chunks. Wrap slow parts in <Suspense> with a fallback — the shell flushes immediately and slow subtrees stream in when ready, improving TTFB and perceived performance.
import { Suspense } from "react";
export default function Page() {
return (
<>
<Header /> {/* flushed immediately */}
<Suspense fallback={<Spinner />}>
<SlowFeed /> {/* streams in when its data resolves */}
</Suspense>
</>
);
}
Next.js App Router#
The app/ directory is file-system routed. Special files: page.jsx (route UI), layout.jsx (shared, nested, persistent wrapper), loading.jsx (Suspense fallback), error.jsx (error boundary), route.js (API handler). Components are Server Components by default.
| Rendering | How | Use for |
|---|---|---|
| SSR | Rendered per request | Personalized/dynamic pages |
| SSG | Rendered at build time | Static content (marketing, docs) |
| ISR | Static + periodic revalidate |
Mostly-static that updates occasionally |
Control it per-fetch/segment: fetch(url, { cache: "force-cache" }) (static/SSG), { cache: "no-store" } (SSR per request), or { next: { revalidate: 60 } } (ISR).
// app/dashboard/layout.jsx — wraps all nested routes, preserved across navigations
export default function DashboardLayout({ children }) {
return <section><Sidebar />{children}</section>;
}
Server actions#
"use server" marks a function that runs only on the server, callable from a form or client component — no manual API route. Great for mutations; pair with revalidatePath/revalidateTag to refresh cached data.
// app/actions.js
"use server";
import { revalidatePath } from "next/cache";
export async function addTodo(formData) {
await db.todo.create({ data: { text: formData.get("text") } });
revalidatePath("/todos"); // refresh the cached page
}
// in a Server Component:
<form action={addTodo}>
<input name="text" />
<button>Add</button>
</form>
🎯 Server actions run on the server with access to secrets/DB, are invoked via a form action or from client code, and integrate with the App Router cache — but validate/authorize inside them; they're a public endpoint.
Interview Q&A#
Q1. What can't a Server Component do?
Use state, effects, refs, event handlers, or browser APIs — it doesn't run or hydrate on the client. For interactivity you cross into a Client Component via "use client".
Q2. What does "use client" actually do?
It marks a module (and its import subtree) as a Client Component boundary — that code is bundled and hydrated in the browser. It's a boundary directive, not a per-component annotation.
Q3. Why do RSCs reduce bundle size?
Their code never ships to the client — only the rendered output (RSC payload). Only components inside a "use client" boundary send JavaScript, so keeping interactivity at the leaves minimizes bundle size.
Q4. SSR vs SSG vs ISR in Next.js App Router?
SSR renders per request (dynamic); SSG renders at build time (static); ISR serves static output but revalidates on an interval. In App Router you choose via fetch cache/revalidate options per segment.
Q5. What is a server action?
A "use server" function that executes only on the server, callable from a form action or client code without a hand-written API route. Ideal for mutations, and it can revalidate cached routes — but must authorize its own inputs.