When I first read about React Server Components, it sounded simple:
“components that run on the server”
But that description doesn’t really help when you start building something real. The confusion usually starts the moment you ask:
“wait… where should this logic actually go?”
What changes with Server Components?
In a typical React app, everything runs in the browser.
You fetch data using useEffect, manage loading states, and ship a decent amount of JavaScript just to render a page. It works, but it also means:
- your bundle grows fast
- your UI waits on client-side fetching
- you end up writing a lot of boilerplate
Server Components flip this a bit.
Instead of thinking:
“render UI, then fetch data”
you start thinking:
“fetch data on the server, then send ready UI”
That shift is the whole point.
What actually happens (without the buzzwords)
Server Components don’t behave like traditional SSR where you just send HTML.
What really happens is:
- the server runs your component
- fetches all required data
- returns a serialized component tree (not plain HTML)
- the client hydrates only the interactive parts
So instead of hydrating everything, you only hydrate what actually needs it.
A simple example
import db from '@/lib/db';
export default async function UserProfile({ id }) {
const user = await db.users.findById(id);
return (
<div>
<h1>{user.name}</h1>
<p>{user.bio}</p>
</div>
);
}No API route.
No useEffect.
No loading state on the client.
That’s the first moment where it clicks - you’re not moving faster, you’re removing entire layers.
Where it starts to feel useful
The real benefit isn’t “server vs client.” It’s less JavaScript shipped to the browser.
Anything inside a Server Component:
- doesn’t increase bundle size
- doesn’t run on the client
- doesn’t need hydration
For data-heavy pages, this makes a noticeable difference.
Especially if you’re building dashboards or anything similar.
The mistake most people make
When you first get comfortable with Server Components, it’s tempting to push everything there.
That usually backfires.
You either:
- lose interactivity
- or start re-fetching too often from the server
The better approach is to split responsibilities cleanly.
A practical way to think about it
- Server Components → data, layout, structure
- Client Components → interaction, state, events
Something like this works well:
Page (Server)
├── Data-heavy sections (Server)
└── Interactive parts (Client)Once you follow this consistently, things start to feel predictable again.
When you still need Client Components
You can’t avoid them.
Any time you need:
useStateoruseEffect- click handlers
- animations
- browser APIs
you’re back on the client.
'use client';
import { useState } from 'react';
export function FollowButton() {
const [loading, setLoading] = useState(false);
return <button>Follow</button>;
}Tradeoffs (this part matters)
Server Components are great, but they’re not magic.
Some real downsides:
- harder mental model at first
- debugging isn’t as straightforward
- not ideal for highly interactive UIs
- tied closely to frameworks like Next.js
You don’t feel these in tutorials, but you do in real projects.
What changed for me
The biggest shift wasn’t technical, it was how I think about data.
Before:
fetch on the client, then render
Now:
render with data already available
That one change removes a lot of unnecessary complexity.
Final thought
Server Components don’t make React easier - they just move complexity to a better place.
If you’re using Next.js App Router, you don’t really have the option to ignore them anymore. But once you get past the initial confusion, they start to feel natural.
Not because they’re magical, but because they remove work you didn’t need to do in the first place.

