BiTree
  • Search For Lessons
  • Curriculum
  • Pricing
  • For Educators
  • Become a Tutor
  • About
  • Contact
Log InGet Started

Questions, concerns, bug reports, or suggestions? We read every message, write to us at [email protected].

More ways to reach us →
BiTree

Live coding lessons for aspiring developers and security professionals.

[email protected]

(201) 785-7951

Mon–Fri, 9 AM–5 PM EST

Learn

  • Search For Lessons
  • Curriculum
  • Pricing

Company

  • About
  • For Educators & Schools
  • Become a Tutor
  • Contact Us

Legal

  • Terms of Service
  • Privacy Policy
© 2026 BiTree. All rights reserved.
Curriculum/Web Development/React and Next.js/Server Components vs Client Components
50 minIntermediate

Server Components vs Client Components

After this lesson, you will be able to: Distinguish Server Components from Client Components in Next.js, decide which to use for any new component, fetch data on the server without writing an API call, and add the `'use client'` boundary correctly.

Next.js's App Router introduced React Server Components — components that run ONLY on the server, fetch data directly, and send their rendered HTML to the browser without shipping their JavaScript. This is the single biggest mental shift from older React. Understanding it is the difference between writing fast, secure apps and dragging unnecessary JavaScript across every page. This lesson takes you from confused to comfortable in 50 minutes.

Prerequisites:React Hooks

The default is the server

In the App Router, every component is a Server Component by default. They run on the server (during the request or at build time), fetch data, and send already-rendered HTML to the browser. No JavaScript for that component is shipped to the client. Client Components opt in with `'use client'` at the top of the file. They ship JavaScript to the browser, run there, and can use state, effects, and event handlers. Default = server. Opt in = client. Most components stay server; you add `'use client'` only when you need interactivity.

A Server Component fetching data

No `useEffect`, no loading state, no API endpoint. You `async`-fetch inside the component. The HTML returned to the browser already has the data baked in.

html
// app/users/page.tsx — Server Component by default
export default async function UsersPage() {
const response = await fetch("https://jsonplaceholder.typicode.com/users", {
// Next.js extends fetch with caching options
next: { revalidate: 60 }, // re-fetch at most every 60 seconds
});
const users: { id: number; name: string; email: string }[] =
await response.json();
return (
<ul className="divide-y">
{users.map((u) => (
<li key={u.id} className="py-3">
<p className="font-semibold">{u.name}</p>
<p className="text-sm text-slate-600">{u.email}</p>
</li>
))}
</ul>
);
}

A Client Component with state

Anything interactive — useState, useEffect, onClick handlers, browser APIs — needs `'use client'`. The directive must be the first non-comment line of the file.

python
"use client";
import { useState } from "react";
export function LikeButton({ initial = 0 }: { initial?: number }) {
const [likes, setLikes] = useState(initial);
return (
<button
onClick={() => setLikes((n) => n + 1)}
className="rounded bg-pink-600 px-3 py-1 text-white"
>
♥ {likes}
</button>
);
}

The boundary, not the file

`'use client'` doesn't mean 'this one component is a client component.' It means 'this file AND every component it imports become client components.' The practical pattern: keep the boundary as deep as possible. Have a Server Component page that fetches data, then import a small Client Component for the interactive island. That way most of the page ships zero JavaScript and only the interactive bit (the button, the form) is hydrated.

Mixing server and client in one page

The page is a Server Component — it fetches the data on the server. Inside, it renders a small Client Component for the interactive piece. The user sees the list immediately (no loading state); the like button hydrates when its JS arrives.

html
// app/posts/page.tsx — Server Component (no 'use client')
import { LikeButton } from "./like-button";
async function fetchPosts() {
const response = await fetch("https://jsonplaceholder.typicode.com/posts?_limit=5");
return response.json();
}
export default async function PostsPage() {
const posts = await fetchPosts();
return (
<ul className="space-y-4">
{posts.map((post: { id: number; title: string }) => (
<li key={post.id} className="border rounded p-4">
<h2 className="font-semibold">{post.title}</h2>
<LikeButton /> {/* hydrates on the client */}
</li>
))}
</ul>
);
}
// app/posts/like-button.tsx — Client Component
"use client";
import { useState } from "react";
export function LikeButton() {
const [n, setN] = useState(0);
return <button onClick={() => setN(n + 1)}>♥ {n}</button>;
}

When to pick each

**Use a Server Component when:** the component just displays data; it needs database/file/secret access; it doesn't need state, effects, or browser APIs; you want to ship less JavaScript. (This is most components.) **Use a Client Component when:** it has interactivity (onClick, onChange, form input state); it uses hooks (useState, useEffect, useContext); it touches browser APIs (window, localStorage); it uses a third-party library that requires the browser (like a charting library).

💡 Server secrets — only on the server

API keys, database connection strings, signing secrets: only put them in Server Components, server-only files, or API routes. Anything in a Client Component or imported into one gets bundled into the JavaScript shipped to the browser, where anyone can read it in DevTools. The `server-only` package is a safety belt — `import "server-only";` at the top of a file causes the build to fail if a Client Component tries to import it. Worth using on files that hold credentials.

💡 Common mistakes only experienced devs catch

Five server-vs-client traps. (1) **Forgetting `'use client'`** — adding `useState` in a server component crashes the build. The error mentions hooks; the fix is the directive. (2) **Wrapping everything in `'use client'`** — defeats the point of Server Components. Keep the boundary deep; only the interactive piece needs to be a client component. (3) **Importing a server module into a client component** — server-only code (file system, Node APIs, database drivers) crashes when bundled for the browser. Use the `server-only` import as a guard. (4) **Passing functions or class instances as props from server to client** — props that cross the boundary must be serializable (strings, numbers, plain objects, arrays). Functions can't be sent — define them in the client component instead. (5) **Putting secrets in environment variables that start with `NEXT_PUBLIC_`** — that prefix tells Next.js to expose the variable to the browser. Use it ONLY for genuinely-public values (like a Supabase anon key); never for secrets.

Sign in and purchase access to unlock this lesson.

Sign in to purchase
←React Hooks
Back to React and Next.js
API Routes in Next.js→