Chapter 4 of 11
This is the most important concept in modern Next.js — and the one most beginners find confusing. Every component you build runs in one of two places: on the server, or in the browser. By default, everything is a Server Component.
ANALOGY
Real-world analogy: A restaurant kitchen vs dining room. Server Components are like the kitchen — the customer never sees it, but it's where all the real work happens (fetching data, processing, database queries). Client Components are like the dining room — everything the customer interacts with directly (buttons, forms, animations). Both are essential. They serve different purposes.
| Feature | Server Component | Client Component |
|---|---|---|
| Default? | ✅ Yes — no special syntax needed | ❌ Must add 'use client' at top of file |
| Runs where? | On the server (Node.js) | In the user's browser |
| Can fetch data directly? | ✅ Yes — async/await works perfectly | ⚠️ Only via useEffect or libraries |
| Can access databases? | ✅ Yes — safely, never sent to user | ❌ Never — would expose credentials |
| Can use useState / hooks? | ❌ No | ✅ Yes |
| Can handle click events? | ❌ No | ✅ Yes |
| Good for SEO? | ✅ Yes — pre-rendered HTML | ⚠️ Partial — initial load may be empty |
// No "use client" at the top = this is a Server Component
// It runs on the server — this code never reaches the user's browser
export default async function CoursesPage() {
// You can use async/await directly — this runs on the server
const response = await fetch("https://api.example.com/courses");
const courses = await response.json();
// The HTML is generated on the server and sent to the browser already rendered
return (
<ul>
{courses.map(course => (
<li key={course.id}>{course.title}</li>
))}
</ul>
);
}
// This is fast, secure, and great for SEO.
// The user gets fully rendered HTML — not an empty page that loads data later."use client"; // ← This one line makes it a Client Component
import { useState } from "react";
export default function LikeButton() {
// useState holds a value that, when changed, causes the component to re-render
const [likes, setLikes] = useState(0);
const [liked, setLiked] = useState(false);
function handleClick() {
if (!liked) {
setLikes(likes + 1);
setLiked(true);
}
}
return (
<button onClick={handleClick}
style={{ color: liked ? "red" : "gray" }}>
♥ {likes} {liked ? "Liked!" : "Like"}
</button>
);
}TIP
The simple rule to remember. Ask yourself: does this component need to respond to user clicks, keyboard input, or browser APIs? If yes → add 'use client'. If it just displays data or fetches from a database → leave it as a Server Component. Default to Server Components and only add 'use client' when you actually need interactivity.
// app/course/[slug]/page.jsx (Server Component — no directive)
import CourseInfo from "@/components/CourseInfo"; // Server Component
import EnrollButton from "@/components/EnrollButton"; // Client Component
export default async function CoursePage({ params }) {
const { slug } = await params;
// This fetch runs on the server — secure and fast
const course = await fetch(`/api/courses/${slug}`).then(r => r.json());
return (
<div>
<CourseInfo course={course} /> {/* Rendered on server */}
<EnrollButton courseId={course.id} /> {/* Interactive, runs in browser */}
</div>
);
}