← Back to chapters
07intermediate

Server vs Client Components

The React Server Components mental model. When to use 'use client' and why.

The Biggest Mental Shift in Next.js

This is THE concept that confuses people coming from React. In Next.js (App Router), components are Server Components by default. They run on the server, never ship JavaScript to the browser, and can directly access databases/file systems.

šŸ’”

How it works under the hood (RSC Payload)

When Next.js renders Server Components, it creates the RSC Payload:
• A compact binary format of rendered Server Components
• Contains placeholders for where Client Components go
• Includes props being passed between server and client
The browser uses this to stitch the full page together.

Server Components (Default)

src/app/users/page.tsx
tsx
1// This is a Server Component (no "use client" directive)
2// It runs ONLY on the server
3
4import { db } from "@/lib/database";
5
6export default async function UsersPage() {
7// This runs on the server. The SQL never reaches the browser
8const users = await db.query("SELECT * FROM users");
9
10return (
11 <ul>
12 {users.map((user) => (
13 <li key={user.id}>{user.name}</li>
14 ))}
15 </ul>
16);
17}
šŸ’”

Why this matters

Server Components send ZERO JavaScript to the client for that component. A page that just displays data from a database? 0kb of client JS. This is massive for performance.

Client Components

Need interactivity? State? Event handlers? Browser APIs? Add 'use client' at the top:

src/components/Counter.tsx
tsx
1"use client";
2
3import { useState } from "react";
4
5export default function Counter() {
6const [count, setCount] = useState(0);
7
8return (
9 <div>
10 <p>Count: {count}</p>
11 <button onClick={() => setCount(count + 1)}>
12 Increment
13 </button>
14 </div>
15);
16}

When to Use Which?

FeatureWhat it doesWhen to use
Fetch dataāœ… Server ComponentDirect DB/API access
onClick, onChangeāœ… Client ComponentNeeds browser events
useState, useEffectāœ… Client ComponentNeeds React hooks
Access cookies/headersāœ… Server ComponentServer-only APIs
Static contentāœ… Server ComponentNo JS shipped
Forms with validationāœ… Client ComponentReal-time feedback

The Composition Pattern

The trick is: keep most things as Server Components and sprinkle Client Components only where needed:

src/app/dashboard/page.tsx
tsx
1// src/app/dashboard/page.tsx (Server Component)
2import { db } from "@/lib/database";
3import { LikeButton } from "@/components/LikeButton"; // Client
4
5export default async function Dashboard() {
6const posts = await db.query("SELECT * FROM posts");
7
8return (
9 <div>
10 <h1>Dashboard</h1>
11 {posts.map((post) => (
12 <article key={post.id}>
13 <h2>{post.title}</h2>
14 <p>{post.body}</p>
15 {/* Only this small button is a Client Component */}
16 <LikeButton postId={post.id} />
17 </article>
18 ))}
19 </div>
20);
21}
šŸš€

My rule of thumb

Start everything as a Server Component. Only add 'use client' when you literally can't:
• You need useState or useEffect
• You need onClick, onChange, or other event handlers
• You need browser APIs (localStorage, window)
• You need custom hooks
Push client boundaries as low as possible in your component tree.

Context Providers Pattern

React Context doesn't work in Server Components. But you still need things like theme providers. The trick: make the provider a Client Component, import it in your layout (Server Component), and pass children through it:

src/providers/theme-provider.tsx
tsx
1// src/providers/theme-provider.tsx
2"use client";
3
4import { createContext } from "react";
5
6export const ThemeContext = createContext({});
7
8export default function ThemeProvider({
9children,
10}: {
11children: React.ReactNode;
12}) {
13return (
14 <ThemeContext.Provider value="dark">
15 {children}
16 </ThemeContext.Provider>
17);
18}
src/app/layout.tsx
tsx
1// src/app/layout.tsx (Server Component!)
2import ThemeProvider from "@/providers/theme-provider";
3
4export default function RootLayout({ children }) {
5return (
6 <html>
7 <body>
8 <ThemeProvider>{children}</ThemeProvider>
9 </body>
10 </html>
11);
12}

Server Components that are passed as children render on the server first, then get slotted into the Client Component. Best of both worlds.

Wrapping Third-Party Components

Some npm packages use client-only features but don't have 'use client' in their code. You'll get an error using them in Server Components. The fix is dead simple:

src/components/carousel.tsx
tsx
1// src/components/carousel.tsx
2"use client";
3
4// Just re-export with the directive
5import { Carousel } from "acme-carousel";
6export default Carousel;
7
8// Now you can use <Carousel /> in any Server Component

ā–¶Watch and Learn