Server and Client components

Server Components

Server Component is a new feature that has been introduced in React 18, and has been adopted by Next.js 13, Server components allow you to render components on the server and send the result to the client without shipping any javascript.

Key Benefits of React Server Components (RSC)

  1. Reduced JavaScript bundle size: RSCs can help reduce the size of your JavaScript bundle by rendering parts of your application on the server. This can lead to faster load times and improved performance on slower devices and networks.
  2. Improved initial load time: Since RSCs don't require JavaScript to render, the browser can display their content as soon as it receives the HTML from the server. This can lead to faster initial load times, particularly for content-heavy applications.
  3. Simplified data fetching: RSCs can handle data fetching on the server, which can simplify your application's data management and reduce the need for client-side data fetching libraries.
  4. Automatic code splitting: React Server Components enable automatic code splitting, meaning that the code for each RSC is only loaded when it's needed. This can further reduce the size of your JavaScript bundle and improve application performance.

By default, every component you declare is considered a server component in Next.js. This means that if you try to add client-side events, such as an onClick event, to a server component, an error will occur. To resolve this issue, you need to explicitly designate the component as a client component.

So let’s take this counter component for example:

import { useState } from 'react';
 
export default function Counter() {
  const [count, setCount] = useState(0);
 
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  );
}

We will get this error:

Error

Client Components

From the Next.js docs: (opens in a new tab)

Client Components enable you to add client-side interactivity to your application. In Next.js, they are pre-rendered (opens in a new tab) on the server and hydrated (opens in a new tab) on the client. You can think of Client Components as how components in the Pages Router (opens in a new tab) have always worked.

To create a client component in Next.js, you need to create a new file and include the "use client" directive at the top of the file. This directive allows the component to behave similarly to components within the pages router. By following this approach, you can utilize client-side events and functionality within the component.

So we need to make the previous counter component a client component with the "use client" directive :

'use client';
 
import { useState } from 'react';
 
export default function Counter() {
  const [count, setCount] = useState(0);
 
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  );
}

When to use Server and Client Components?

To simplify the decision between Server and Client Components, I recommend using Server Components (default in the app directory) until you have a use case for a Client Component.

This table summarizes the different use cases for Server and Client Components:

WhenToUse


Combining Client and Server Components

I recommend reading through the patterns outlined in the official Next.js documentation (opens in a new tab) for a comprehensive understanding. However, I'll highlight the key points for you:

Moving Client Components to the Leaves

To improve the performance of your application, we recommend moving Client Components to the leaves of your component tree where possible.

For example, you may have a Layout that has static elements (e.g. logo, links, etc) and an interactive search bar that uses state.

Instead of making the whole layout a Client Component, move the interactive logic to a Client Component (e.g. <SearchBar />) and keep your layout as a Server Component. This means you don't have to send all the component Javascript of the layout to the client.

// SearchBar is a Client Component
import SearchBar from './searchbar';
// Logo is a Server Component
import Logo from './logo';
 
// Layout is a Server Component by default
export default function Layout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <>
      <nav>
        <Logo />
        <SearchBar />
      </nav>
      <main>{children}</main>
    </>
  );
}

Composing Client and Server Components

Server and Client Components can be combined in the same component tree.

Behind the scenes, React handles rendering as follows:

  • On the server, React renders all Server Components before sending the result to the client.
    • This includes Server Components nested inside Client Components.
    • Client Components encountered during this stage are skipped.
  • On the client, React renders Client Components and slots in the rendered result of Server Components, merging the work done on the server and client.
    • If any Server Components are nested inside a Client Component, their rendered content will be placed correctly within the Client Component.

Passing Server Components to Client Components as Props

Instead, when designing Client Components you can use React props to mark "holes" for Server Components.

The Server Component will be rendered on the server, and when the Client Component is rendered on the client, the "hole" will be filled in with the rendered result of the Server Component.

A common pattern is to use the React children prop to create the "hole". We can refactor <ExampleClientComponent> to accept a generic children prop and move the import and explicit nesting of <ExampleClientComponent> up to a parent component.

app/example-client-component.tsx

'use client';
 
import { useState } from 'react';
 
export default function ExampleClientComponent({
  children,
}: {
  children: React.ReactNode;
}) {
  const [count, setCount] = useState(0);
 
  return (
    <>
      <button onClick={() => setCount(count + 1)}>{count}</button>
 
      {children}
    </>
  );
}

Now, <ExampleClientComponent> has no knowledge of what children is. Infact, from its perspective it doesn't even know that children will eventually be filled in by the result of a Server Component.

The only responsibility ExampleClientComponent has is to decide where whatever children will eventually be should be placed.

In a parent Server Component, you can import both the <ExampleClientComponent> and <ExampleServerComponent> and pass <ExampleServerComponent> as a child of <ExampleClientComponent>:

app/page.tsx

// This pattern works:
// You can pass a Server Component as a child or prop of a
// Client Component.
import ExampleClientComponent from './example-client-component';
import ExampleServerComponent from './example-server-component';
 
// Pages in Next.js are Server Components by default
export default function Page() {
  return (
    <ExampleClientComponent>
      <ExampleServerComponent />
    </ExampleClientComponent>
  );
}

With this approach, the rendering of <ExampleClientComponent> and <ExampleServerComponent> are decoupled and can be rendered independently - aligning with Server Components, which are rendered on the server before Client Components.

Good to know

From the Next.js docs:

  • This pattern is already applied in layouts and pages (opens in a new tab) with the children prop so you don't have to create an additional wrapper component.
  • Passing React components (JSX) to other components is not a new concept and has always been part of the React composition model.
  • This composition strategy works across Server and Client Components because the component that receives the prop has no knowledge of what the prop is. It is only responsible for where the thing that it is passed should be placed.
    • This allows the passed prop to be rendered independently, in this case, on the server, well before the Client Component is rendered on the client.
    • The very same strategy of "lifting content up" has been used to avoid state changes in a parent component re-rendering an imported nested child component.
  • You're not limited to the children prop. You can use any prop to pass JSX.