React Server Components Explained - Are They Better Than SSR?
React Server Components Explained - Are They Better Than SSR?

This post will explain React server components and their differences from server-side rendering.

React server components can be as confusing as useEffect was when it was new. Many people don't understand their purpose, why they're needed, or how to use them correctly.

What do we have?

Currently, we primarily use client-side rendering with React, where JavaScript is rendered directly on the client side. This approach covers about 90% of the applications we build.

Additionally, we have server-side rendering, where React is rendered initially on the backend using Node.js, before being sent to the client.

How ssr works

When we utilize server-side rendering, the server generates the markup from our React application and renders it on the page. It also provides all the data used in the rendering process via a global window object to the client. The client can then use this data to rehydrate the markup and data on the client side. After this process, we essentially have a client-side application with typical event handling, local storage, and other client-side functionalities.

This approach is beneficial because it caters to different needs. We use client-side React when we only require a client-side JavaScript application. On the other hand, we opt for server-side rendering when we want to render the application on the server initially. This ensures that our page isn't blank at the beginning and is advantageous for search engine optimization (SEO) because search engine crawlers like Google can read the markup of our page, which they can't do with a client-side application.

What Comes Next?

Now, there's a new concept emerging called React server components, and it's not entirely clear why they're necessary.

React server components have not been officially released yet. They are currently accessible through other frameworks that leverage new React features.

As of now, the only framework where you can utilize React server components is Next.js.

next comps

As indicated on the official website, React server components are integrated into Next.js by default. This means that if you create a Next.js application, all your components will be server components by default.

However, the main challenge lies in the fact that many people don't fully grasp how to utilize server components, why they're necessary, and what advantages they offer.

Server Components

Let's take a look at what you've prepared. You have a small Next.js application with a Home component.

const Home = async () => {
  try {
    const response = await fetch(
      "https://api.realworld.io/api/articles?limit=10&offset=0",
    ).then((res) => res.json());
    return (
      <div>
        {response.articles.map((article) => (
          <div>{article.title}</div>
        ))}
      </div>
    );
  } catch (err) {}
  return <h1>Home</h1>;
};

export default Home;

It appears to be a typical React component at first glance. However, the key difference lies in how we directly fetch data from an API and render it on the screen. We make an asynchronous call and render the markup without concerning ourselves with displaying a loader, as we would typically do on the client. This component is not a client component; it's a server React component.

In Next.js, any component is automatically treated as a server component by default.

What this means is that this component will exclusively run on the server. As a result, we can directly make API calls without any client-side code. We're limited in that we can't access local storage or session storage, bind events, or even use React hooks. Essentially, 90% of React's capabilities are unavailable to us.

Let's attempt to add a useState hook to our component.

const Home = async () => {
  const [user, setUser] = useState('')
  ...
};

This will lead to an error because we're attempting to use client-side code inside a server component.

client error

It indicates that we're trying to use client-specific functionality within a server component. We're unable to utilize any hooks inside a server component, including useState, useEffect, and React.Context, for example.

Now, your next question might be, "But I need to use these features because I'm developing a React application." To resolve this, we can add use client at the top of the file.

'use client'
const Home = async () => {
  const [user, setUser] = useState('')
  ...
};

This line will transform the entire component into a client-side component.

list

And as you can observe, there are no errors in the browser anymore. We successfully rendered a list of items even with a useState hook inside.

However, there's a misconception here. When people hear the term "server component," they assume it's a component for the server. Conversely, when they hear "client component," they think it's executed only on the client. This is not entirely accurate.

The 'use client' approach effectively reintroduces server-side rendering.

This is precisely what we had in Next.js previously. We had components that could be rendered both on the backend and on the client. If we inspect the source code of our list rendered on the screen, you'll notice that it was also rendered on the server. Therefore, our client component is being rendered both on the client and on the server.

More Confusion

You might be feeling even more confused now. We have server components, client components, and we're rendering client components on the server using use client. How does this all make sense?

When we don't use use client, and a component is a server component without any hooks, it's rendered only once. In this case, our Home component (not the entire tree) is generated on the backend and then rendered in the browser.

The most significant difference between server-side rendering and server components is that we're focusing on granular rendering of individual components, rather than rendering the entire application on the server.

schema

This means that we can choose to render one group of components exclusively on the server and another group of components solely on the client. Moreover, if you need to update your server component later on, you can easily achieve this by sending an update from the server to the client.

Another significant advantage of server components is that they are not included in the bundle. Unlike server-side rendering, where the entire page is rendered on the backend but all the components are client components and included in the bundle, server components allow us to exclude certain parts of our application from the bundle. This helps reduce the size of our application.

Here's one more extremely important point to remember.

Your client component can only render other client components; it cannot render server components.

When a client component exists in the component tree, all its children will also be client components. They cannot be server components. Realistically, refactoring an application to use server components is entirely different from refactoring an application with classes to React hooks.

With React hooks, you could take one component at a time and change it. However, this approach doesn't work with server components. You can't simply refactor individual components; you need to go from top to bottom and select which part of your application will be rendered on the client side and which part will be rendered on the server.

Writing use client in all your components doesn't make sense because you're not utilizing server components at all. This is precisely why server components are still not released in React. Implementing them is simply too difficult. All the bundlers must understand how to work with server components, which involves a significant codebase, and many libraries need to be updated accordingly.

My Thoughts

Here are my closing thoughts on React server components: Personally, I'm not a fan of them. Why is that? Well, I truly believe React is an exceptional framework that excels at creating client applications. However, when it comes to server-side rendering, it's not always necessary.

In 99% of cases, you may not need React at all. A server-side rendered application can often suffice.

You can indeed use languages like Java, Ruby, or PHP on the backend to render your entire application, and only utilize React for the parts that require client-side rendering. This approach has been working effectively for a long time.

When it comes to server-side React components, the idea is to render parts of our application with React on the backend. While it's possible and will likely become more common, I personally don't find it particularly compelling. It doesn't fundamentally change how the web works; we're still rendering some content on the server and some on the client. From my perspective, React may not be the best tool for server-side rendering. Using other languages and technologies may offer more flexibility and efficiency, especially when dealing with database requests.

So, I'm not entirely convinced of the necessity of using React in this context. Perhaps I'm mistaken, and only time will tell.

If you're interested in learning how to test your React applications with unit testing and end-to-end testing, I highly recommend checking out my course React Testing: Unit Testing React and E2E Testing.

📚 Source code of what we've done