React Server Components are the biggest shift in how React apps are built since hooks, and also the most misunderstood. The confusion almost always comes from one missing idea: in an RSC app, your component tree is split across two environments — some of it runs on the server and never ships to the browser, the rest runs on the client as it always has.
Server Components
Fetch data directly · Reach the database · Zero JS shipped · Render to a stream
Client Components
"use client" · State & effects · Event handlers · Browser APIs
Server by default, client on purpose
In the App Router, every component is a server component unless you opt out. Server components run on the server, can fetch data directly, and ship no JavaScript to the browser — they arrive as already-rendered output. You only add "use client" when a component genuinely needs interactivity: state, effects, event handlers, or browser-only APIs.
The question stopped being "where do I fetch data" and became "where does this need to be interactive".
The boundary flows one way
Once you mark a component "use client", everything it imports becomes client code too. So push the boundary as far down the tree as you can — keep data-heavy layout on the server, and isolate the small interactive leaves (a like button, a dropdown) as client components. A common pattern is a server component that fetches data and passes it as props into a thin client child.
Data fetching gets boring again
Because server components run on the server, you fetch data inline with async/await — no effect, no loading state, no client cache to manage for the initial render. The data never leaves the server except as rendered HTML, which is also a quiet security win: your keys and queries stay server-side.
// a server component — runs on the server, ships no JS
export default async function Page() {
const posts = await db.posts.findMany(); // direct, no API hop
return <PostList posts={posts} />; // client leaf for interactivity
}
Where people trip up
The classic mistakes: reaching for useState in a server component (it cannot), passing functions across the boundary as props (you cannot), or marking the whole page "use client" and losing every benefit. Keep the interactive surface small and intentional, and RSC stops feeling strange and starts feeling like less work.
Server Components are not a new API to memorise so much as a new question to ask of every component: does this need the browser at all? Answer that honestly and the architecture mostly designs itself.