← Back to articles

React Server Components Explained: The Complete Guide (2026)

React Server Components (RSC) are the biggest paradigm shift in React since hooks. They fundamentally change where your components render, how data flows, and how much JavaScript ships to the browser. After two years of production use, here's what you need to know.

What Are React Server Components?

Server Components are React components that only render on the server. They never ship to the browser. No JavaScript bundle. No hydration. Just HTML.

// This component runs on the server only
// It can directly access databases, file systems, and secrets
async function ProductPage({ id }: { id: string }) {
  const product = await db.product.findUnique({ where: { id } });
  const reviews = await db.review.findMany({ where: { productId: id } });
  
  return (
    <div>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
      <ReviewList reviews={reviews} />
      <AddToCartButton productId={id} /> {/* This is a Client Component */}
    </div>
  );
}

Key insight: Server Components can fetch data directly (no API route, no useEffect, no loading states for initial render). The HTML arrives fully rendered.

Server vs Client Components

Server ComponentsClient Components
Where they runServer onlyServer (SSR) + Client
JavaScript shippedNoneYes (hydration)
Can useasync/await, db, fs, env varsuseState, useEffect, onClick, browser APIs
Can't useuseState, useEffect, event handlersDirect database access
Data fetchingDirect (await in component)useEffect, React Query, SWR
DirectiveDefault (no directive)"use client" at top of file

The Mental Model

Think of your component tree as having two zones:

Server Zone (default)          Client Zone ("use client")
├── Layout                     ├── SearchBar (interactive)
├── Header                     ├── AddToCartButton
├── ProductGrid                ├── FilterDropdown
│   ├── ProductCard            └── MobileMenu
│   └── PriceDisplay
├── Footer
└── ReviewList

Rule: Server Components can import and render Client Components. Client Components CANNOT import Server Components (but can accept them as children/props).

Common Patterns

Pattern 1: Data Fetching in Server Components

// app/dashboard/page.tsx (Server Component — no "use client")
import { getUser, getMetrics } from '@/lib/data';

export default async function DashboardPage() {
  const user = await getUser();
  const metrics = await getMetrics(user.id);
  
  return (
    <div>
      <h1>Welcome, {user.name}</h1>
      <MetricsGrid metrics={metrics} />
      <InteractiveChart data={metrics.chartData} /> {/* Client Component */}
    </div>
  );
}

No loading spinners for initial data. No waterfall requests. Data is fetched on the server and rendered as HTML.

Pattern 2: Composition (Server + Client)

// Server Component wrapping a Client Component
async function Sidebar() {
  const categories = await db.category.findMany();
  
  return (
    <aside>
      <h2>Categories</h2>
      {/* Pass server data to client component */}
      <CategoryFilter categories={categories} />
    </aside>
  );
}
// components/CategoryFilter.tsx
"use client";

import { useState } from 'react';

export function CategoryFilter({ categories }) {
  const [selected, setSelected] = useState([]);
  
  return (
    <div>
      {categories.map(cat => (
        <button 
          key={cat.id}
          onClick={() => setSelected(prev => [...prev, cat.id])}
          className={selected.includes(cat.id) ? 'active' : ''}
        >
          {cat.name}
        </button>
      ))}
    </div>
  );
}

Pattern 3: Children as Server Components

Client Components can render Server Components passed as children:

// Client Component
"use client";

export function Modal({ children, trigger }) {
  const [open, setOpen] = useState(false);
  return (
    <>
      <button onClick={() => setOpen(true)}>{trigger}</button>
      {open && <div className="modal">{children}</div>}
    </>
  );
}
// Server Component uses Modal with server-rendered content
<Modal trigger="View Details">
  <ProductDetails product={await getProduct(id)} /> {/* Server Component! */}
</Modal>

Pattern 4: Server Actions (Mutations)

Server Actions let you call server-side functions directly from Client Components:

// app/actions.ts
"use server";

export async function addToCart(productId: string) {
  const user = await getUser();
  await db.cart.create({
    data: { userId: user.id, productId },
  });
  revalidatePath('/cart');
}
// Client Component
"use client";
import { addToCart } from '@/app/actions';

export function AddToCartButton({ productId }) {
  return (
    <button onClick={() => addToCart(productId)}>
      Add to Cart
    </button>
  );
}

No API route. No fetch call. The function runs on the server, triggered from the client.

When to Use Which

