DEV Community

Cover image for React Server Components in Next.js 12
Matt Angelosanto for LogRocket

Posted on • Originally published at blog.logrocket.com

React Server Components in Next.js 12

Written by Chinwike Maduabuchi ✏️

There have been continuous efforts to deliver a unanimous rendering solution for React apps. By December 2020, the React team had introduced the idea of React Server Components — abbreviated as “RSC” in this post — a feature for their upcoming version, React 18.

RSC is an experimental, opt-in feature that aims to cut out the work we usually give to the client by rendering everything, including the components, on the server. This approach produces zero bundle-sized components, consequently improving the load time of your web pages.

Vercel, the company behind Next.js, has been collaborating with the React team to roll out their latest version, Next.js 12, which integrates React 18 — RSC included — directly into the framework. View the full list of Next.js 12 features.

Prerequisites

For this tutorial, you'll need:

  • The latest version of Node.js
  • Basic knowledge of Next.js
  • React 18 installed in your project

N.B., React 18 is still in alpha and it is not advisable to use in production just yet. Also, RSC won't be shipped with the first version of React 18 and is only available in certain frameworks — such as Next.js and Hydrogen – under an experimental flag. This article should only serve as a guide as we look forward to a stable release.

Before we dive into RSC, let’s look at the previous prerendering solutions Next.js provided

Taking a look back

Next.js has introduced several clever techniques for prerendering content, which include:

Server-side rendering (SSR)

In server-side rendering, your application's data is fetched on the server, and HTML pages are generated for each route and sent to the user. When received, your user’s browser runs the client JavaScript code to make the generated HTML interactive — this is known as hydration.

This way, your user has content to see when they first land on your page, as opposed to serving a blank, white screen (cringes internally – Skeletons, at least?) while external data is being fetched — which is the case in single-page React apps.

Static site generation (SSG)

Traditional JavaScript-based websites as we know them are actually static sites. Here, the process of compiling and rendering a website is done at runtime, in the browser. Next.js improves upon this by compiling and rendering the website at build time.

The output is a bunch of static files, the HTML file, and assets like JavaScript and CSS. Similar to SSR, this method prerenders the website’s content for your user without depending on their internet speed to display something on screen.

Next.js also has other prerendering methods like

Despite their success, SSR and SSG both have their setbacks. SSR websites are expensive to host, and SSG increases build time drastically as your application gets larger. Read extensively before choosing.

This is where RSC come to help. Let's get into the good stuff!

What do React Server Components really do?

To put it simply, React Server Components are components stored — and rendered — on the server. For this reason, they have two main advantages:

  • Direct access to the backend/database, which makes fetching data faster
  • They contribute nothing to the overall bundle size of your application. With this, you could even get away with importing large libraries for whatever function you may need them for:


npm install largest-package


Enter fullscreen mode Exit fullscreen mode

However, because RSC aren't connected to the browser, they don't possess client-side interactivity and therefore can't manage state. This means hooks like useState, useEffect, and some other Next.js APIs are not supported.

When coupled with client components and Suspense, React Server Components — created with a .server.js extension — can prerender content through HTTP streaming.

HTTP streaming is a push-style data transfer technique that allows a web server to continuously send data to a client over a single HTTP connection that remains open indefinitely. It is an efficient method of transferring dynamic data between server and client.

Take this example:



// my-rsc.server.js
import {db} from 'database'

// access backend 
const posts = db.posts.get(id);

return (
  <Page>
  <Suspense fallback={<Spinner/>}> 
      <BlogPosts posts={posts} />
    <Suspense/>
    <Footer />
  <Page/>
)


Enter fullscreen mode Exit fullscreen mode

When this route is hit, the component renders everything on the page whilst displaying a fallback Spinner component for BlogPosts as it reaches out to the backend for data. The returned data is then streamed into BlogPosts, a client component.

Essentially, Suspense stops child components — that need extra computing — from blocking the entire application, thereby letting us beat the standard React waterfall architecture.

Using React Server Components in Next.js 12

Next.js 12 can be installed by running the following command in your terminal:



npx create-next-app nextjs12-project


Enter fullscreen mode Exit fullscreen mode

This creates a new Next.js app with its latest version.

Next, install the beta version of React 18 with the following command:



npm install next@latest react@beta react-dom@beta


Enter fullscreen mode Exit fullscreen mode

This updates the React version to 18.

After the download is completed, proceed to edit the next.config.js file located in the root of your folder and add the following code:



// next.config.js
module.exports = {
  experimental: {
    concurrentFeatures: true,
    serverComponents: true,
  },
}


Enter fullscreen mode Exit fullscreen mode

This configuration enables both React Server Components and Concurrent Mode.

