React Server Components: A Wall Between Client and Server

That's a pretty stuffy sounding title. But the React team's march towards (some might say shoving down everyones' throats of) server components represents by far the biggest design change in the nearly 10 year long history of the library. There's quite a lot to think through here. Is this actually going to be "a thing". What problems do these components actually solve? What problems might they introduce? I think the answer to both questsions, as of now, is "plenty!".
React Until Now
React started off as way to build Single Page Apps sanely, and it's still primarily used like that today. A SPA bundled with an off-the-shelf webpack config is still by far the easiest way to ship a React app; there isn't a lot of extra overhead to getting the thing working, and you can opt in to whatever packages/libraries/massively bloated depdencies you want to accomplish your goal.
But SPAs have serious tradeoffs when it comes to:
- Performance: massively bloated Javascript bundles that have to be sent down the wire to and then unpacked/parsed/run by clients.
- Maintainability: Giant libraries of components whose individual purposes can be difficult to keep track of.
- State Management: We've been through nearly a decade of different approaches to maintaining a ton of client side state, and in my experience it always turns into a disaster.
Server side rendering came along, and has especially grown in adoption now that Next.js has made it much, much easier to manage (my first React job involved a completely hand rolled SSR Node server, and it was insane!). Server side rendering helps improve perceived and real performance by ensuring some actual HTML is sent down the wire for the browser's first paint, as opposed to just Javascript. This, coupled with clever code splitting techniques and UI intentionally designed for progressive enhancement, allows developers to create apps that are both "fast" and plenty interactive. A consequence of this is the need to manage and pay for actual servers or some kind of "serverless" solution.
React Server Components: Solving a Problem? Or Inventing New Ones?
Alright so let's get into it. What are React Server components? Simply put, they are React components that are used ONLY on the server as templates. They are not included in the client side bundle sent down the wire!
Let's single out and highlight some things here:
- Server components have access to standard Node APIs for accessing data sources (files, datastores) without using effect hooks.
- Server components can't include any kind of state or user activity.
- They aren't bundled into the client side part of the app.
So what does this mean?
- You can directly fetch data on the server and
- Keep any expensive data parsing logic on the server and
- Leave any expensive dependencies/libraries on the server and
- Still progressively enhance your UI in a non-blocking way through the use of streaming.
Pretty cool. But of course, we need interactivity in our apps. This is handled through the use of what are now called client components. These components are what we're all used to. They can have state, interact with Web APIs, etc. And they are bundled and shipped over the wire to the browser.
In practice, the React team intends for apps to be structured like the diagram below. I don't know exactly who made this diagram originally, but it's all over the web now, so credit to whomever deserves it!

Essentially, the idea is that developers should favor server components. Data fetching should ideally be done on the server, and then passed to client components if interactivity is needed. Developers should compose their apps/components to isolate client behavior such that the absolute minimum amount of Javascript needs to be bundled. I love the sound of that...
Examples
Below is a server component. Notice that it can interact directly with a database.
async function Demo() {
// Talking directly to a DB
const data = await fakeDBCall("some_table", "SOME QUERY");
return (
<div>
<h1>Here is some data</h1>
{data && (
<div>
{data.foo.map((c: string) => (
<ClickyThing data={c}/>
))}
</div>
)}
</div>
);
}
And then if we wanted to have some sort of interactivity for rendered data, we'd create a client component that can consume the data as props. We want to take care to isolate as much logic as possible so that client components don't contain more markup than they need.
"use client";
function ClickyThing(props) {
return (
<button
onClick={() => {
console.log("wow");
}}
>
Clicky
</button>
);
}
Notice the use of the "use client" directive. This is the Next.js convention for telling React/Webpack that a component should be included in the client bundle. This can also be indicated by filename.
Earlier I mentioned that server components can be streamed to clients while we wait for things to happen like data fetching. This is achieved (as of now) by composing your app such that server components with a data dependency are wrapped in a <Suspense> boundary. This allows React to stream in updates as they are available. Very cool.
function Wrapper() {
return (
<div>
<h1>This will render in the first</h1>
<h2>And the below will stream in when ready</h2>
<Suspense fallback="loading">
<Demo />
</Suspense>
</div>
)
}
How the Hell Does This Work?
Well, I will start by saying I am not 100% sure. But there are a few interesting things that I do know are happening by reading some source code. A summary:
- The server attempts to render the entire component tree as JSON.
- Server components are "rendered down" to their raw HTML tags such that their description is fully serializable
- This JSON simply describes a component and its corresponding HTML tag and props as JSON:
([1,"b:[\"$\",\"div\",null,{\"children\":[\"$\",\"h1\",null,{\"children\":[\"foo: \",\"bar\"]}]}]\n"])
- If a CLIENT component is encountered while rendering the tree on the server, a reference to it's eventual place in the client JS bundles is written instead. In the client, React will know where to find that component (it's a function, and functions can't be serialized, hence the need for a reference to the bundle)
- All of this is intended to be STREAMED, hence the JSON as opposed to plain old HTML. JSON works well for streaming due to line breaks, etc.
- Traditional SSR can be used alongside this for a fast first paint, I think. But god knows how.
- I'm already too confused to continue attempting to figure out how this shit works, but it's cool!
Consequences / Implications
As previously highlighted, server/client components really do represent a fundamental shift for React. However, I don't anticipate a particularly smooth transition here. This is such a breaking change that a lot of applications probably won't need a mere patching, but a significant rewrite or restructuring (at least if they want to opt into the streaming RSC benefits and not just make everything a client component).
So the cost will be high! Are the benefits worth it? As usual, I think it really depends on your application. Many apps have always been and will always be fine as SPAs. Traditional SSR also gets the job done perfectly well for a lot of others. Server components necessarily introduce a LOT of complexity to any existing React application. I'm sure the rougher edges will be smoothed out over time, but the shift to this new pattern itself is foundational, and the scope of the change can't easily be sidestepped.
Controversy!
It's worth noting that such a drastic change in direction for React is being regarded with some degree of skepticism. There is certainly an "if it ain't broke, don't fix it" argument to be made here - SSR + progressive enhancement works quite well and addresses both performance and interactivity. In my opinion, it's certainly reasonable to question whether the lift in complexity of server components is worth the benefits.
One could also make a reasonable argument that everything is just coming full circle again - moving as much logic back to the server as possible after the trend shifted so much logic (and Javascript) to the client. I think this argument is pretty reductive, especially if one considers the potential benefits of leaning into Node streaming APIs.
Lastly, there's the issue of profit! React doesn't make any money itself. So why are all these for profit companies throwing expensive resources at the project? The skeptic will look at the situation and claim it's all a conspiracy to make people pay for expensive servers and Platform-As-A-Service solutions, aka Vercel*. I don't buy this.
Sources
https://www.plasmic.app/blog/how-react-server-components-work https://marmelab.com/blog/2023/06/05/react-angularjs-moment.html https://github.com/reactwg/server-components/discussions/5
*This blog is hosted on Vercel, but since no one reads it, I don't pay them shit!