To answer the question of why you actually need React Server Components, it makes sense to backtrack a bit. I myself have been developing so-called single page applications (SPAs) for over 10 years. These are applications in which the user calls up an HTML page only once and further navigation takes place via JavaScript and reloaded data. At that time, the whole thing wasn’t called SPA but RIA (Rich Internet Applications), but the basic idea remains the same. These applications have gained popularity in the last 10 years. Often people go straight to Angular or React for new web applications, which have many advantages – but as always in life, these advantages go hand in hand with disadvantages.
Disadvantages of SPAs
One of the disadvantages of single page applications is that they often cannot be indexed well by search engines, because they only deliver a small HTML framework without content and the rest is loaded by JavaScript. Search engines like Google are working on indexing such pages, but you should not rely on that. Additionally, SPAs tend to load huge amounts of JavaScript before the user can start using the application. This problem has been reduced due to faster and faster internet access. But when you are sitting on a train between Cologne and Düsseldorf, you will get annoyed after nothing happens for quite a while. The combination of both problems results in a user who waits for a minute for “the few megabytes of JavaScript” to download while looking at a blank page the whole time. Of course, no one wants to subject their own users to that. Since Google has also recognized that users do not like to wait, loading times are now also included in search engine ranking.
Server-side rendering as a solution?
Server-side rendering, or SSR for short, has established itself as a solution to the problem that search engines cannot index SPAs and users should not see a blank page during load time. The React community, in particular, has been talking about components and entire applications being “isomorphic” for several years. This is a technical mathematics term, but in this context, it only means: The component or application can be executed both server-side and in the browser. Typically, the server renders the components for display in the browser using Node.js. The resulting HTML skeleton is then sent to the client. Rendering there should be complete before the JavaScript code starts loading and the SPA becomes interactive.
We can use server-side rendering to make sure that the blank page is not visible for so long, and search engines can index our page. Still, we have to download the complete JavaScript code, and strictly speaking, rendering that was done server-side is done again in the client.
React Server Components introduced
This is where React Server Components become an interesting concept. React Server Components allow us to completely offload individual components, along with their dependencies, to the server. This means they no longer run in the browser, and that means the amount of JavaScript that needs to be downloaded (and executed) shrinks. In Facebook’s initial experiments in a production environment, React Server Components resulted in a 29 percent smaller bundle size, or nearly a third, according to Facebook’s video presentation on React Server Components [1]. And that’s just the beginning.
Let’s take a look at an example of what this might look like. Let’s assume that we are working on a project that already supports React Server Components. Facebook provides a demo project for this purpose. Let’s start with a component that displays Markdown text as HTML. For this, we use the open source library marked in Listing 1.
Listing 1
import marked from 'marked'; const longMarkdownString = `/* Long Text */`; export default function MarkdownExample() { return ( <div className="markdown" dangerouslySetInnerHTML={{ __html: marked(longMarkdownString), }} /> ); }
The syntax with dangerouslySetInnerHTML is perhaps a bit unusual but has nothing to do with React Server Components, it simply ensures that we render HTML directly without any form of sanitation. Other than that, the component looks like a normal React component, and it is. It can be easily integrated into an existing React application and will run in the browser – including our dependency on marked. We can now turn the component into a React Server Component. We do that by renaming the file from MarkdownExample.js to MarkdownExample.server.js. Now the file is no longer executed in the browser, but on our server. As a result, the Markdown library is no longer needed in the browser – instead, the conversion from Markdown to HTML is done on the server-side. In the sample code, we simply put the Markdown string in a variable. Here, we could communicate with an API on the server-side instead. This doesn’t have to be accessible from our browser, but only from our server. We could also simply read the data from the server’s file system, or even from a server-side database.
At this point, you may be asking where is the advantage compared to a REST API that provides our Markdown directly as HTML. While the question is valid, we will see that there are still advantages to using React Server Components.
In the context of React Server Components, which are identified by the filename suffix .server.js, there are also Client Components, with the .client.js suffix in the filename. All components that we have developed so far for the browser are also client components. But there is also a mixture of both, so-called shared components, which can run on the client and the server.
For example, let’s imagine that we are developing a CMS that renders Markdown content. For the end users, we render the Markdown in a React Server Component and send it to the user ready rendered. An editor could also have an “edit” button displayed next to the content. When this button is clicked, the editor is offered a text field to edit. We can now use our Markdown component on the client-side and show the editor a preview of their content in real-time, without having to communicate with the server. However, we need to load the Markdown component with its dependency on the marked library the moment the editor clicks on “edit”, and not when the end user looks at the article. To achieve this, we need a bundler that supports that. The React team at Facebook is cooperating with the webpack team on this, and there is already a pre-release version of a plug-in that solves this problem for us.
Let’s look at our MarkdownExample component again. Now, this can be used both as a React Server Component and as a Client Component. Since React Server Components and SSR are not mutually exclusive, we can also easily use this component in server-side rendering with NextJS or Frontastic, for example.
You may still be asking if a simple API for converting Markdown to HTML wouldn’t work just as well. And of course, that’s the case for now. But for the editor, we would then have to rebuild the functionality in JavaScript or the preview would no longer be real-time. If we build the Markdown conversion once as an API and once as a React component, there is also a risk of the results diverging.
Avoiding waterfalls
Let’s move on to another positive effect that can result from using React Server Components. In React applications without Server Components, there is sometimes a so-called “waterfall” in the browser. Let’s imagine a component like the following, which represents an article on javascript-conference.com: <Article articleId={4711} />. This component is apparently responsible for loading the article data itself because it only gets passed the article ID. It represents the article, including the comments. The component’s code might look like the one shown in Listing 2.
Listing 2
const Article = ({ articleId }) => { const [article, setArticle] = useState(null); useEffect(() => { fetchArticle(articleId).then((articleData) => { setArticle(articleData); }); }, [articleId]); if (article === null) { return <LoadingSpinner />; } return ( <> <ArticleDetails article={article} /> <ArticleComments article={article} /> </> ); };
The component uses a reusable hook to load an article. Since we probably don’t load all the comments when we load an article, the ArticleComments component itself has to load the comments again. Now, let’s take a look at what happens when our Article component is rendered.
In the first rendering, article is initialized with null. The effect specified in useEffect is not executed directly during rendering. This means that article is always null in the first rendering. So, the component always renders a LoadingSpinner in the first rendering. After rendering, the effect is executed, which loads the data from the server and sets the article in the state asynchronously after successful loading. By changing the state, a new rendering is executed. In this rendering, the article is no longer null, and we render the ArticleDetails and ArticleComments components. If the ArticleDetails component loads information about the author of the article and the ArticleComments component starts loading the comments, we have our waterfall – an unwanted sequential loading of data that we would have liked to load in parallel. The page builds up piece by piece, and content may jump back and forth as new data becomes available.
Now let’s imagine that we use exactly the same component as a Server Component. Please not that this won’t really work like that, it’s just an example (more on that later). We would have to adjust the component a bit, but conceptually it remains very similar, and so I’ll spare us this digression. Server-side we would have a waterfall where first the article data is loaded and then the comments and auto data. On the server-side, however, this is usually a much smaller problem, as the lower latency means that the individual calls can be completed much more quickly. The problem of content being displayed bit by bit disappears completely when done server-side.
There are, of course, other ways to get rid of such waterfalls. However, those often involve a certain change in the architecture of the React application, which is not to everyone’s liking. The way shown above is sometimes called “fetch-on-render” and is relatively popular and widely used. In that respect, it’s nice to see that React Server Components can shrink the problem.
The downsides of React Server Components
As always in life, React Server Components is not a magical solution, and it’s important to look at its downsides.
Let’s start with the obvious: React Server Components are just a proposal so far. They are the result of lengthy research at Facebook, and the answer to several problems Facebook has had. The team at Facebook is optimistic that this new approach can solve problems for many applications. There is no release date yet for when we can start using React Server Components in our applications. The React team currently assumes that React Server Components will first find their way into frameworks like NextJS, and direct use is even further away. But there is no release date for that either.
Also, it’s not like any of our existing components will automatically work as a React Server Component. For example, some hooks do not work server-side; for example, useState or useEffect are not supported in React Server Components. That’s why our Article component above doesn’t work as a React Server Component. However, we can implement the same functionality as a React Server Component, just without these hooks. By the way, this restriction also implies that any (custom) hooks that use one of the unsupported hooks cannot be used in React Server Components.
The same is true the other way around – not all React Server Components can also be executed in the browser. This is partly the case because React Server Components can access APIs that are not available client-side. For example, in React Server Components we can access the server’s file system. Fortunately, we can’t do that from the browser.
Another limitation is that Server Components may render Client Components (and native HTML elements), but Client Components may not render Server Components. However, it is possible to pass rendered React Server Components from a React Server Component as a prop to a client component, for example, as children.
By the way, it is interesting to understand that when we use a Client Component inside a Server Component, the rendering of the Client Component takes place regularly on the client. If the Server Component wants to send new data to the Client Component, this data is transferred via http and given to the Client Component – which then renders itself again, but does not lose its state.
Conclusion and outlook
Server Components could actually accelerate the spread of React once again, as the framework can also be used in applications where it was previously not possible. They also allow frontend developers, who previously had little insight into the backend, to develop parts of the backend in ways they are familiar with. This is a hurdle that fullstack developers often lose sight of. Nevertheless, we are moving in a direction here that allows frontend developers to be able to take on more responsibility for the complete user interface.
The ability to run the same code server-side or client-side depending on the situation is something that I think is a very exciting concept. It allows for interesting options, such as more complex algorithms that need lots of training data to be added. The training data is sometimes very large and makes the downloaded JavaScript look small. For example, one could run the algorithm server-side while the training data is still being loaded. Only when everything is available offline in the browser would the calculation then be outsourced to the client, which should improve performance and save computing capacity on the server-side, and possibly even make our application work offline.
Basically, of course, this is not a revolutionary new development. Many of the “advantages” gained through Server Components are inherent in a traditional web application without JavaScript in the browser. Facebook also explicitly says that they have been inspired by such traditional web architectures. Nevertheless – React Server Components allow us to get the best of both worlds. That’s why I’m very excited to experience React Server Components in a real application sometime in the future.
Links & Literature
[1] https://www.youtube.com/watch?v=TQQPAU21ZUw