Day 8 — Server vs. Client Components: The Paradigm Shift
🧭 Day 8 — Server vs. Client Components: The Paradigm Shift
Zero to Hero — Hands-on Next.js Tutorial
Welcome to Week 2! This week is all about Data and Rendering. We start with the most critical concept in the App Router: React Server Components (RSC).
🟦 1. The Default: Server Components
In the app directory, every component is a Server Component by default.
What does this mean?
- Zero Bundle Size: The code for this component is NOT sent to the browser.
- Direct Database Access: You can write
db.query()directly inside your component. - Async/Await: You can make your component
asyncandawaitdata. - No Interactivity: You CANNOT usage
useState,useEffect, oronClick.
// This is a Server Component. It runs ONLY on the server.
export default async function Page() {
const data = await db.posts.findMany(); // secure!
console.log("This prints in the server terminal, not the browser console");
return (
<div>
{data.map(post => <h2 key={post.id}>{post.title}</h2>)}
</div>
);
}
🟩 2. The Opt-In: Client Components
If you need interactivity (clicking, state, hooks), you must “opt-in” to the client.
Add the 'use client' directive at the top of your file.
When to use Client Components:
- Event listeners (
onClick,onChange). - State and Hooks (
useState,useEffect). - Browser-only APIs (
localStorage,window). - Class components (rare nowadays).
'use client'; // This marks the "Client Boundary"
import { useState } from 'react';
export default function Counter() {
const [count, setCount] = useState(0); // Hooks work here!
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
}
🟧 3. The “Network Boundary”
Think of your app as a tree.
The root is on the Server. You render Server Components down the tree.
Eventually, you hit a leaf that needs interactivity (like a Button or Search Bar).
You mark that file 'use client'.
Crucial Rule: A Client Component CANNOT import a Server Component.
- ❌ You cannot import
ServerCompintoClientComp. - ✅ You CAN pass a
ServerCompAS A CHILD (prop) to aClientComp.
The “Hole in the Donut” Pattern:
// ClientWrapper.tsx ('use client')
export default function ClientWrapper({ children }) {
const [isOpen, setIsOpen] = useState(false);
return <div>{isOpen && children}</div>;
}
// Page.tsx (Server)
import ClientWrapper from './ClientWrapper';
import ServerContent from './ServerContent';
export default function Page() {
return (
<ClientWrapper>
<ServerContent /> {/* This works! */}
</ClientWrapper>
);
}
🧪 Challenge: Day 8
- Create a
ServerComponent.tsxthat logs “Hello from Server”. - Create a
ClientComponent.tsxwith a button that alerts “Hello from Client”. - Try to import the Server Component inside the Client Component. Watch it fail (or degrade to client).
- Fix it by passing the Server Component as
childrento the Client Component in yourpage.tsx.
See you tomorrow for Data Fetching! 📡