Module 13 — Server vs Client Components
Understanding the difference between Server Components and Client Components is one of the most important skills in modern Next.js development.
The goal is not to choose one over the other.
The goal is to understand which parts of your application should run on the server and which parts actually need to run in the browser.
A well-designed Next.js application usually combines both.
1. Server Components vs Client Components
In the Next.js App Router, components are Server Components by default.
A component becomes a Client Component when you add:
1"use client";
The fundamental difference is:
1Server Component 2 │ 3 ├── Runs on the server 4 ├── Can access server resources 5 ├── Can fetch data directly 6 └── Cannot use browser interaction APIs 7 8Client Component 9 │ 10 ├── Runs with client-side React 11 ├── Can use state 12 ├── Can use effects 13 ├── Can handle browser events 14 └── Can access browser APIs
2. Complete Comparison
| Requirement | Server Component | Client Component |
|---|---|---|
| Database access | Yes | Usually no |
| Server API fetch | Yes | Yes, when appropriate |
useState | No | Yes |
useEffect | No | Yes |
| Browser API | No | Yes |
| Event handlers | No | Yes |
| SEO content | Excellent | Can still render, but server-first is preferred |
| Interactive UI | Limited | Yes |
localStorage | No | Yes |
window | No | Yes |
document | No | Yes |
| Server-only environment variables | Yes | No |
| Private API keys | Yes | No |
| Server-side data fetching | Yes | Possible, but often better on server |
| Interactive forms | Limited | Yes |
| Client-side state management | No | Yes |
The important word in this table is usually.
Next.js does not mean that every operation has only one possible location. Architecture depends on the application.
3. The Default: Server Components
Consider a simple page:
1export default async function ProductsPage() { 2 const products = await getProducts(); 3 4 return ( 5 <main> 6 <h1>Products</h1> 7 8 {products.map((product) => ( 9 <article key={product.id}> 10 <h2>{product.name}</h2> 11 <p>${product.price}</p> 12 </article> 13 ))} 14 </main> 15 ); 16}
There is no:
1"use client";
Therefore, this is a Server Component.
It can perform server-side work:
1ProductsPage 2 │ 3 ├── Database 4 │ 5 ├── Server API 6 │ 7 ├── Authentication 8 │ 9 └── Render HTML
This is ideal for content that does not require browser-side interaction.
4. Client Components
Now suppose the user needs to increase and decrease a quantity.
That requires state.
1"use client"; 2 3import { useState } from "react"; 4 5export default function QuantitySelector() { 6 const [quantity, setQuantity] = useState(1); 7 8 return ( 9 <div> 10 <button 11 onClick={() => 12 setQuantity(Math.max(1, quantity - 1)) 13 } 14 > 15 - 16 </button> 17 18 <span>{quantity}</span> 19 20 <button 21 onClick={() => setQuantity(quantity + 1)} 22 > 23 + 24 </button> 25 </div> 26 ); 27}
This must be a Client Component because it uses:
1useState
and:
1onClick
5. The Important Architecture
A common and powerful Next.js architecture looks like this:
1Server Page 2 ↓ 3Fetch Data 4 ↓ 5Render Content 6 ↓ 7Interactive Client Component
For example:
1ProductPage 2 │ 3 ├── Fetch product 4 │ 5 ├── Render title 6 │ 7 ├── Render description 8 │ 9 ├── Render price 10 │ 11 └── QuantitySelector 12 │ 13 ├── useState 14 └── onClick
This means you don't need to make the entire product page a Client Component just because the quantity selector is interactive.
6. Real-World Example
Imagine an e-commerce product page.
Server Component
1import QuantitySelector from "./QuantitySelector"; 2 3export default async function ProductPage() { 4 const product = await getProduct(); 5 6 return ( 7 <main> 8 <h1>{product.name}</h1> 9 10 <p>{product.description}</p> 11 12 <p>${product.price}</p> 13 14 <QuantitySelector /> 15 </main> 16 ); 17}
Client Component
1"use client"; 2 3import { useState } from "react"; 4 5export default function QuantitySelector() { 6 const [quantity, setQuantity] = useState(1); 7 8 return ( 9 <div> 10 <button onClick={() => setQuantity(quantity - 1)}> 11 - 12 </button> 13 14 <span>{quantity}</span> 15 16 <button onClick={() => setQuantity(quantity + 1)}> 17 + 18 </button> 19 </div> 20 ); 21}
The final architecture is:
1 ProductPage 2 │ 3 Server Component 4 │ 5 ┌────────────┼────────────┐ 6 │ │ │ 7 Product Price Description 8 │ 9 │ 10 ▼ 11 QuantitySelector 12 │ 13 Client Component 14 │ 15 ┌──────┴──────┐ 16 │ │ 17State Events
This is generally preferable to making the entire page client-side.
7. Database Access
Server Components can directly access server-side resources.
For example:
1import { db } from "@/lib/db"; 2 3export default async function UsersPage() { 4 const users = await db.user.findMany(); 5 6 return ( 7 <main> 8 <h1>Users</h1> 9 10 {users.map((user) => ( 11 <p key={user.id}>{user.name}</p> 12 ))} 13 </main> 14 ); 15}
The database stays on the server.
1Browser 2 │ 3 │ request 4 ▼ 5Next.js Server 6 │ 7 ▼ 8Database
You generally should not put database access directly inside a Client Component.
8. Why Client Components Usually Don't Access the Database
Consider:
1"use client"; 2 3import { db } from "@/lib/db"; 4 5export default function Users() { 6 // ❌ Bad architecture 7}
The browser should not receive database credentials or a direct database connection.
Instead:
1Client Component 2 │ 3 │ Request 4 ▼ 5Server 6 │ 7 ▼ 8Database
For example:
1"use client"; 2 3export default function DeleteButton({ 4 userId, 5}: { 6 userId: string; 7}) { 8 async function handleDelete() { 9 await fetch(`/api/users/${userId}`, { 10 method: "DELETE", 11 }); 12 } 13 14 return ( 15 <button onClick={handleDelete}> 16 Delete 17 </button> 18 ); 19}
The server handles the actual database operation.
9. Server API Fetching
Server Components are excellent for fetching data from server-side APIs.
1export default async function Dashboard() { 2 const response = await fetch( 3 "https://api.example.com/dashboard" 4 ); 5 6 const data = await response.json(); 7 8 return ( 9 <main> 10 <h1>{data.title}</h1> 11 </main> 12 ); 13}
The request can happen on the server before the page reaches the browser.
Client Components can also make API requests when the interaction requires it.
For example:
1"use client"; 2 3import { useState } from "react"; 4 5export default function Search() { 6 const [results, setResults] = useState([]); 7 8 async function search(query: string) { 9 const response = await fetch( 10 `/api/search?q=${encodeURIComponent(query)}` 11 ); 12 13 const data = await response.json(); 14 15 setResults(data.results); 16 } 17 18 return ( 19 <button onClick={() => search("nextjs")}> 20 Search 21 </button> 22 ); 23}
So the rule is not:
"Client Components cannot fetch APIs."
The better rule is:
Use the server for server-oriented data fetching and the client when fetching is part of browser interaction.
10. useState
useState belongs to Client Components.
This works:
1"use client"; 2 3import { useState } from "react"; 4 5export default function Counter() { 6 const [count, setCount] = useState(0); 7 8 return ( 9 <button onClick={() => setCount(count + 1)}> 10 Count: {count} 11 </button> 12 ); 13}
This does not work as a Server Component:
1import { useState } from "react"; 2 3export default function Counter() { 4 const [count, setCount] = useState(0); 5 6 return <p>{count}</p>; 7}
The solution is to add:
1"use client";
11. useEffect
useEffect is also a Client Component feature.
For example:
1"use client"; 2 3import { useEffect } from "react"; 4 5export default function Analytics() { 6 useEffect(() => { 7 console.log("Component mounted"); 8 }, []); 9 10 return <p>Analytics</p>; 11}
Effects are useful for browser-side operations such as:
1Browser subscriptions 2Timers 3DOM interaction 4Browser APIs 5Client-side synchronization 6Event listeners
12. Browser APIs
Server Components cannot directly use browser-specific APIs.
For example:
1window.innerWidth
requires the browser.
A Client Component can use it:
1"use client"; 2 3import { useEffect, useState } from "react"; 4 5export default function ScreenWidth() { 6 const [width, setWidth] = useState<number | null>(null); 7 8 useEffect(() => { 9 setWidth(window.innerWidth); 10 }, []); 11 12 return ( 13 <p> 14 Screen width: {width ?? "Loading..."} 15 </p> 16 ); 17}
Common browser APIs include:
1window 2document 3navigator 4localStorage 5sessionStorage 6Clipboard API 7Geolocation API 8IntersectionObserver 9ResizeObserver
13. Event Handlers
Server Components cannot contain browser event handlers such as:
1onClick 2onChange 3onSubmit
For example:
1"use client"; 2 3export default function Button() { 4 function handleClick() { 5 alert("Clicked!"); 6 } 7 8 return ( 9 <button onClick={handleClick}> 10 Click Me 11 </button> 12 ); 13}
The button is interactive because the browser needs to execute the event handler.
Therefore it belongs to the Client Component boundary.
14. SEO Content
Server Components are particularly useful for SEO-focused pages.
Consider an article:
1export default async function ArticlePage() { 2 const article = await getArticle(); 3 4 return ( 5 <article> 6 <h1>{article.title}</h1> 7 8 <p>{article.description}</p> 9 10 <div>{article.content}</div> 11 </article> 12 ); 13}
This is a strong server-first architecture because the page primarily contains content.
You can then add small interactive components:
1ArticlePage 2│ 3├── Title → Server 4├── Description → Server 5├── Article Content → Server 6├── Table of Contents → Client 7├── Like Button → Client 8└── Share Button → Client
The content remains server-first while interaction is isolated.
15. Interactive UI
Client Components are ideal for interactive interfaces.
Examples:
1Dropdown 2Modal 3Tabs 4Carousel 5Search box 6Filters 7Shopping cart 8Quantity selector 9Theme switcher 10Drag-and-drop interface 11Interactive dashboard
For example:
1"use client"; 2 3import { useState } from "react"; 4 5export default function Tabs() { 6 const [activeTab, setActiveTab] = useState("overview"); 7 8 return ( 9 <div> 10 <button onClick={() => setActiveTab("overview")}> 11 Overview 12 </button> 13 14 <button onClick={() => setActiveTab("reviews")}> 15 Reviews 16 </button> 17 18 {activeTab === "overview" && ( 19 <p>Product overview</p> 20 )} 21 22 {activeTab === "reviews" && ( 23 <p>Customer reviews</p> 24 )} 25 </div> 26 ); 27}
The tabs need state and events, so the interactive portion belongs on the client.
16. Do Not Make the Whole Page Client-Side
A common mistake is:
1"use client"; 2 3export default async function ProductPage() { 4 // Everything becomes client-side. 5}
when only one button needs interactivity.
Instead:
1ProductPage 2│ 3├── Product information 4│ └── Server 5│ 6├── Product description 7│ └── Server 8│ 9├── Reviews 10│ └── Server 11│ 12└── Add to Cart 13 └── Client
This gives you a smaller and clearer client boundary.
17. Server Component → Client Component
Server Components can render Client Components.
1import LikeButton from "./LikeButton"; 2 3export default async function PostPage() { 4 const post = await getPost(); 5 6 return ( 7 <article> 8 <h1>{post.title}</h1> 9 10 <p>{post.content}</p> 11 12 <LikeButton postId={post.id} /> 13 </article> 14 ); 15}
Client Component:
1"use client"; 2 3import { useState } from "react"; 4 5export default function LikeButton({ 6 postId, 7}: { 8 postId: string; 9}) { 10 const [liked, setLiked] = useState(false); 11 12 return ( 13 <button onClick={() => setLiked(!liked)}> 14 {liked ? "Liked" : "Like"} 15 </button> 16 ); 17}
This creates a clean boundary:
1Server Component 2 │ 3 │ props 4 ▼ 5Client Component 6 │ 7 ├── State 8 ├── Events 9 └── Browser interaction
18. Passing Data Across the Boundary
A Server Component can pass data to a Client Component through props.
Server:
1import UserCard from "./UserCard"; 2 3export default async function Page() { 4 const user = await getUser(); 5 6 return <UserCard user={user} />; 7}
Client:
1"use client"; 2 3type User = { 4 id: string; 5 name: string; 6}; 7 8export default function UserCard({ 9 user, 10}: { 11 user: User; 12}) { 13 return ( 14 <div> 15 <h2>{user.name}</h2> 16 <p>ID: {user.id}</p> 17 </div> 18 ); 19}
This pattern is extremely useful:
1Server 2 │ 3 ├── Fetch data 4 ├── Process data 5 └── Authenticate 6 │ 7 │ props 8 ▼ 9Client 10 │ 11 ├── Display 12 ├── Interact 13 └── Update UI
19. Keep Secrets on the Server
Never expose private credentials to Client Components.
Bad:
1"use client"; 2 3const secret = process.env.PRIVATE_API_KEY; 4 5export default function Component() { 6 return <p>{secret}</p>; 7}
Private credentials should remain in server-side code.
Correct architecture:
1Client Component 2 │ 3 │ Request 4 ▼ 5Server 6 │ 7 ├── Private API key 8 ├── Database credentials 9 └── Server-only logic 10 │ 11 ▼ 12External API / Database
The client receives only the data it actually needs.
20. Server vs Client: A Practical Example
Imagine a course platform.
The course page contains:
1Course Page 2│ 3├── Course title 4├── Course description 5├── Instructor information 6├── Lessons 7├── Search lessons 8├── Progress indicator 9├── Bookmark button 10└── Video player
A good architecture could be:
1CoursePage 2│ 3├── Course title 4│ └── Server 5│ 6├── Description 7│ └── Server 8│ 9├── Instructor 10│ └── Server 11│ 12├── Lessons 13│ └── Server 14│ 15├── LessonSearch 16│ └── Client 17│ 18├── ProgressIndicator 19│ └── Client 20│ 21├── BookmarkButton 22│ └── Client 23│ 24└── VideoPlayer 25 └── Client
This is much better than:
1CoursePage 2 ↓ 3Everything Client-Side
because only the interactive pieces need client-side functionality.
21. Another Example: Dashboard
Consider an analytics dashboard.
1Dashboard 2│ 3├── Server 4│ ├── Fetch statistics 5│ ├── Fetch user information 6│ └── Fetch database records 7│ 8└── Client 9 ├── Date picker 10 ├── Chart controls 11 ├── Filters 12 └── Interactive charts
The server can prepare the initial data:
1export default async function Dashboard() { 2 const stats = await getDashboardStats(); 3 4 return ( 5 <main> 6 <h1>Dashboard</h1> 7 8 <DashboardChart stats={stats} /> 9 </main> 10 ); 11}
The chart can be interactive:
1"use client"; 2 3import { useState } from "react"; 4 5export default function DashboardChart({ 6 stats, 7}: { 8 stats: number[]; 9}) { 10 const [range, setRange] = useState("7d"); 11 12 return ( 13 <section> 14 <button onClick={() => setRange("7d")}> 15 7 Days 16 </button> 17 18 <button onClick={() => setRange("30d")}> 19 30 Days 20 </button> 21 22 <p>Current range: {range}</p> 23 24 {/* Render chart using stats */} 25 </section> 26 ); 27}
22. How to Decide Which Component to Use
Use this decision process:
1Does the component need useState? 2 │ 3 YES 4 ↓ 5Client Component 6 7Does it need useEffect? 8 │ 9 YES 10 ↓ 11Client Component 12 13Does it use browser APIs? 14 │ 15 YES 16 ↓ 17Client Component 18 19Does it need event handlers? 20 │ 21 YES 22 ↓ 23Client Component 24 25Does it need interactive client-side libraries? 26 │ 27 YES 28 ↓ 29Client Component 30 31Otherwise 32 │ 33 ↓ 34Prefer Server Component
This is a simple but powerful rule.
23. Architecture Pattern to Remember
The most important architecture pattern from this module is:
1 Server Page 2 │ 3 ▼ 4 Fetch Data 5 │ 6 ▼ 7 Render Content 8 │ 9 ┌──────────┴──────────┐ 10 │ │ 11 Static Content Client Component 12 │ │ 13 │ ┌──────┼──────┐ 14 │ │ │ │ 15 │ State Events Effects 16 │ 17 ▼ 18 Browser
In other words:
1Server = Data + Content + Security 2 3Client = Interaction + State + Browser APIs
24. Server-First Does Not Mean No Client JavaScript
A server-first architecture does not mean your application cannot be interactive.
Instead, you selectively introduce Client Components.
For example:
1 Page 2 │ 3 ┌─────────┼─────────┐ 4 │ │ │ 5 Server Server Client 6 Content Content Search 7 │ 8 State
Only the search functionality needs client-side React.
This is the key architectural advantage.
25. Performance Considerations
Keeping Client Components focused can reduce unnecessary client-side JavaScript.
For example, instead of:
1Entire Page 2 ↓ 3Client Component 4 ↓ 5Large client bundle
prefer:
1Entire Page 2 ↓ 3Server Component 4 │ 5 ├── Server content 6 ├── Server data 7 │ 8 └── Small Client Component
The result can be a more efficient application because browser-side code is limited to functionality that actually requires it.
26. Common Mistakes
Mistake 1 — Using "use client" everywhere
1"use client";
should not automatically appear in every component.
First determine whether the component needs client-side capabilities.
Mistake 2 — Accessing the database from the browser
Do not expose database connections or credentials.
Use a server-side layer.
Mistake 3 — Using browser APIs on the server
This is incorrect:
1const width = window.innerWidth;
inside a Server Component.
Move browser-dependent logic into a Client Component.
Mistake 4 — Fetching everything with useEffect
Not every data-fetching operation needs to happen inside:
1useEffect(...)
If data can be fetched on the server, consider doing so.
Mistake 5 — Making an entire page client-side for one button
Instead of:
1Entire Page → Client
prefer:
1Page → Server 2 │ 3 └── Button → Client
27. Production Architecture Example
A realistic production application might look like this:
1app/ 2│ 3├── courses/ 4│ └── [slug]/ 5│ ├── page.tsx 6│ │ └── Server Component 7│ │ 8│ ├── SearchLessons.tsx 9│ │ └── Client Component 10│ │ 11│ ├── BookmarkButton.tsx 12│ │ └── Client Component 13│ │ 14│ └── VideoPlayer.tsx 15│ └── Client Component 16│ 17├── dashboard/ 18│ ├── page.tsx 19│ │ └── Server Component 20│ │ 21│ └── DashboardFilters.tsx 22│ └── Client Component 23│ 24└── api/ 25 └── courses/ 26 └── route.ts 27 └── Server
The application is not entirely server-side or client-side.
It is hybrid.
28. The Golden Rule
When building a Next.js application, remember:
Start with Server Components. Add Client Components only where interaction requires them.
Think about the application like this:
1 SERVER 2 │ 3 ┌────────┼────────┐ 4 │ │ │ 5 Database APIs Secrets 6 │ │ │ 7 └────────┼────────┘ 8 │ 9 Server Page 10 │ 11 ▼ 12 Render Content 13 │ 14 ▼ 15 CLIENT BOUNDARY 16 │ 17 ┌────────┼────────┐ 18 │ │ │ 19 State Events Effects 20 │ │ │ 21 └────────┼────────┘ 22 │ 23 ▼ 24 Interactive UI
The best Next.js applications carefully divide responsibilities between these two environments.
29. Final Comparison
1SERVER COMPONENT 2│ 3├── Default 4├── Database access 5├── Server-side data fetching 6├── Authentication 7├── Private environment variables 8├── SEO-focused content 9└── Server-side rendering 10 11CLIENT COMPONENT 12│ 13├── "use client" 14├── useState 15├── useEffect 16├── Event handlers 17├── Browser APIs 18├── localStorage 19├── Interactive UI 20└── Client-side libraries
The most important architectural principle is:
1Server Page 2 ↓ 3Fetch Data 4 ↓ 5Render Content 6 ↓ 7Interactive Client Component
Once you understand this pattern, you can build Next.js applications that are both server-first and highly interactive without unnecessarily moving your entire application into the browser.