What are React Server Components and will you need to use them in the future?

michielmulders

Michiel Mulders

Posted on February 3, 2021

What are React Server Components and will you need to use them in the future?

Around Christmas, the React team introduced React Server Components. It’s a complete surprise for the React ecosystem as React has always focused on the client-side with a small focus on server-side rendering. Server-side rendering can, for instance, be accomplished using Next.js.

However, with React Server Components (RSC), it’s now possible to write server-rendered code using React only. Note that RSC is not a final feature as of now. Currently, React Server Components are undergoing beta-testing.

In this article, you’ll learn the following concepts about React Server Components:

  1. What is a React Server Component?
  2. Why do we need React Server Components? What problems do they solve?
  3. What does a React Server Component look like, and what are the benefits?
  4. What’s the difference between React Server Components and Server Side Rendering (SSR)?
  5. Is there any future for React Server Components?

What is a React Server Component?

Let’s make this clear first: React always renders React Server Components on the server. It allows frontend developers to create components that span both server and client.

Why is this useful? This property allows developers to quickly fetch data from the backend. Therefore, you don't have to send an expensive request from your client to your backend to fetch data. As you render your components on the server that also hosts your backend, you can make a local call to your API to fetch data. In other words, locally fetched data is super fast (cheap).

To summarize, React Server Components allow you to access the speed of fetching data on the server-side while maintaining its rich interactivity of client-side apps. That’s a fantastic improvement for the React ecosystem to build faster applications.

Why do we need React Server Components?

UI development needs to offer the following properties:

  1. It should offer a good user experience
  2. It should be cheap to maintain
  3. It should offer fast performance

However, these properties are at odds with each other. To illustrate this problem with the current state of React, let’s imagine a Spotify artist page that has the following components.

function ArtistPage({ artistID }) {

  return (
    <ArtistDetails artistId={artistId}>
      <TopTracks artistId={artistId} />
      <Discography artistId={artistId} />
    </ArtistDetails>
  );
}
Enter fullscreen mode Exit fullscreen mode

Now, let’s fetch all data at the top of the component and pass each component’s required data. This approach’s benefit is that it’s performant as we only send one API call to retrieve all data we need.

We end up with the below code.

function ArtistPage({ artistID }) {
  const artistData = fetchAllArtistData();

  return (
    <ArtistDetails 
      details={artistData.details}
      artistId={artistId}>
      <TopTracks 
        topTracks={artistData.topTracks}
        artistId={artistId} />
      <Discography 
        discography={artistData.discography}
        artistId={artistId} />
    </ArtistDetails>
  );
}
Enter fullscreen mode Exit fullscreen mode

The API response becomes very coupled to the component. We expect the API call to return artist details, top tracks, and discography. If we want to change the component’s behavior in the future, we might need to change the behavior of the API endpoint as well.

Further, if we remove a component inside the <ArtistDetail/> component, we might forget to remove this data from the API call. Therefore, this approach affects code maintenance. But don't worry, there’s nothing fundamentally wrong with this approach. It’s how React works.

What if we allow each component to fetch its data to decouple the API response from the component? Our code might look like this.

function ArtistDetails({ artistId, children }) {
  const artistData = fetchDetails(artistId)'
  // ...
}

function TopTracks({ artistId }) {
  const topTracks = fetchTopTracks(artistId)'
  // ...
}

function Discography({ artistId }) {
  const discography = fetchDiscography(artistId)'
  // ...
}
Enter fullscreen mode Exit fullscreen mode

While this code is much easier to maintain, it creates performance issues. If you remember our initial component, we’ll get a cascade of API calls that wait for each other to finish.

In other words, the <ArtistDetails/> component fetches data, then <TopTracks/> fetches its data, and finally, the <Discography/> component starts fetching the required data. They also call this a network waterfall.

Our previous example used a single API call to fetch all data to increase performance.

function ArtistPage({ artistID }) {

  return (
    <ArtistDetails artistId={artistId}>
      <TopTracks artistId={artistId} />
      <Discography artistId={artistId} />
    </ArtistDetails>
  );
}
Enter fullscreen mode Exit fullscreen mode

So, can we come up with a solution that addresses all three properties in the React ecosystem: good UX, cheap maintenance, and fast performance? The potential answer here is React Server Components (RSC) by moving our components to the server.

What does a React Server Component look like?

It’s time to explore a React Server Component. This component is an example out of the React demo for React Server Components. We have a list of notes that we can expand when we click it.

You can distinguish a server-side component from a client-side component by appending the file name with .server.js. The file below is named NoteList.server.js. You can apply the opposite for a client-side file: myFile.client.js.

// NoteList.server.js
import {fetch} from 'react-fetch';

import SidebarNote from './SidebarNote';

export default function NoteList({searchText}) {
  const notes = fetch('http://localhost:4000/notes').json();

  return notes.length > 0 ? (
    <ul className="notes-list">
      {notes.map((note) => (
        <li key={note.id}>
          <SidebarNote note={note} />
        </li>
      ))}
    </ul>
  ) : (
    <div className="notes-empty">
      {searchText
        ? `Couldn't find any notes titled "${searchText}".`
        : 'No notes created yet!'}{' '}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

First of all, we can immediately fetch notes data from the server using http://localhost:4000/notes. This ticks off the fast performance property. On top of that, we can use interactive logic on our server, such as return notes.length > 0 ? <code> : <code>.

Next, let’s look at an example where we have an import that we only use once to format dates data-fns. Often, front-end developers have to evaluate if they want to include a dependency that they only use once or twice in the application. It will increase the final bundle size. Developers often ask the question: is it worth it?

import {format, isToday} from 'date-fns';

export default function SidebarNote({note}) {
  const updatedAt = new Date(note.updated_at);
  const lastUpdatedAt = isToday(updatedAt)
    ? format(updatedAt, 'h:mm bb')
    : format(updatedAt, 'M/d/yy');

  return (
    <div className="sidebar-note-list-item">
      <header className="sidebar-note-header">
        <strong>{note.title}</strong>
        <small>{lastUpdatedAt}</small>
      </header>
      <button classname="sidebar-note-open" />
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

With React Server Components, we don’t have to worry about this evaluation. React won’t include a dependency that you use only in a React Server Component in the bundle size. Therefore, the user won’t have to download this dependency. The RSC will use this dependency on the server to render the required output and send this output to the client. Cool, right?

What’s the difference between React Server Components and Server Side Rendering (SSR)?

Server-side rendering focuses on reducing the initial page load. When using SSR, you send HTML to the client. The client then loads all of the React JavaScript you need to make your web page interactive. In other words, you have a traditional React app after the initial page load when React injects all JavaScript.

The most significant difference is that your components are still client-side components. All required dependencies are still downloaded. The only difference when using SSR is that your initial page load contains only HTML to improve performance.

Is there any future for React Server Components?

There’s a massive potential for React Server Components as they address many tradeoffs React developers experience. Some of the most important benefits include:

  • Fast data fetching locally on a server
  • Server-side dynamic logic
  • Ability to include any dependency for RSCs without having to worry about increasing the cost to load a web page

On the other hand, many React users might get confused at first by this domain switch. React has shown a strong focus on the client-side in the past. React Server Components suddenly span both server and client. This change requires a significant shift in the way we think about React components.

For that reason, I expect React developers to embrace this approach but also to see many people struggle to grasp this concept and make correct decisions about server/client-side components.

If you want to play around with the demo code for React Server Components, you can find this code on GitHub.

💖 💪 🙅 🚩
michielmulders
Michiel Mulders

Posted on February 3, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related