paint-brush
Getting Started with React Server Components: A Step-by-Step Tutorialby@fedor
2,445 reads
2,445 reads

Getting Started with React Server Components: A Step-by-Step Tutorial

by Fedor UsakovJune 22nd, 2023
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

React Server Components are a new feature that lets you build apps that span the server and client. They can access data directly from the backend, without sending it to the client or bundling it into the JavaScript code. This reduces the network and bundle size, resulting in faster loading and rendering.
featured image - Getting Started with React Server Components: A Step-by-Step Tutorial
Fedor Usakov HackerNoon profile picture

React Server Components are a new feature that lets you build apps that span the server and client, combining the interactivity of client-side apps with the performance of server rendering. In this tutorial, you’ll learn what React Server Components are, how they work, and how to use them in a Next.js app.

What are React Server Components?

React Server Componentsare React components that run on the server and stream their output to the client. They can access data directly from the backend, without sending it to the client or bundling it into the JavaScript code. This reduces the network and bundle size, resulting in faster loading and rendering.


React Server Components have a special file extension: .server.js. They can only use Node.jsmodules or other server components as dependencies, and they cannot use any browser APIs or client-side features. They can render other server components, client components, or shared components.


Client components are regular React components that run on the browser and have a .client.jsfile extension. They can use any browser APIs or client-side features, and they can render other client components or shared components.


Shared components are React components that can run on both the server and the client and have a .jsfile extension. They can use any features that are available on both environments, such as hooks, context, or custom components. They can render other shared components.


React Server Components can pass data to client components using props. The data passed from server to client is serialized and deserialized using a custom format that preserves references and supports streaming. This means that data can be sent incrementally as it becomes available, and circular or repeated data structures can be handled efficiently.

How do React Server Components work?

React Server Components work by using a special renderer that streams the output of server components to the client as a JSON-like format. The client then parses the stream and renders the corresponding client components using ReactDOM. The server and the client communicate using a protocol that supports streaming, such as HTTP/2.


The rendering process of React Server Components has three phases:

  1. Server rendering: The server renders the server components and sends their output to the client as a stream of data. The output includes information about which client components need to be rendered on the browser, along with their props.

  2. Client rendering: The client receives the stream of data from the server and renders the client components using ReactDOM. The client also keeps track of which server components have been rendered and which ones are still pending.

  3. Reconciliation: The client compares the rendered output of the server components with the expected output based on their props. If there is any mismatch, the client requests a re-render of those server components from the server.


The reconciliation phase ensures that the server and the client are always in sync and that any changes in data or props are reflected correctly on both sides.

How to use React Server Components in Next.js?

Next.js is a popular framework for building React apps that supports both static and dynamic rendering. Next.js also supports React Server Components as an experimental feature since version 13.


To use React Server Components in Next.js, we need to enable them in our next.config.js file:

module.exports = { 
  experimental: {
    appDirectory: true,
  },
};


This will enable the app/ directory feature, which allows us to create an app/ directory in our project root where we can place our server components, client components, and shared components. Next.js will automatically handle the file extensions and the rendering logic for us.


Next, we need to install some dependencies that are required for React Server Components:

npm install react@experimental react-dom@experimental
npm install @react-server/components @react-server/transport-webpack


These packages provide the React Server Components renderer, the streaming protocol, and the webpack plugin for bundling our code.


Next, we need to create an app/pages/_app.server.js file that will be our entry point for our server-side rendering:


import { pipeToNodeWritable } from "react-dom/server";
import { createFromFetch } from "@react-server/components"; 

export default function handleRequest(req, res) {
  const manifest = require("../../.next/server-components-manifest.json"); 
  const url = `${req.protocol}://${req.get("host")}${req.originalUrl}`;

  const stream = createFromFetch( 
    fetch(url, {
      headers: req.headers, 
      method: req.method, 
      body: req,
    }),
    manifest
  );

  pipeToNodeWritable(stream, res, () => {});
}


This file uses thecreateFromFetch function from @react-server/components to create a stream of data from the server components based on the request URL and the manifest file generated by Next.js. It then uses the pipeToNodeWritable function from react-dom/server to send the stream to the response object.


Next, we need to create an app/pages/_app.client.js file that will be our entry point for our client-side rendering:


import { hydrateRoot } from "react-dom";
import { createFromFetch } from "@react-server/components"; 
import App from "./_app";

hydrateRoot(
  document.getElementById("_next"),
  <App
    pageProps={{}} 
    Component={createFromFetch(
      new Response(document.getElementById("_next_data_").textContent, { 
        headers: { "Content-Type": "application/json" },
      })
    )}
  />
);


This file uses the hydrateRoot function from react-dom to render the client components into the existing DOM tree. It also uses the createFromFetch function from @react-server/components to create a stream of data from the server components based on the script tag that contains the initial data sent by the server.


Finally, we need to create anapp/pages/_app.js file that will be our shared component for wrapping our pages:


import React from "react";

export default function App({ Component, pageProps }) { 
  return <Component {...pageProps} />;
}


This file simply renders the component that corresponds to the current page, along with any props that are passed to it.


Now we can create our pages using React Server Components.

For example, we can create an app/pages/index.server.js file that will be our home page:


import React from "react";
import { useFetch } from "@react-server/components";
import Header from "../components/Header"; // shared component 
import Footer from "../components/Footer"; // shared component
import PostList from "../components/PostList.server"; // server component

export default function Home() {
  const posts = useFetch("/api/posts"); // fetch data from the backend

  return (
    <div>
      <Header /> {/* render a shared component */}
      <h1>Welcome to React Server Components</h1>
      <PostList posts={posts} /> {/* render a server component */}
      <Footer /> {/* render a shared component */}
    </div>
  );
}


This file uses the useFetch hook from @react-server/components to fetch data from the backend using a relative URL. It then renders a shared component (<Header />), a server component (<PostList />), and another shared component (<Footer />). The PostList component can render other server components or client components as needed.


We can also create an app/pages/[id].server.js file that will be our dynamic page for displaying a single post:

import React from "react";
import { useFetch } from "@react-server/components";
import Header from "../components/Header"; // shared component 
import Footer from "../components/Footer"; // shared component
import PostDetails from "../components/PostDetails.server"; // server component

export default function Post({ id }) {
  const post = useFetch(`/api/posts/${id}`); // fetch data from the backend

  return (
    <div>
      <Header /> {/* render a shared component */}
      <PostDetails post={post} /> {/* render a server component */}
      <Footer /> {/* render a shared component */}
    </div>
  );
}


This file uses the useFetch hook from @react-server/components to fetch data from the backend using a dynamic URL based on the page parameter. It then renders a shared component (<Header />), a server component (<PostDetails />), and another shared component (<Footer />). The <PostDetails /> component can render other server components or client components as needed.

Conclusion

React Server Components are a new feature that lets you build apps that span the server and client, combining the interactivity of client-side apps with the performance of server rendering. In this tutorial, you learned how to use React Server Components in a Next.js app, how they work, and what are the benefits and limitations of using them.


React Server Components are still an experimental feature and may change in the future. However, they offer a promising way of building modern web apps with React.

Sources