Concurrent Mode enables React apps to stay responsive and adjust to the user’s device capabilities and network speed. This feature is where Suspense comes from.

Finally, create a pages/_document.js file with the following content:



// _document.js
import { Html, Head, Main, NextScript } from 'next/document'

export default function Document() {
  return (
    <Html>
      <Head />
      <body>
        <Main />
        <NextScript />
      </body>
    </Html>
  )
}


Enter fullscreen mode Exit fullscreen mode

Now you're all set to use RSC in your application!

For this tutorial, rather than creating a project from scratch, I’ll be using the Hacker News project created by Vercel to explain how RSC are used in Next.js 12. Repository here.

Open up your terminal, and clone the repository into your preferred directory:



Chinwike@Chinwike ~/Desktop/codebase/code
λ git clone https://github.com/vercel/next-rsc-demo.git


Enter fullscreen mode Exit fullscreen mode

Open this project up in your text editor and let's examine the files. We’ll start at pages/index.js:



// index.js
export default function Page() {
  return (
    <div className="container">
      {/* code ... */}
      <h1>React Server Components in Next.js</h1>
      {/* code ... */}
      <h2>React Server Components with Streaming</h2>
      <section>
        <a href="/rsc" target="_blank">
          RSC + HTTP Streaming
        </a>
      </section>
      {/* code ... */}
    </div>
  );
}


Enter fullscreen mode Exit fullscreen mode

This index page contains links to other routes, each one displaying the same Hacker News app with different rendering methods.

Our focus will be on the RSC method in pages/rsc.server.js:



// rsc.server.js
import { Suspense } from 'react'

// Shared Components
import Spinner from '../components/spinner'

// Server Components
import SystemInfo from '../components/server-info.server'

// Client Components
import Page from '../components/page.client'
import Story from '../components/story.client'
import Footer from '../components/footer.client'

// Utils
import fetchData from '../lib/fetch-data'
import { transform } from '../lib/get-item'
import useData from '../lib/use-data'

function StoryWithData({ id }) {
  const data = useData(`s-${id}`, () => fetchData(`item/${id}`).then(transform))
  return <Story {...data} />
}

function NewsWithData() {
  const storyIds = useData('top', () => fetchData('topstories'))
  return (
    <>
      {storyIds.slice(0, 30).map((id) => {
        return (
          <Suspense fallback={<Spinner />} key={id}>
            <StoryWithData id={id} />
          </Suspense>
        )
      })}
    </>
  )
}

export default function News() {
  return (
    <Page>
      <Suspense fallback={<Spinner />}>
        <NewsWithData />
      </Suspense>
      <Footer />
      <SystemInfo />
    </Page>
  )
}


Enter fullscreen mode Exit fullscreen mode

This component features the blog page where the NewsWithData component — the component responsible for fetching posts — is wrapped in Suspense:



<Suspense fallback={<Spinner />}>
  <NewsWithData />
</Suspense>


Enter fullscreen mode Exit fullscreen mode

Here, NewsWithData makes use of two functions, fetchData and useData, to fetch the storyIds of all the posts from the API. It then maps out a StoryWithData component for each post.

StoryWithData then uses the functions to fetch the content of each individual post, and stream it to the Story client component:



// rsc.server.js 
function StoryWithData({ id }) {
  const data = useData(`s-${id}`, () => fetchData(`item/${id}`).then(transform))
 // Story is client component imported in a server component
  return <Story {...data} />
}


Enter fullscreen mode Exit fullscreen mode

Story.client.js displays posts in the UI and adds client side interactivity to the application by implementing upvotes for each post.




// story.client.js
// client components are regularreact components you're already familiar with

// client component can use state
import { useState } from 'react'

export default function Story({
  id,
  title,
  date,
  url,
  user,
  score,
  commentsCount,
}) {
  const { host } = url ? new URL(url) : { host: '#' }
  const [voted, setVoted] = useState(false)

  return (
    <div style={{ margin: '5px 0' }}>
      {/* code... */}
    </div>
  )
}


Enter fullscreen mode Exit fullscreen mode

A live effect of RSC can be observed on the project when compared with the client-side rendering method in pages/csr.js.

On a throttled network, the page using CSR is evidently slower because the browser combines data fetching with the hydration of the components. Watch the demo here.

Conclusion

React Server Components are an amazing feature because they effectively handle rendering and let us build apps that span the server and client. Components can now load faster because most of the computing is shifted to the Server Components and away from the client. RSC’s official release is set to bring a change to the architecture of future React applications.

References

As this is still a breaking topic, refer to the materials below for further information:


Full visibility into production React apps

Debugging React applications can be difficult, especially when users experience issues that are hard to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket Dashboard Free Trial Banner

LogRocket is like a DVR for web apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Top comments (0)