BLACK FRIDAY: 50% OFF MENTORING (CODE: BLACKFRIDAY50)

React Server vs Client components in Next.js 13

React Server vs Client components in Next.js 13

With Next.js, you can try out React Server and client components. All components are server components by default in the app directory. In this post, we will cover React Server and Client Components in-depth.

First, let's start with the basics and slowly learn each concept.

What is Serialization?

Serialization is an important topic to know if you are going to use Client and Server components. You'll learn why later in the post.

It's a process of converting an object into a stream of bytes so that object can be stored in your file system, DB, or memory.

To store anything on your file system, DB, or memory, you need to keep them as a stream of bytes. Otherwise, it won't work, as your computer won't be able to understand what's going on.

Let’s dive in,

What are React Server components?

React Server components are components that are fetched and rendered on the server. They don't exist if you look for them in the client-side bundle. For example,

The example above is a simple blog with 2 blog posts. The pink component is called a Tag component which is used to denote if a component is client or server-side just for demo purposes. Others are Page, Card List, Card and Date components.

If you need a refresher on what Routing in Next.js 13 is, then take a look at this thread I posted.

Characteristics of React Server components:

  • They don't include interactivity such as onClick handlers etc.
  • Fallbacks and functions can't be passed down as props
  • They are not interactive and don't need to read React state
  • They can't use React life cycle hooks
  • It will make sense if you start thinking of React components like the backend.
  • Examples of React Server components - Database or file system-based operations, any component that doesn't have interactivity or lifecycle hooks are a good fit for these.

By default, components are Server side components in Next.js 13.

Purpose of Server Components

One of the most significant advantages of Server components is that they aren't included in the client-side bundle. This allows us to ship tiny client-side bundles, so there is less JavaScript to download for the user. This helps when using your phone data plan or in a city where the internet is unreliable.

You can see that in the webpack bundle. Only the components that are marked as use client or components that are used inside a client component, such as Date are included in the webpack bundle. In this case, Page, Card List are server components whereas Card, Date are client-side components.

A typical application has a server and a client, which is the browser. The browser fetches and renders JavaScript on the page, and you ask the server for your API data. This works, except that the client is asking for API data. Once data is received, then render it on the page. They are also known as Hydration.

But why not save the round trip? What if the server was capable of fetching and rendering and all the browser needed to do is to hydrate the page? This is precisely why React Server components were born.

Client side components

Client-side components are components fetched and rendered on the client-side i.e. your browser. They are your typical React components that get included in the Browser's JavaScript bundle.

Characteristics of React Client components:

  • They include interactivity such as onClick handlers etc.
  • They are rendered on the browser, which is the client.
  • They use ‘use client’ directive to denote they are client-side components
  • If you plan to use React lifecycle hooks such as useState, useEffect, then you need to use client-side components

Difference between React Server Components vs React Client Components

React Server Components are fetched and rendered on the server whereas React client components are fetched and rendered on the client. If your component includes interactivity, such as any of the lifecycle hooks? Make it a client-side component, whereas if it doesn't, then make it a Server component.

What happens to the million npm packages that use useState, and useEffect? Will they stop working since Server components are by default?

They won’t, as it matters where you declare the components. For example, if you have a component called Calendar and as long as you declare that component inside a client-side component, you should be good. For example,

export default function Calendar() {
  return <div><Date/></div>
}

'use client'; // ←- notice the use client directive here, this make this a client component

export default function CalendarView() {
  return (
    <div>
      <Calendar /> ← Declaring the Calendar component here
    </div>
  );
}

If you remove the use client directive, this will be a server component and you will see an error.

Understanding of leaves and root nodes

