React 18 server components deep dive
Tags: react, javascript, frontend
Published: 2023-05-21 19:50:21

React Server Components (RSC) is a promising feature that can have a significant impact on page load performance, bundle size, and the way we write React applications. This feature is still experimental in React 18, but it's worth understanding how it works under the hood.

### Separation of server and client components

![A diagram that illustrates the rules that apply to server and client components](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vyhy8fge3fpor9hhj8pc.png)

React Server Components allows the server and client (browser) to work together to render your React application. Some components in the React element tree are rendered by the server, and some are rendered by the browser. This is not the same as server-side rendering (SSR). SSR simulates an environment for rendering a React tree into raw HTML, but it does not distinguish between server and client components.

The React team has defined server and client components based on the extension of the file in which the component is written: if the file ends in .server.jsx, it contains server components; if it ends in .client.jsx, it contains client components. If it ends in neither, it contains components that can be used as both server and client components.

### Life of an RSC Render

The life of a page using RSC always begins on the server, in response to an API call to render a React component. The server then serializes the root component element into JSON. The end goal here is to render the initial root server component into a tree of basic HTML tags and client component placeholders.

The server then sends this serialized tree to the browser, and the browser can do the work of deserializing it, filling the client placeholders with the actual client components, and rendering the end result.

### RSC and Suspense

Suspense plays an important role in RSC. Suspense allows you to throw promises from your React components when they need something that is not yet ready (fetching data, lazily importing components, etc). These promises are captured at the suspense boundary. When a promise is thrown from rendering a Suspense subtree, React stops rendering that subtree until the promise is resolved, and then tries again.

### RSC Wire Format

The server outputs a simple format with a JSON blob on each line, tagged with an ID. This format is very streamable - once the client has read a full line, it can parse a snippet of JSON and make some progress.

### Consuming the RSC Format

The `react-server-dom-webpack` package contains the entrypoints that take the RSC response and rebuild the React element tree. When the server finishes loading the data, it outputs the rows for the module reference - which defines the module reference to the component - and the React element tree that should be swapped into where the reference is.

### RSC vs. Fetching Data from Client Components

Whether RSC is better than fetching data from client components depends on what you are rendering to the screen. With RSC, you get denormalized, "processed" data that maps directly to what you are showing to the user. If the rendering requires multiple data fetches that depend on each other in a waterfall, then it is better to do the fetching on the server - where data latency is much lower - than from the browser.

### RSC and SSR
With React 18, it is possible to combine both SSR and RSC, so you can generate HTML on the server and then hydrate that HTML with RSC in the browser.

### Server-Side Rendering (SSR)

Server-side rendering (SSR) is a technique where a React application is rendered on the server into a static HTML string, which is then sent to the client. This can improve performance and SEO by allowing the page to be displayed before all the JavaScript has been loaded and parsed.

```jsx
import express from 'express';
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import App from './App';

const server = express();

server.get('/', (req, res) => {
  const appString = ReactDOMServer.renderToString(<App />);
  
  res.send(`
    <!DOCTYPE html>
    <html>
      <head>
        <title>My App</title>
      </head>
      <body>
        <div id="root">${appString}</div>
        <script src="/bundle.js"></script>
      </body>
    </html>
  `);
});

server.listen(8080);
```

In this example, the App component is rendered to a string on the server, and that string is inserted into the HTML response. The client then "hydrates" this static HTML into a fully interactive React application.

### React Server Components (RSC)

React Server Components (RSC) allow the server and client to work together to render a React application. Some components render on the server, and others render on the client. This can improve performance by reducing the amount of JavaScript sent to the client and allowing the server to fetch and render data directly.

Here's a simple example of RSC:

```jsx
// Message.server.js

import {db} from './db.server';

function Message({id}) {
  const message = db.messages.get(id);
  return (
    <div>
      <h1>{message.title}</h1>
      <p>{message.body}</p>
    </div>
  );
}

export default Message;
```

```jsx
// App.client.js

import {useState} from 'react';
import Message from './Message.server';

function App() {
  const [selectedId, setSelectedId] = useState(1);
  return (
    <div>
      <button onClick={() => setSelectedId(selectedId + 1)}>
        Next
      </button>
      <Message id={selectedId} />
    </div>
  );
}

export default App;
```

In this example, the Message server component retrieves and renders a message based on an id prop. The App client component maintains a piece of state (selectedId) and renders the Message server component with the current id.

### Practical Recommendations

- **Understand the difference between server and client components:** Understanding which components are rendered by the server and which are rendered by the client is critical to using RSC effectively.

- **Use Suspense with RSC:** Suspense allows you to handle promises in your React components, which is integral to the functioning of RSC

- **Optimize for performance:** RSC can significantly improve page load performance and reduce bundle size. However, it's important to measure and monitor these metrics to ensure that you're getting the expected benefits.

- **Consider the tradeoffs:** While RSC offers many benefits, it also comes with tradeoffs. For example, server components cannot use states or effects, which can limit their use in certain scenarios.

- **Experiment with RSC in non-critical parts of your application:** Given the experimental nature of RSC, it may be a good idea to start experimenting with it in non-critical parts of your application. This will give you a better understanding of how it works and how it can be used effectively.

- **Combine RSC with other React features:** RSC can be combined with other React features like Suspense and Concurrent Mode to build more efficient and user-friendly applications.

## Examples.

Let's look at some code examples to illustrate how React Server Components (RSC) work.

First, let's define a server component. Server components are identified by the .server.js extension:

```jsx
// Message.server.js

import {db} from './db.server';

function Message({id}) {
  const message = db.messages.get(id);
  return (
    <div>
      <h1>{message.title}</h1>
      <p>{message.body}</p>
    </div>
  );
}

export default Message;
```

In this example, Message is a server component that fetches a message from a database based on an id prop. Notice that we're directly importing a server module (db.server) and using it to fetch data. You can't do this in a client component.

Next, let's define a client component that uses this server component:

```jsx
// App.client.js

import {useState} from 'react';
import Message from './Message.server';

function App() {
  const [selectedId, setSelectedId] = useState(1);
  return (
    <div>
      <button onClick={() => setSelectedId(selectedId + 1)}>
        Next
      </button>
      <Message id={selectedId} />
    </div>
  );
}

export default App;
```

In this example, App is a client component that maintains a piece of state (selectedId) and renders the Message server component with the current id. When the Next button is clicked, the selectedId is incremented, causing the Message component to be rendered again with the new id.

This is a simple example, but it illustrates the key idea behind RSC: server components can be used to fetch and render data on the server, while client components maintain interactivity on the client.

### Comparison

While both SSR and RSC involve rendering on the server, they serve different purposes and have different tradeoffs:

SSR is primarily concerned with improving performance and SEO by sending static HTML to the client. However, it requires sending all JavaScript to the client, which can be large and slow to parse.
RSC, on the other hand, allows the server and client to collaborate on rendering, which can reduce the amount of JavaScript sent to the client and improve performance. However, server components cannot use states or effects, which can limit their use in certain scenarios.
In summary, both SSR and RSC are powerful techniques for rendering React applications, each with its own strengths and tradeoffs. Understanding these differences can help you make more informed decisions about which technique to use in your projects.

Links:
https://react.dev/blog/2022/03/29/react-v18
https://react.dev/blog/2020/12/21/data-fetching-with-react-server-components
https://react.dev/blog/2023/03/22/react-labs-what-we-have-been-working-on-march-2023#react-server-components
https://shopify.github.io/hydrogen-v1/tutorials/react-server-components
https://www.plasmic.app/blog/how-react-server-components-work