What Are React Server Components (RSCs) and How Are They Different?

React server components are React components that run only on the server, a different paradigm from the mental model we've grown accustomed to where React simply runs on the client (in the browser).

With this new paradigm, the main difference comes from the output of the component and where the component runs, not necessarily the way you build (at least not so much).

There's still a shift in the way you think about building React apps, but it's not as drastic as you might think, and the goal of this website is to explain what changed, why things changed, and how to think about building React apps with this new paradigm.

This site is completely open sourced, so if you're interested in adding anything, changing anything, correcting any mistakes, or just want to see how it works, you can check out the repo on GitHub.

Server Side Rendering

Running React components on the server might seem familiar if you've heard of "server-side rendering" or SSR (a la Next.js or Remix), which is when you pre-render your React components on the server, and then hydrate it on the client.

What pre-rendering means is that we're essentially rendering your React component tree (pre-animations, pre-hooks, etc.) as HTML and sending it to the browser, using React's renderToString() method.

You can think of that HTML string as a screenshot of the very first frame you see when a page loads before any animations, state changes, effects, etc. happen. For example:

`<html><head><title>My App</></head><body><div id="root"><div class="App"><h1>Hello World!</></div></div></body></html>`

Here's the same thing, but formatted for readability:

`<html>
  <head>
    <title>My App</title>
  </head>
  <body>
    <div id="root">
      <div class="App">
        <h1>Hello World!</h1>
      </div>
    </div>
  </body>
</html>`

This string of HTML is then sent to the browser to render, and then hydrated by React once the browser loads React and all the necessary Javascript.

Now, while frameworks like Next and Remix are widely known for their capabilities in server rendering your React app, it's not quite the same as running a React component on the server, because there's still the step of hydration.

Hydration

"Hydration" essentially means re-rendering all of that HTML, plus the necessary JS in the user's browser so that the page can become interactive.

So even though SSR means that your user sees your content on their screen, it doesn't mean they can use the page yet.

React still has to do a lot of work in terms of binding your React components from the virtual DOM to the actual DOM, setting up event listeners/handlers, etc.

Even if you have a completely static page with no hooks, state, effects, click handlers, etc., React would still need to hydrate the page.

So the challenge SSR solved wasn't necessarily running React on servers to get rid of hydration, but rather giving the user content to look at while React loads up all the components.

How do React components work in a non-RSC world?

Now, if hydration is still necessary using SSR, that means that your React logic (i.e. what gets called before the return() statement) still needs to run on the client. For now, let's ignore solutions like Next.js's getServerSideProps and Remix loaders.

The reason why your component and its logic still runs on the client is because behind the scenes, Babel is transpiling your JSX into React.createElement() calls on the client-side.

These calls create React elements (JSON objects) that describe your components and tell the browser "these are the DOM nodes from the React virtual DOM that we'd like to add/change in the actual DOM".

For example, if we wanted to render a component in JSX that only contained a div that says "Hello World", we'd simply write:

export default function HelloWorld() {
  return <div>Hello World</div>
}

And Babel would transpile that into React.createElement() calls which would output something like:

{
  "type": "div",
  "props": {
    "children": "Hello World"
  }
}

I'm, of course, oversimplifying this process quite a bit, but if you want a more explicit and tangible example of what it means to go from React in just an HTML file with <script> tags to JSX and what creating elements looks like a bit more under the hood, Kent C. Dodds' free intro to React course here explains it really well.

So even if you server-side render your application, that initial HTML output is just going to be overwritten by the output of those React.createElement() calls, and the HTML is then going to be considered hydrated.

How do React Server Components differ from SSR?

The difference, then, between React Server Components and SSR is that the process SSR takes to render your React app is:

  1. Having the server render a snapshot of your JSX's initial HTML output with renderToString() and sending that to the browser

  2. Sending your component tree's JSX from the server to the browser

  3. Creating React elements (which, again, are just JSON representations of the DOM and your component tree) from that JSX in the browser with React.createElement() calls

  4. Rendering those elements to the DOM to finish hydrating the HTML on the client

Instead, with server components, what you're sending to the browser are just React components that have already been rendered as React elements by the server.

That is to say the browser is getting the serialized JSON output from your JSX directly from the server, instead of having to transpile the JSX itself client-side.

That's certainly a lot to keep track of, but Ryan Florence, one of the creators of React Router and Remix, had a great Tweet to explain this mental model as simply as possible:

Also, Dan Abramov's (from the React core team at Meta) discussion on GitHub, "RSC from Scratch. Pt. 1: Server Components", goes a lot more in depth on what it'd look like to recreate server components from the ground up, and I'd highly recommend reading that if you want to dive deeper into the technical details to clarify your own mental model.

Reconciliation in Server Components

It's worth noting that server components can be seen as somewhat static once they're loaded, because they can't and won't hydrate.

This is why if you've seen or played with any applications that use Next.js 13's app directory, you might've seen the error message pop up that a server component can't support hooks that require a browser, i.e. useState() or useEffect().

The reason why is that server components:

  1. Are considered "stateless" components, meaning they don't manage their own state, but rather rely on props from parent components or data fetching within their own component.

  2. Run only on the server, so browser-only APIs like window or document don't work, since the server has no concept of a browser, just an endpoint to which it sends responses.

Because they can't have state and can't access browser APIs, state can't be derived in server components, and effects do not work, because the components are shipped as elements already that can't be hydrated, and don't know when they are mounted to the DOM, updated, etc.

If you're familiar with traditional server-side frameworks like Rails or those in PHP, this might seem like a familiar concept, and you might be wondering why server components are addressing a solved problem.

However, the key difference here is that data changes don't trigger a reload of the entire page, and this is one of the key improvements over getServerSideProps and Remix loaders.

Instead of calling your entire route (i.e. your entire page re-fetching getServerSideProps or /your-example-path/index.php path being hit again) to fetch and replace stale data, React Server Components' architecture allows your application to re-execute your component tree and perform its reconciliation process to update only the components and DOM nodes that have changed after a mutation for example, or after new data is available.

What this means is that state is completely maintained on the client, so if your user is typing something in the search bar of a blog and new comments are streaming into the current blog post that the user is on, they:

  • Won't have to reload to get those new comments.

  • Won't lose their search input if they navigate to a new blog post (provided the search bar is part of a layout that wraps the comments and blog post)

On the server-side, a new, updated component tree will be serialized and sent to the browser for the client to diff and patch, and no unnecessary re-renders or re-fetches will be triggered.

Example Diff

Old component tree
New component tree
ServerClient
Only the green outlined components in this component tree diff will be re-rendered, because only those two have changed. The rest of the tree is unaffected, and will be preserved.

Each component can be responsible for its own data/data-fetching, rather than the entire route having to be responsible for dynamic data.

The other benefit is that since these components are running on the server, you have access to things like direct database access, private API access, and file system access directly within components themselves.

So not only can each component be responsible for its own data, but each component can be kept secure from client-side data or API-key leaks, whereas previously, in a framework-less approach, you'd most commonly see an effect being used for data fetching through a public API endpoint.

So then, many people have the question: what happened to "regular React" or the React we all know?

Well, it's still there, but now, "regular" React components (i.e. React pre-Server Components) are called "client components".

Only the name changed, though, but they're exactly the same and nothing at all has changed about them in terms of functionality.