How to Implement React Server Components

React Server Components (RSC) represent a paradigm shift in how we build web applications, allowing developers to render components on the server and send minimal JavaScript to the client. This article provides a straightforward guide on how to implement Server Components, covering the basic setup, data fetching techniques, and how to seamlessly integrate them with interactive Client Components using a modern framework like Next.js.

Understanding Server vs. Client Components

By default, in modern React frameworks like Next.js (App Router), all components are treated as Server Components. They run exclusively on the server, which keeps large dependencies out of the client-side JavaScript bundle and improves initial page load times.

If you need interactivity (such as useState, useEffect, or browser-only APIs), you must explicitly define a Client Component by adding the 'use client' directive at the very top of the file.


Step 1: Set Up your Environment

To implement Server Components today, you need a framework that supports the React Server Components architecture. The easiest way to start is by creating a new Next.js application:

npx create-next-app@latest my-rsc-app

During the setup, ensure you select the App Router option, as it natively supports Server Components by default.


Step 2: Create a Basic Server Component

Since components in the App Router are Server Components by default, you can write them using standard React syntax. You can also make the component function asynchronous (async/await) to fetch data directly inside the component rendering lifecycle.

Create a file named page.js (or page.tsx) inside the app/ directory:

// app/page.js

// This is a Server Component by default
export default async function Page() {
  const data = await getData();

  return (
    <main style={{ padding: '20px' }}>
      <h1>React Server Components Demo</h1>
      <ul>
        {data.map((item) => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    </main>
  );
}

// Data fetching happens directly on the server
async function getData() {
  const res = await fetch('https://api.example.com/items', {
    next: { revalidate: 3600 } // Cache data for 1 hour
  });
  
  if (!res.ok) {
    throw new Error('Failed to fetch data');
  }
 
  return res.json();
}

Step 3: Implement Interactivity with Client Components

Server Components cannot handle user interactions like click events or form submissions directly. To add interactivity, create a separate Client Component using the 'use client' directive.

Create a file named Counter.js in your components folder:

// components/Counter.js
'use client'; // This directive marks this file as a Client Component

import { useState } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

Step 4: Import Client Components into Server Components

You can import and render Client Components directly inside your Server Components. This allows you to keep the static, data-heavy parts of your page on the server while isolating interactive elements to the client.

Update your app/page.js to include the Counter component:

// app/page.js
import Counter from '../components/Counter';

async function getData() {
  const res = await fetch('https://api.example.com/items');
  return res.json();
}

export default async function Page() {
  const data = await getData();

  return (
    <main style={{ padding: '20px' }}>
      <h1>React Server Components Demo</h1>
      
      {/* Interactive Client Component */}
      <Counter />

      <h2>Data List:</h2>
      <ul>
        {data.map((item) => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    </main>
  );
}

Best Practices for Implementation