Next.js Architecture: Server vs Client Components
π§ Understanding the Component Architecture
Next.js introduces a powerful paradigm with React Server Components that fundamentally changes how we think about React applications. Understanding when and how to use Server vs Client Components is crucial for building performant, modern web applications.
π₯οΈ Server Components (Default)
Server Components are the default in Next.js App Router. They run on the server and have several key characteristics:
What Server Components CAN do:
- Fetch data directly from databases or APIs
- Access server-side resources (file system, environment variables)
- Render static content efficiently
- Reduce client-side JavaScript bundle size
- Improve initial page load performance
What Server Components CANNOT do:
- Use browser-only APIs (localStorage, sessionStorage, etc.)
- Handle user interactions (onClick, onChange, etc.)
- Use React hooks like
useState
,useEffect
,useContext
- Access browser APIs like geolocation or camera
// This is a Server Component by default
async function ServerComponent() {
// This runs on the server
const data = await fetch('https://api.example.com/data');
const posts = await data.json();
return (
<div>
<h1>Posts from Server</h1>
{posts.map(post => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.content}</p>
</article>
))}
</div>
);
}
π Client Components
Client Components run in the browser and enable all the interactive features weβre familiar with in traditional React applications.
When to use Client Components:
- User interactions (clicks, form inputs, hover effects)
- Browser APIs (geolocation, camera, localStorage)
- React hooks (
useState
,useEffect
, custom hooks) - Real-time features (WebSockets, live updates)
- Complex state management
Converting to Client Component
Add the 'use client'
directive at the top of your file:
'use client'
import { useState, useEffect } from 'react'
function ClientComponent() {
const [count, setCount] = useState(0);
const [data, setData] = useState(null);
useEffect(() => {
// This runs in the browser
const stored = localStorage.getItem('count');
if (stored) {
setCount(parseInt(stored));
}
}, []);
const handleClick = () => {
const newCount = count + 1;
setCount(newCount);
localStorage.setItem('count', newCount.toString());
};
return (
<div>
<h1>Interactive Counter</h1>
<p>Count: {count}</p>
<button onClick={handleClick}>
Increment
</button>
</div>
);
}
ποΈ Best Practices & Architecture Patterns
1. Start with Server Components
Always start with Server Components and only add 'use client'
when you need browser-specific functionality.
2. Minimize Client Component Boundaries
Keep your 'use client'
boundaries as small as possible. Instead of making an entire page a Client Component, create smaller interactive components.
// β Don't do this - entire page becomes client-side
'use client'
function Page() {
const [likes, setLikes] = useState(0);
return (
<div>
<Header />
<StaticContent />
<LikeButton likes={likes} onLike={() => setLikes(likes + 1)} />
<Footer />
</div>
);
}
// β
Do this - only interactive part is client-side
function Page() {
return (
<div>
<Header />
<StaticContent />
<LikeButton /> {/* This component has 'use client' */}
<Footer />
</div>
);
}
3. Composition Pattern
You can pass Server Components as children to Client Components:
// Client Component
'use client'
function InteractiveWrapper({ children }) {
const [isOpen, setIsOpen] = useState(false);
return (
<div>
<button onClick={() => setIsOpen(!isOpen)}>
Toggle Content
</button>
{isOpen && children}
</div>
);
}
// Server Component
function Page() {
return (
<InteractiveWrapper>
<ServerRenderedContent /> {/* Stays on server */}
</InteractiveWrapper>
);
}
π Performance Implications
Server Components Benefits:
- Smaller bundle size - Server Component code doesnβt ship to the client
- Faster initial load - Less JavaScript to download and parse
- Better SEO - Content is server-rendered
- Direct data access - No need for API routes for database queries
Client Components Benefits:
- Immediate interactivity - No round-trip to server for state changes
- Rich user experience - Full access to browser APIs
- Offline capabilities - Can work without server connection