What Are React Server Components?

React Server Components (RSC) fundamentally change the rendering model. Instead of shipping JavaScript to the browser and rendering there, RSC allows components to run on the server — sending only the result as HTML or serialized React tree to the client.

The Old Model vs RSC

// ❌ Old approach — client renders everything
// Large bundle, slow hydration
export default function ProductList() {
  const [products, setProducts] = useState([]);

  useEffect(() => {
    fetch("/api/products")
      .then(r => r.json())
      .then(setProducts);
  }, []);

  return <ul>{products.map(p => <li key={p.id}>{p.name}</li>)}</ul>;
}

// ✅ Server Component — runs on server, no JS sent
// async/await works natively!
export default async function ProductList() {
  const products = await db.products.findAll(); // direct DB access!

  return <ul>{products.map(p => <li key={p.id}>{p.name}</li>)}</ul>;
}

Next.js App Router

The Next.js 14+ App Router makes RSC the default. Here is how you structure a full-stack page:

// app/dashboard/page.tsx  (Server Component by default)
import { Suspense } from "react";
import { Analytics } from "./Analytics"; // client component
import { fetchStats }  from "@/lib/db";

export default async function DashboardPage() {
  const stats = await fetchStats(); // runs on server

  return (
    <div>
      <h1>Dashboard</h1>
      <p>Total Users: {stats.users}</p>
      <Suspense fallback={<div>Loading chart...</div>}>
        <Analytics data={stats} />
      </Suspense>
    </div>
  );
}

RSC allows you to access databases, file systems, and secrets directly — without exposing them to the client. This is a game-changer for security and performance.

Key Benefits

  • Zero client-side JavaScript for server components
  • Automatic code splitting
  • Direct database and file system access
  • Streaming with Suspense boundaries
  • Reduced Time to First Byte (TTFB)

When to Use Client Components

Not everything should be a server component. Use "use client" when you need:

"use client";

import { useState } from "react";

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

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

The rule of thumb: keep components on the server by default. Only opt into client rendering when you need browser APIs, event handlers, or state.