Make It a Server Component When:

  • It displays data (fetched from database, API, or CMS)
  • It doesn't need interactivity (no clicks, no state, no effects)
  • It accesses server-only resources (database, file system, secrets)
  • It's a layout, page, or content wrapper
  • You want to reduce client-side JavaScript

Make It a Client Component When:

  • It uses useState, useEffect, or other hooks
  • It handles user interactions (onClick, onChange, onSubmit)
  • It uses browser APIs (localStorage, geolocation, IntersectionObserver)
  • It needs real-time updates (WebSocket connections)
  • It uses third-party libraries that require browser APIs

The 80/20 Rule

In most apps, 80% of components can be Server Components. Only the interactive pieces need "use client". Keep the client boundary as low in the tree as possible.

Performance Benefits

Before RSC (Traditional React)

  1. Browser downloads JS bundle (everything)
  2. Browser parses and executes JS
  3. React hydrates the entire tree
  4. Components fetch data (useEffect)
  5. Loading spinners while data loads
  6. Components re-render with data

With RSC

  1. Server renders HTML with data already included
  2. Browser receives ready-to-display HTML
  3. Only Client Components download JS and hydrate
  4. No data-fetching waterfalls

Real-world impact:

  • 30-50% smaller JavaScript bundles
  • Faster Time to Interactive (TTI)
  • Better Core Web Vitals (LCP, CLS)
  • No layout shift from loading states

Common Mistakes

Mistake 1: Making Everything a Client Component

// ❌ Don't do this — adding "use client" to everything
"use client";
export function ProductList({ products }) {
  return products.map(p => <div>{p.name}</div>);
}

If a component doesn't use hooks or event handlers, leave it as a Server Component.

Mistake 2: Trying to Import Server Components in Client Components

// ❌ This won't work
"use client";
import { ServerDataComponent } from './ServerComponent';

Instead, pass Server Components as children or props.

Mistake 3: Fetching Data in Client Components When Server Would Work

// ❌ Unnecessary client-side fetching
"use client";
export function UserProfile() {
  const [user, setUser] = useState(null);
  useEffect(() => {
    fetch('/api/user').then(r => r.json()).then(setUser);
  }, []);
  if (!user) return <Spinner />;
  return <div>{user.name}</div>;
}

// ✅ Just make it a Server Component
export async function UserProfile() {
  const user = await getUser();
  return <div>{user.name}</div>;
}

Mistake 4: Putting "use client" Too High in the Tree

// ❌ This makes the entire page a Client Component
"use client"; // in layout.tsx or page.tsx

// ✅ Push "use client" to the smallest interactive component
// page.tsx (Server Component)
export default async function Page() {
  const data = await getData();
  return (
    <div>
      <StaticContent data={data} />
      <InteractiveWidget /> {/* Only this is "use client" */}
    </div>
  );
}

Framework Support

FrameworkRSC SupportStatus
Next.js (App Router)FullProduction-ready
RemixPlannedIn development
WakuFullMinimal RSC framework
RedwoodJSEvaluatingNot yet

Next.js App Router is the primary way to use RSC in production today.

FAQ

Do Server Components replace API routes?

For data reading, often yes. You fetch data directly in the component. For mutations, Server Actions replace simple API routes. Complex API endpoints (webhooks, third-party integrations) still use API routes.

Can I use Server Components without Next.js?

Technically yes (Waku, custom setups), but practically Next.js is the only production-ready option in 2026.

Are Server Components the same as SSR?

No. SSR renders your entire React tree on the server, then hydrates everything on the client. Server Components render on the server and never hydrate — they stay as HTML. Client Components within the tree still hydrate normally.

Do Server Components work with React Query / SWR?

Yes, but you'd use them in Client Components for client-side data fetching (real-time updates, pagination, etc.). For initial data, Server Components are preferable.

Should I migrate my Pages Router app to App Router?

If starting fresh, use App Router. For existing apps, migrate incrementally — you can use both routers simultaneously. Don't rewrite everything at once.

The Verdict

React Server Components aren't optional knowledge anymore — they're how modern React apps are built. The mental model shift is significant but the benefits are clear: less JavaScript, faster pages, simpler data fetching, and better user experience.

Start here: Make your next page a Server Component. Only add "use client" to the pieces that genuinely need interactivity. You'll be surprised how few components actually need it.

Get AI tool guides in your inbox

Weekly deep-dives on the best AI coding tools, automation platforms, and productivity software.