Keep your client components in the leave nodes as much as possible. Because if they can be rendered on the server, they should be. Since you get a lot of advantages such as reduced javascript bundle (since they won't be included in the client side bundle) which allows users to download less javascript.

React Server components vs Server-side Rendering

React Server components are not the same as Server side Rendering but are complementary. It can be confusing since they both have the term 'server' in their name. Let's look at what Server-side Rendering is first before we start comparing,

Server-side Rendering (SSR) in Next.js

Websites depend on HTML, CSS, and JavaScript, and we fetch the data from the server. The first load of a web app is essential, so SSR prebuilds the page's HTML content, which is excellent for SEO plus, the user has something to view while JavaScript gets downloaded. The output from the server is HTML.

SSR allows you to speed up that initial boot time when the user loads the page by displaying an HTML version of the client components; however, you still need to download, parse and execute those components once HTML is loaded.

Now that we understand SSR, let's review the differences:

Difference between React Server Components vs Server side Rendering

Rather than returning HTML, the Server components return a description of the rendered UI. This allows React to intelligently merge that data with existing client components without losing state.

You can see that intermediate state below:

The browser stream is no longer JSON, but it looks like the above image, which is similar to JSON but isn't JSON. This is because react Server components use a different stream type to render components on the page.

React Server Components allow you to choose whether to render components on the client or server-side on a per-component basis, whereas, with Server-side Rendering, you get to do this on a per-page basis.

How to pass props from Server to Client?

When we pass props from Server to Client components, they need to be serializable. This means we can’t pass a function like () ⇒ {} as a prop, for example. Instead, we can only pass props that can be serialized to JSON.

It would be a hassle to serialize every prop all the time, each time, right? Well, you can pass elements to it so <p>hi</p> is valid as this can be serialized to JSON.

Well, if you want to make it explicit that your code should only run on the server and not the client, then we can install a new package called ‘server-only’. Keep in mind that components by default are server only but if you want to make it explicit and don’t want to import accidentally, then you can use this package.

Best practices

  • Fetch data in server components. For example, whenever you think of using ‘fetch’ make sure it’s called in Server components, without the ‘use client’ directive
  • This helps with performance and is a big reason why Server components originated

Using Context in React Server Components

How to share Global state across Components?

Typically you might have stored a global state such as Context in the _app.js component, but you won’t be able to as _app doesn’t exist in Next.js 13, and the replacement is RootLayout. For example,

import 'styles/globals.css';

export default function RootLayout({
  children,
}: {
  children: React.ReactNode,
}) {
  return (
    <html lang="en">
      <head>
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      </head>
      <body>{children}</body>
    </html>
  );
}

But if you create the context inside RootLayout, then that won't work as remember? by default, RootLayout is a server component, and there is Server logic in there, so client-side context won't work.

Here is how you can fix this,

You can create a client only component and create your context there. For example,

//app/store-provider.js
'use client';
import { createContext, useReducer } from "react";

export const StoreContext = createContext();

const StoreProvider = ({ children }) => {
  const initialState = {
    data: []
  };
  return (
    <StoreContext.Provider value={{ state = initialState }}>
      {children}
    </StoreContext.Provider>
  );
};

//app/layout.js
export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <StoreProvider>
          {children}
        </<StoreProvider>>
      </body>
    </html>);
}

What about 3rd party providers?

Again, what about 3rd party package providers? For this to work, they need to declare the use client directive. But, you can create a common file called GlobalProviders, render all your providers there, and import GlobalProviders file inside RootLayout.

What are Shared components?

What if you want to create components that work on both the client and server? Allowing logic to be shared between the two will be essential to follow the DRY (Do not repeat yourself) principle.

Well, you can abstract the logic of the common component without interactivity and declare it wherever you want to use it. So, for example, if you declare a component a client-only file, it will be a client-side component, whereas if you do the same in a server-only file, then it's a server-side component or not at a directive at all.

Although, remember, for the server only component, you need to make sure you don't have any event handlers in there. Otherwise, it won't work.

Here is how you can create shared components:

Date is a common shared component. We want to use this component on the client as well as a server-side component. Here is how you can do it,

//date component is a common shared component
import { parseISO, format } from 'date-fns';

export default function Date({ dateString, formatType = 'LLLL dd, yyyy' }) {
  const date = parseISO(dateString);
  return (
    <time className="text-sm text-gray-600" dateTime={dateString}>
      {format(date, formatType)}
    </time>
  );
}

Notice how Card is a use client only component and the fact that Date is used here, it will be a client only component. If you inspect your client bundle, Date will be included in the client-side bundle.

'use client';

import Image from 'next/image';
import Link from 'next/link';
import { useEffect, useState } from 'react';
import Date from './date';
import { Tag } from './Tags';

export default function Card({
  id,
  date,
  title,
  link,
  formatType,
  abstract,
  imgUrl = '/card-bg.jpg',
}) {
  const [imgSrc, setImgSrc] = useState('/card-bg.jpg');

  useEffect(() => {
    setImgSrc(imgUrl);
  }, []);

  const handleError = () => {
    setImgSrc('/card-bg.jpg');
  };

  return (
    <div
      className={`w-full border-gray-700 border-solid border-spacing-4 border-2 m-2`}
    >
      <Link href={link}>
        <Tag>Client Component: Card</Tag>
        <li
          className="flex flex-col mb-4 px-6 dark:bg-gray-900 bg-white text-black dark:text-white hover:cursor-pointer pb-2 overflow-hidden hover:text-gray-900 border border-solid mr-2 dark:border-gray-800 border-gray-100 glass-list"
          key={id}
        >
          <>
            <Image
              src={imgSrc}
              alt={`image for ${title}`}
              width="400"
              height="100"
              onError={handleError}
            />
            <h3 className="font-averta-bold outline-none mb-0"> {title}</h3>
            <p className="ellipsis">{abstract}</p>
            {date && (
              <span className={'text-gray-951 text-xs flex flex-col'}>
                <Tag>React Server component: Date</Tag>
                <Date dateString={date} formatType={formatType} />
              </span>
            )}
          </>
        </li>
      </Link>
    </div>
  );
}

But if you were to use Date inside Page where Page is a Server side component, it would be rendered on the server, making Date a server only component. For Page, Date won't be included in the client bundle.

Resources:

https://nextjs.org/docs/advanced-features/react-18/server-components

That's a wrap, hope this post was useful. 🙌 Please let me know on Twitter how you like it and show some love.

Last updated:

. . .

Frontend Snacks 🍿

A Frontend Newsletter you'll love & get FREE weekly Snacks!

  • A code snippet to teach complex topics visually
  • Latest Frontend gossip/news
  • A snack for thought

All this to help you grow as a developer!