Back to articles
ReactNext.js

React Server Components, Explained Like You're Busy

A plain-English mental model for React Server Components — what runs where, why 'use client' exists, and a concrete example of composing server and client components without shipping your database to the browser.

Yash Thakur
4 min read
React Server Components, Explained Like You're Busy

The short answer: A React Server Component runs on the server and sends plain HTML to the browser — its JavaScript never ships to the client — while a Client Component (marked with "use client") ships and hydrates so it can use state, effects, and event handlers. The whole skill is drawing the line between them: server for data and secrets, client for interactivity.

Every time React Server Components come up, someone explains them with a diagram of serialization boundaries and a lecture on the Flight protocol, and the room's eyes glaze over. You're busy. Here's the version I give my team on their first day touching an App Router codebase.

The one idea that unlocks everything

A React Server Component runs on the server, finishes its job, and sends plain HTML (plus a little data) to the browser. Its JavaScript never ships to the client. A Client Component is the React you already know: it ships to the browser, hydrates, and runs there so it can use state, effects, and event handlers.

That's the whole thing. The server does the work that needs secrets, databases, or heavy dependencies. The client does the work that needs interactivity. The art is drawing the line between them in the right place.

The default flipped, and that's the point

In the App Router, everything is a Server Component unless you say otherwise. You opt a file into client-land with a "use client" directive at the top. This is the inversion that trips people up: you used to assume all your code ran in the browser. Now you assume none of it does until you ask.

Why is this a good default? Because most of a typical page — layout, text, a list of fetched records — has no interactivity at all. Rendering it on the server means that code and its dependencies never bloat the bundle the user downloads.

A concrete example

Say I'm building a dashboard that lists guests and lets you favorite one. The list comes from the database. The favorite button needs an onClick and some local state. Here's how the split falls out:

Notice there's no useEffect, no loading spinner, no fetch call. The component is async, awaits the data, and renders it. The db import and every row of guest data stay on the server. The browser receives finished HTML.

The button is the only JavaScript that ships for this interaction. The 50-row list, the database client, the query logic — none of it is in the bundle.

The rules that keep you out of trouble

Two things will bite you until they click:

A Server Component can render a Client Component, but not the reverse by import. The server composes the tree. If you need a server-rendered thing inside a client component, pass it as children or a prop, don't import it.

Props crossing the boundary must be serializable. You can pass strings, numbers, arrays, plain objects. You cannot pass a function, a class instance, or a database connection to a client component — there's a network hop in between, and functions don't travel over the wire. (Server Actions are the sanctioned exception for passing behavior back.)

When to reach for which

My heuristic: start on the server. Only sprinkle "use client" on the leaves of the tree that genuinely need state or events — a button, an input, a dropdown. Keep those islands as small as possible so the interactive JavaScript stays tiny.

Server Components aren't a rewrite of React. They're React finally admitting that most of what we render never needed to run in the browser in the first place. Once that clicks, the mental model is almost boring — and boring is exactly what you want from your framework.

Written by Yash Thakur

Senior React Developer · 8+ years building for the web

More articles

Keep reading