DEV Community

Byteminds Agency for ByteMinds

Posted on • Originally published at byteminds.co.uk

Server and client components in Next.js: when, how and why?

Next.js offers powerful capabilities for creating high-performance web applications. An important part of its functionality, with the advent of the Next App Router, is the server and client components, which allow developers to control server-side and client-side rendering, depending on their project’s requirements. Let's look at these components in more detail.

All the text and examples in this article refer to Next.js 13.4 and newer versions, in which React Server Components have gained stable status and became the recommended approach for developing applications using Next.js.

What is a Server Component (RSC) and how is it rendered?

> React Server Components are rendered exclusively on the server. Their code is not included in the JavaScript bundle file, so they are never hydrated or re-rendered on the client.

By default, all components are server-side components. This allows you to automatically implement server-side rendering without additional configuration, and you can later convert a server component into a client-side component if necessary.

RSC renders in two stages on the server:

  1. React renders server-side components into a special data format called RSC Payload.
  2. Next.js uses the RSC payload and JavaScript instructions for client components to render HTML on the server.

Then, on the client:

  1. HTML is used to instantly show a quick, interactive preview - this is only for the initial loading of the page.
  2. The RSC payload is used to reconcile the client and server component trees and update the DOM accordingly.
  3. JavaScript instructions are used to hydrate client components and provide interactivity to the application.

What is the RSC payload?

> The RSC payload is a compact binary representation of a rendered tree of React server components. The RSC payload is used on the client to update the browser DOM and contains:

  1. The rendered result of server components.
  2. Placeholders for where the rendered client components should appear, and links to their JavaScript chunk files.
  3. Any props passed from the server component to the client component.

Advantages of RSC

  1. Improves application performance because heavy dependencies that could be used to render the component on the server (Markdown, code highlighter, etc.) are not sent to the client.

  2. Enhances web vitals application metrics (TTI, etc.)

  3. HTML streaming when using RSC allows you to break the rendering work into fragments and transfer them to the client when ready. This allows the user to see parts of the page earlier, without waiting for the entire page to be fully rendered on the server.

Disadvantages of RSC

  1. The RSC payload increases HTML file size

  2. Secrets intended only for the server (tokens, keys, etc.) can leak to the client. Potential security issues for next.js applications are described in detail in this article.

  3. Increases the mental load when choosing the appropriate component type during application development, likely requiring time to train the team.

What is a client component and how is it rendered?

Client-side components allow you to create an interactive user interface that is pre-rendered on the server and can use client-side JavaScript to execute in the browser.

To optimize the initial page load, Next.js uses the API React to render static HTML previews on the server for both client and server components. This ensures that when a user first visits your application, they immediately see the content of the page without waiting for the JavaScript client component bundle to load, parse, and execute.

Despite their name, "client components" are initially rendered on the server, but are then executed on both the server and the client.

Image description

We can easily convert a server component into a client component by adding a “use client” directive to the beginning of the file or renaming it to “counter.client.js”:

'use client';

export default function Counter() {
  return <div>Counter - client component</div>;
}
Enter fullscreen mode Exit fullscreen mode

When to use a server component and when to use a client component?

The choice between server and client components depends on the specific requirements of your task. Server-side components are ideal for scenarios that require accessing data on the server during rendering or retrieving data that should not be available on the client.
Client components, on the other hand, are effective for creating interactive elements that use React hooks and browser APIs.

To understand which type of component is suitable in a particular case, you can use the helpful table located on the next.js documentation website.

Image description

In RSC, we cannot use React hooks, Context or browser APIs. We can only use server-side component APIs such as headers, cookies, etc.

> Important: Server components can import client components.

When we use client components, we can use React hooks, Context, and APIs that are only available in the browser. However, we cannot use APIs that are only available in server components, such as headers, cookies, etc.

> Important: Client components cannot import server components, but you can pass a server component as a child element or property of a client component.

With the advent of React Server Components, it has become a recommended best practice to move client components to the end nodes of your component tree whenever possible. However, sometimes you need to conditionally render server-side components using client-side interactivity.

Let's say we have a client component like this:

'use client'

import { useState } from 'react'

export default function ClientComponent({
  children,
}: {
  children: React.ReactNode
}) {
  const [show, setShow] = useState(false)

  return (
    <>
      <button onClick={() => setShow(!show)}>Show</button>
      {show && children}
    </>
  )
}
Enter fullscreen mode Exit fullscreen mode

The ClientComponent doesn't know that its children will eventually be filled with the server component's render result. The ClientComponent's only responsibility is to decide where the child elements will ultimately be placed.

// This pattern works:
// You can pass a Server Component as a child or prop of a
// Client Component.
import ClientComponent from './client-component'
import ServerComponent from './server-component'

// Pages in Next.js are Server Components by default
export default function Page() {
  return (
    <ClientComponent>
      <ServerComponent />
    </ClientComponent>
  )
}
Enter fullscreen mode Exit fullscreen mode

With this approach, and are separated from each other and can be rendered independently. In this case, the child component can be rendered on the server before the is rendered on the client.

All possible patterns of sharing server and client components are described in detail in the documentation.

FAQ

Why use Next.js React Server Components (RSC)?

React Server Components (RSC) provide a new way to build applications that allows developers to split code between the client and server. This becomes especially useful for large-scale projects with significant amounts of data or dynamic content.

How are RSC and Next.js related? Can I use RSC without Next.js?

RSC is tightly integrated with Next.js and provides additional features to optimize page load. While you can theoretically create RSC without using Next.js, it will be much more difficult and less efficient. Next.js provides an intuitiveRSC framework, automatic preloading, and many other features that make the development process much easier.

How does this relate to Suspense?

Server Components data retrieval APIs are integrated with Suspense. RSC uses Suspense to provide loading states and to unblock parts of a stream so that the client can show content before the entire response has completed.

What are the performance advantages of using RSC?

Server Components allow you to move most of the data retrieval to the server so that the client doesn't have to make as many requests. This also eliminates the typical useEffect network waterfalls on the client for retrieving data.

Server Components also allow you to add non-interactive functionality to your application without increasing the JS bundle size. Moving functions from the client to the server reduces the initial code size and parsing time of client JS. Also, reducing the number of client components improves client processor time. The client can skip server-generated parts of the tree during reconciliation because it knows that they could not be affected by state updates.

Do I have to use RSC?

If you already have a client React application, you can think of it as a tree of client components. If that suits you, great! Server-side components extend React to support other scenarios and are not a replacement for client-side components.

Is this a replacement for SSR?

No, they complement each other. SSR is primarily a technique for quickly rendering a non-interactive version of client components. You will still have to pay the cost of downloading, parsing, and executing these client components once the HTML is loaded.

You can combine server-side components and SSR, where server-side components are rendered first, and client-side components are rendered in HTML for a fast, non-interactive rendering during hydration. When they are combined this way, you still get a fast launch time, but you also significantly reduce the amount of JS loaded on the client.

Can I gradually migrate to RSC by rewriting the project's codebase?

Yes, with the release of the new app router and RSC, the previous approach still works, and you can gradually switch to the RSC approach. It should be noted that RSC components only work in the app router. There is a detailed guide on how to transition to the new app router.

Author: Sergei Pestov

Top comments (0)