Server and Client Components Architecture in React
A practical guide explaining how server and client components work, how they interact, and how to design scalable applications using a server-first model.
Server and Client Components Architecture in React
Modern React applications use two kinds of components: server components and client components. Understanding how they differ is essential for building fast and maintainable applications. This guide explains how each type works, when to use them, and how to structure your application around a server-first approach.
1. What Server Components Are
Server components run only on the server. They can safely use server resources such as databases, private APIs, or environment variables. React renders them on the server and sends the result to the client as a serialized payload.
Key characteristics:
- Do not include client JavaScript by default
- Cannot use hooks like useState or useEffect
- Can fetch data directly
- Render before any client code runs
Example:
// Server component
import { getPosts } from "@/lib/data";
export default async function PostsPage() {
const posts = await getPosts();
return <PostsList posts={posts} />;
}Server components handle the heavy lifting. They fetch data, prepare UI structure, and minimize JavaScript that reaches the browser.
2. What Client Components Are
Client components run in the browser. They handle interactivity, event handlers, animations, and browser APIs.
To mark a component as a client component, add:
"use client";Example:
"use client";
export function PostsList({ posts }) {
const [selected, setSelected] = useState(null);
return (
<ul>
{posts.map((p) => (
<li key={p.id} onClick={() => setSelected(p.id)}>
{p.title}
</li>
))}
</ul>
);
}Client components must be used when the UI requires interactivity.
3. How Server and Client Components Work Together
React allows server components to pass serialized props to client components. This keeps the interactive parts of the UI small while handling data on the server.
Example:
// Server
export default async function ProfilePage() {
const profile = await getProfile();
return <ProfileView profile={profile} />;
}Client:
"use client";
export function ProfileView({ profile }) {
const [open, setOpen] = useState(false);
return (
<section>
<h1>{profile.name}</h1>
<button onClick={() => setOpen(!open)}>Toggle</button>
{open && <p>{profile.bio}</p>}
</section>
);
}The server retrieves data. The client component manages interactions.
4. Why Server First Architecture Matters
A server-first approach offers several benefits:
- Smaller JavaScript bundles
- Less client-side state management
- Predictable rendering and fewer effects
- Improved performance through server data fetching
- Cleaner separation of responsibilities
Most components should remain server components unless interactivity is required.
5. Designing Applications with Clear Boundaries
A helpful mental model:
Server = data, layout, expensive work
Client = interactions, browser APIs, animations
Good candidates for server components:
- dashboards that load data
- product listings
- layouts and navigation
- static or semi-static UI sections
Good candidates for client components:
- form handlers
- interactive widgets
- modals
- theme switchers
- inputs and controlled fields
6. Passing Data Between Components
Server components can pass JSON-serializable data to client components. They cannot pass:
- class instances
- functions
- sockets
- database clients
Serializable example:
return <UserCard user={{ id: 1, name: "Alice" }} />;This boundary keeps client code clean and safe.
7. Common Mistakes to Avoid
Mistake 1: Marking everything as a client component
This leads to large bundles and unnecessary complexity. Use client components only when needed.
Mistake 2: Fetching data inside useEffect
Data should be loaded on the server whenever possible.
Mistake 3: Mixing interactive and non-interactive logic
Split components based on responsibilities.
8. Example Architecture Structure
app/
layout.tsx // Server
page.tsx // Server
components/
PostCard.tsx // Server or client depending on usage
InteractiveFilter.tsx // ClientThis ensures that only the parts that need interactivity become client components.
Final Thoughts
Understanding server and client components is essential for modern React development. A server-first approach leads to more predictable performance, smaller bundles, and cleaner architecture. By using server components for data and structure and client components for interactivity, you can build fast and scalable user interfaces.