Server Components in React: Exploring the Next Generation of Server-Side Rendering

React started its way in 2013 as a simple UI library, suitable for developing simple client-side web apps. It had evolved since then into a huge ecosystem of frameworks and communities. Many features were added as time passed, and one most notable is server-side rendering (SSR) in the form of NextJS and the like. However, this is about to change as React gets native support for server-side rendering in the form of Server Components.

What is server-side rendering?

The rendering method (Server vs Client) refers to the location where rendering occurs. Rendering is the process that turns code and data into HTML code, that is used by the browser to display the website. In server-side rendering, the server does all the work and sends the prepared HTML code to the browser, which only has to display it. Using client-side rendering, the server sends raw JavaScript code and data, and the browser must execute the JavaScript code to produce the HTML code. In a hybrid approach (used by NextJS and React Server Components), some parts are rendered on the server side, and the rest are rendered on the client side. JavaScript code will then combine the two parts to produce the resulting HTML code.

Why use server-side rendering?

There are two main reasons for using SSR in your project:

  • Performance: the website will display much faster as the browser does not have to run the JS code to render it. Additionally, the rendered HTML code can be cached by the server so it does not have to render it every time.
  • Security: SSR is a great way to hide some sensitive logic and data from the user. In CSR, all API keys and processing logic is stored in the browser, which means users can easily access them. In SSR, this is happening on the server, which is much harder to leverage.

Nevertheless, there are some disadvantages to using server-side rendering:

  • Cost: it is much more expensive to run a server that is equipped to handle server-side rendering than a server that only serves static JavaScript code.
  • Learning curve: it is much easier to write purely CSR code (when considering React) than CSR+SSR.

Difference between NextJS and Server Components

Seasoned React developers at this point may notice that we have had SSR in React for a long time now, in the form of NextJS. If that is the case, what is the difference between the two, and why bother learning Server Components?

While both NextJS and Server Components enable SSR, they have different approaches. NextJS renders the necessary components on the server, turns them into HTML, and sends the HTML to the browser once it is ready. When using Server Components, React will render the components into a JSON tree of nodes, and stream them to the browser concurrently. It means that React will not wait until all rendering is complete, and the data transfer can happen simultaneously with the rendering process. In practice, this results in shorter load times and lower latency.

How to use Server Components

After going through some of the basics of SSR and NextJS, we can move on to writing some server components ourselves. To follow along, you can create a new NextJS project with npx create-next-app@latest, or clone my repo.

You may be wondering why are we using NextJS when writing React Server Components. The reason is that by themselves, Server Components do not support SSR. They merely expose an API so other devs can write their own SSR implementation on top of Server Components. As of writing this article, the only framework that supports Server Components is NextJS

NextJS supports Server Components as of version 13, and you will need to have the /app directory enabled for Server Components to work.

Now we get to the main part: writing Server and Client Components. You will be happy to hear that this is quite simple! By default, any component that you define in the /app directory of a NextJS project will be a Server Component:

# /app/header.jsx

export default function Header() {
    return <header><h1>Welcome to my SSR App!</h1></header>
}

This file will render on the server using Server Components! The JS code is never sent to the browser, and the render result is streamed asynchronously as the server performs rendering.

Server Components have a few downsides in terms of developer experience. If you try to use a lifecycle hook such as useState, you will get this error:

ReactServerComponentsError:

You're importing a component that needs useState. It only works in a Client Component but none of its parents are marked with "use client", so they're Server Components by default.

   ,-[.../server-components-tutorial/app/header.js:1:1]
 1 | import {useState} from "react";
   :         ^^^^^^^^
 2 | 
 3 | export default function Header() {
 4 |     const [counter, setCounter] = useState(0);
   `----

Maybe one of these should be marked as a client entry with "use client":
  ./app/header.js
  ./app/page.js

This makes sense: the server is stateless and cannot keep track of the client’s state. To use state and effects, we need to mark the component with "use client" marker:

"use client";

import {useEffect, useState} from "react";

export default function Header() {
    const [counter, setCounter] = useState(0);
    useEffect(() => {
        const handle = setInterval(() => setCounter(c => c + 1), 250)
        return () => clearInterval(handle)
    }, [])
    return <header><h1>Welcome to my SSR App! Counter - {counter}</h1></header>
}

With this marker in place, the Header component becomes a Client Component. It will render fully on the client and the server will not touch this code.

After looking at some of the basics of React Server Components, let’s dive into some more advanced details.

Data fetching with Server Components

One obvious use case for Server Components is to perform data fetching on the server. This allows you to protect your API keys and make requests closer to the data source. With React Server Components, it is very easy. You can make the server component function async and perform fetch calls inside:

# cat-fact.js
export default async function CatFact() {
    const response = await fetch('https://catfact.ninja/fact');
    const {fact} = await response.json();

    return <p>{fact}</p>
}

This looks like a regular React component, but because it is a Server Component, we can make it async and perform data fetching inside. The server will fetch a random cat fact from the awesome Cat Facts API and send the HTML code to the browser.

Importing client and server components

You can freely import your Client Components into Server Components. React will server-side render what it can, and then hydrate that HTML code on the client with client components. However, you cannot import Server Components into Client Components! If you must do this, consider passing Server Components as a prop (children, for example).

Keeping server-side code protected

If you are separating your component code into server-side and client-side, it might be a good idea to do that for all code, like helper functions or API clients. Suppose that you have a sensitive function defined somewhere in your code:

# utils.js
export function getSecretApiKey() {...}

You don’t want this function to ever be accessible to the client. However, someone else on your team might accidentally import it into a Client Component and expose it to the browser. To better protect against these kinds of bugs and vulnerabilities, install the server-only package:

$ npm i -D server-only

To use it, import this package in all files that are supposed to be guarded from the client:

import 'server-only';

export function getSecretApiKey () {...}

Now you will get a build error if you try to import this function in a Client Component.

Conclusion

In this article, I tried to give an overview of React Server Components, how to use them with NextJS, and how they work. I am super excited about the widespread adoption of these, and it can really reinvent the way we write React apps. Please let me know in the comments what you think about React Server Components! Are you going to use them in your NextJS projects?

Get new content delivered to your mailbox:

leave a comment