React Server Components vs. Server-Side Rendering

React 18 introduced a completely new paradigm to the frontend realm in the form of React Server Components (RSC). Next.js 13 followed suit by introducing support for RSC through their app directory, which is in beta now. This was in addition to the Server-Side Rendering (SSR) feature that Next.js has always been popular for. Now, it is natural to struggle to understand the differences between SSR and RSC at best, or at worst, you may even think these two are the same. This article is an attempt to clear the air.

Before we look at what RSC is and how it relates to SSR, it helps to understand how Single-Page Applications (SPA) work. To that end, let’s use the example of a simple application, which I have very creatively named Personal Assistant. This app has two pages, namely tasks, and reminders. The tasks page shows you a list of tasks and has a button. The button when clicked shows you a modal that you can use to add a task. The reminders page shows you a list of reminders and has a button with similar functionality. The tasks page is the default page, so when you open the app, that is the first page you see.

Single-Page Application (SPA)

Now, let’s see how this application works when it is a SPA. First, a user would enter the URL of this application into the address bar of a browser and hit enter. Then, the browser would send a request to the server that serves this application. The server would respond with an HTML file and a JavaScript bundle. The HTML file is just a scaffolding with the html, head, and body tags with a root div tag to render the application. The JavaScript bundle contains the code to render the application and add interactivity.

The browser would then render the tasks page and dispatch an API request to fetch the list of tasks. Once the browser gets the list of tasks, it renders the list on the screen as per the JavaScript code. If the user navigates to the reminders page, then the browser renders the reminders page and sends an API request to fetch the list of reminders. Once the browser receives the response, it renders the list of reminders.

SPA
Initial page load vs. Navigating to the reminders page in a SPA

The drawbacks of SPA

As you can see, both rendering and data fetching take place in the browser itself. Since the rendering takes place in the browser entirely, the server has to send the JavaScript code for the entire app to the browser. This means the JavaScript bundle is going to be huge and downloading it is going to take longer. This results in the First Contentful Paint (FCP) taking longer affecting the user experience. In other words, the user will have to wait for some time before they see any content on their screen. Besides, when the browser fetches data, the user’s internet connection is going to decide how fast the data is going to be painted on the screen. In addition, the API requests from the browser go through public networks and hence, are going to be comparatively slower.

Server-Side Rendering (SSR)

SSR mitigates these issues by rendering the initial page in the server itself. After rendering the initial page, it sends the pre-rendered HTML file to the browser along with the JavaScript code. If we take our example, when the user requests the app, the server will first fetch the task list from the backend. Then, it will render the tasks page and send the HTML file to the browser. Along with this, the server will also send the JavaScript code for the entire application.

So, unlike in SPAs, when we use SSR, the browser will be able to immediately show the tasks page. This reduces the time taken for FCP and consequently, vastly improves the user experience. Moreover, since it is the server that fetches the task list, the user’s internet speed is going to be immaterial. And the server can fetch the data a lot faster because the request goes through a private network.

SSR
Initial page load vs. Navigating to the reminders page when we use SSR

The drawbacks of SSR

However, the page will not become interactive immediately and the browser needs to wait till it completes downloading the JavaScript bundle to add interactivity. Once it completes downloading, then the HTML page can be hydrated using the JavaScript code. So, the user will have to wait for a while before they can start interacting with the app. For instance, if the user wants to add a task by clicking on the “Add Task” button, they will have to wait till the browser hydrates the HTML file. This is going to result in a poor Time to Interactive (TTI) score.

Additionally, once the browser loads the initial page, then this app is going to behave like a SPA. So, if the user navigates to the reminders page, then the browser is going to render this page and send a request to fetch the list of reminders. On that account, the only real advantage of SSR comes at the initial page-load stage with the FCP score, while TTI is going to be almost the same.

React Server Components (RSC)

RSC resolves these issues by incrementally streaming the content of the app from the server to the browser. Let’s see how this happens. When the user loads the app, the browser is going to send a request to the server. Just like what happens with SSR, the server is going to fetch the data from the backend. But we don’t need to wait till we get the data from the backend. There are parts of the app like the header and the side panel that we can render without the data. So, the server renders these components and streams them to the browser.

RSC
Initial page load vs. Navigating to the reminders page when we use RSC

To stream the component, the server first serializes this component into the JSON format before sending it. The browser can deserialize this JSON data and reconstruct the React component tree in the browser using the JavaScript code the server sends. Since the JavaScript code we need for this is very small, the browser can download the JavaScript file a lot faster. However, do note that Next.js 13 pre-renders this component and sends it as an HTML file instead of streaming it during the initial page load.

SSR vs. RSC

Since the server does not wait for the data, the FCP is going to be a lot faster in RSC than SSR. And once the server fetches the data from the backend, it can render the list and stream it to the browser. The browser can deserialize it and add it to the React component tree.

Besides, unlike in SSR, in RSC, server-side rendering does not take place during the initial page load only. Let’s examine this in detail. In RSC, there are two types of components—server components and client components. Server components, as the name implies, are rendered in the server and the browser renders the client components. We can define any component that doesn’t involve user interactivity like a mouse click or keyboard input, and use React hooks like the useState hook and the useEffect hook as a server component. The server can render these components and stream them to the browser.

So, the server needs to send the JavaScript code to render only the client components. Therefore, the JavaScript bundle size is going to be a lot smaller and faster to download. This means that the user interface can become interactive faster reducing TTI. Going back to our example, on the tasks page, only the “Add Task” button needs to be a client component. The server can render the rest. The code for the button is going to be expectedly small and faster to download. So, this button becomes clickable a lot earlier.

Now, what happens when the user navigates to the reminders page? Since the reminder list involves no user event and doesn’t use any React hooks, it can be a server component. So, the server can fetch the data, render the list and stream it to the browser, along with the code for the ‘Add Reminder’ button. As a result, unlike with SSR, we can offload all data fetching to the server.

The benefits of RSC

Accordingly, RSC allows us to leverage the advantages of server-side rendering throughout the application lifecycle, unlike SSR which performs server-side rendering only during the initial page load. In addition, since most of the components can be declared as server-side components, huge npm packages don’t need to be bundled with the JavaScript bundle that the server sends to the browser.

In conclusion, SSR and RSC are two different concepts. RSC differs from SSR by continuously rendering components in the server as the user navigates around the app, whereas SSR performs server-side rendering only during the initial page load. This means other than the initial components, all the other components are rendered in the browser when SSR is used. So, the browser has to download the JavaScript for the whole app just like in a SPA. In RSC, since the server renders most of the components and streams to the browser, the JavaScript that the browser needs is very small. This makes the UI load and become interactive faster. Furthermore, RSC can have the server fetch almost all of the data. This allows RSC to score better on FCP and TTI than SPA and SSR and provide a better user experience.

Leave a Reply

placeholder="comment">