A superb breakdown of the changes being made in React 18+ around the new React Server Components paradigm. Josh has a knack for explaining complex problems in simpler ways, and this is no exception; the little graphs showing data flows in the various React paradigms are particularly useful.
On the difference between CSR and SSR, with a bonus explanation of hydration that is clearer than I've ever seen before:
Server Side Rendering was designed to improve this experience. Instead of sending an empty HTML file, the server will render our application to generate the actual HTML. The user receives a fully-formed HTML document.
That HTML file will still include the<script>
tag, since we still need React to run on the client, to handle any interactivity. But we configure React to work a little bit differently in-browser: instead of conjuring all of the DOM nodes from scratch, it instead adopts the existing HTML. This process is known as hydration.
On SSR and hydration:
A server generates the initial HTML so that users don't have to stare at an empty white page while the JS bundles are downloaded and parsed. Client-side React then picks up where server-side React left off, adopting the DOM and sprinkling in the interactivity.
The way I see it, “Server Side Rendering” is an umbrella term that includes several different rendering strategies. They all have one thing in common: the initial render happens in a server runtime like Node.js, using the ReactDOMServer
APIs. It doesn't actually matter when this happens, whether it's on-demand or at compile-time. Either way, it's Server Side Rendering.
On how SSR feels like it is helping (by presenting a loading state faster) but ultimately doesn't always improve the actual UX (and, arguably, can degrade it in certain scenarios e.g. where loading state is not well conveyed):
[SSR] is an improvement — a shell is better than a blank white page — but ultimately, it doesn't really move the needle in a significant way. The user isn't visiting our app to see a loading screen, they're visiting to see the content (restaurants, hotel listings, search results, messages, whatever).
On how SSR feels a little illogical once you start to graph out the data flows involved:
But doesn't this flow feel a bit silly? When I look at the SSR graph, I can't help but notice that the request starts on the server. Instead of requiring a second round-trip network request, why don't we do the database work during that initial request?
On how Server Components can't have side effects or mutations – they only render once:
The key thing to understand is this: Server Components never re-render. They run once on the server to generate the UI. The rendered value is sent to the client and locked in place. As far as React is concerned, this output is immutable, and will never change.
On the impact of moving logic to a single-render paradigm:
This means that a big chunk of React's API is incompatible with Server Components. For example, we can't use state, because state can change, but Server Components can't re-render. And we can't use effects because effects only run after the render, on the client, and Server Components never make it to the client.
On why the term "Client Component" is a bit confusing:
The name “Client Component” implies that these components only render on the client, but that's not actually true. Client Components render on both the client and the server.
On the fact that "client" is no longer the default:
In this new “React Server Components” paradigm, all components are assumed to be Server Components by default. We have to “opt in” for Client Components.
That standalone string at the top, "use client"
, is how we signal to React that the component(s) in this file are Client Components
On when to use Client vs. Server Components:
As a general rule, if a component can be a Server Component, it should be a Server Component.
Some of our components will need to run on the client, because they use state variables or effects.
On how state works now, particularly in high-level components like page layouts:
In order to prevent this impossible situation, the React team added a rule: Client Components can only render other Client Components. When we convert a component to a Client Component, it automatically converts its descendants.
On how the React DOM now repopulates itself with the content of Server Components (this feels, well, quite clunky in some ways, and does mean that you still have to ship some JS even though you're fully rendering on the server):
Typically, when React hydrates on the client, it speed-renders all of the components, building up a virtual representation of the application. It can't do that for Server Components, because the code isn't included in the JS bundle.
And so, we send along the rendered value, the virtual representation that was generated on the server. When React loads on the client, it re-uses that description instead of re-generating it.
On some of the core benefits of React Server Components (and the benefits of tools like Bright built to work on the server only):
A proper syntax-highlighting library, with support for all popular programming languages, would be several megabytes, far too large to stick in a JS bundle. As a result, we have to make compromises, trimming out languages and features that aren't mission-critical.
But, suppose we do the syntax highlighting *in a Server Component.* In that case, none of the library code would actually be included in our JS bundles. As a result, we wouldn't have to make any compromises, we could use all of the bells and whistles.