Follow AiTechWorlds on LinkedIn for professional AI content!Follow Now →
20 minLesson 18 of 33
Server & Client Components

The Server Component Mental Model

The Server Component Mental Model

Server Components are the biggest architectural shift in React since hooks. Understanding them at a mental-model level — not just syntax — is what makes Next.js App Router click. Most developers struggle because they try to learn rules before they understand the why.

The Problem Server Components Solve

Traditional React apps (Create React App, Vite) have a fundamental issue: everything runs in the browser.

User requests page → Browser gets empty HTML → Downloads JS bundle → 
React runs in browser → Renders components → User sees content

Problems with this:

  • Large bundles — every dependency ships to the browser
  • Slow initial load — user waits for JS to download, parse, execute
  • Server secrets can't be used — API keys, database connections in the browser
  • Waterfall requests — component loads, then fetches data, then child fetches data

Server Components run on the server. They render to HTML before the browser gets involved.

The Mental Model: Two Worlds

Imagine your app split into two distinct environments:

┌─────────────────────────────────┐
│           SERVER                │
│  - Has database access          │
│  - Has secrets/API keys         │
│  - No interactivity             │
│  - Renders to HTML              │
│  - Server Components live here  │
└────────────────┬────────────────┘
                 │ HTML + RSC payload
┌────────────────▼────────────────┐
│           BROWSER               │
│  - Has interactivity            │
│  - Has event handlers           │
│  - Has state (useState)         │
│  - No database/secrets          │
│  - Client Components live here  │
└─────────────────────────────────┘

Server Components: Render on server, generate HTML. Can access databases directly. Cannot use hooks, event handlers, or browser APIs.

Client Components: Run in browser. Have interactivity. Cannot access database directly.

In Next.js App Router

By default, every component is a Server Component.

You opt into a Client Component with 'use client' at the top of the file:

// ProductPage.tsx — Server Component (default, no directive)
import { db } from '@/lib/db';  // Direct database access ✓

async function ProductPage({ params }: { params: { id: string } }) {
    // Fetch data directly — no API route needed!
    const product = await db.query(
        'SELECT * FROM products WHERE id = ?',
        [params.id]
    );
    
    return (
        <div>
            <h1>{product.name}</h1>
            <p>${product.price}</p>
            <AddToCartButton productId={product.id} />  {/* Client Component */}
        </div>
    );
}
// AddToCartButton.tsx — Client Component
'use client';

import { useState } from 'react';

function AddToCartButton({ productId }: { productId: number }) {
    const [added, setAdded] = useState(false);
    
    return (
        <button onClick={() => {
            addToCart(productId);
            setAdded(true);
        }}>
            {added ? '✓ Added!' : 'Add to Cart'}
        </button>
    );
}

The Composition Pattern

You can render Client Components inside Server Components. The key: data flows down, not up.

// Server Component — can do async, DB queries, access secrets
async function Dashboard() {
    const [user, stats, recentOrders] = await Promise.all([
        db.getUser(userId),
        db.getStats(userId),
        db.getRecentOrders(userId, 10),
    ]);
    
    return (
        <div className="dashboard">
            <UserHeader user={user} />              {/* Could be Server Component */}
            <StatsGrid stats={stats} />             {/* Server Component */}
            <InteractiveChart data={stats.chartData} />  {/* Client Component — has hover/filter */}
            <OrderTable orders={recentOrders} />    {/* Server Component */}
            <NotificationBell userId={user.id} />   {/* Client Component — has real-time */}
        </div>
    );
}

The result: most of the page is static HTML from the server. Only the interactive parts ship JavaScript.

What Can and Can't Each Component Type Do

CapabilityServer ComponentClient Component
async/await (top level)
Direct database queries
Access env variables (secrets)Only public ones
useState / useEffect hooks
Event handlers (onClick, etc.)
Browser APIs (window, document)
Render Server Components as childrenOnly via children prop
Import Client Components

The "Passing Children" Pattern

Client Components can contain Server Components if they receive them as children:

// 'use client' — this is a Client Component
'use client';
function Modal({ children }: { children: React.ReactNode }) {
    const [open, setOpen] = useState(false);
    return (
        <div>
            <button onClick={() => setOpen(!open)}>Toggle</button>
            {open && <div className="modal">{children}</div>}
        </div>
    );
}

// Server Component — renders a Client Component with Server Component children
async function Page() {
    const data = await db.getExpensiveData();   // Server-side
    return (
        <Modal>
            <DataDisplay data={data} />  {/* Server Component as children */}
        </Modal>
    );
}

DataDisplay is still a Server Component — it rendered on the server. Modal is a Client Component with interactivity. They work together.

Why This Matters for Performance

A traditional React app for a dashboard:

Bundle: React (45KB) + App (120KB) + Chart library (80KB) + Data grid (95KB) = ~340KB
Time to interactive: ~3-5 seconds on mobile

With Server Components:

Server renders: Dashboard HTML + StatsGrid HTML + OrderTable HTML
Bundle: React (45KB) + AddToCartButton (2KB) + InteractiveChart (80KB) = ~127KB
Time to interactive: <1 second on mobile (HTML already there)

Only interactive components need to ship JavaScript. Read-only content becomes pure HTML.

The Decision Framework

Ask yourself: Does this component need to be interactive?

  • No interactivity needed? → Server Component
  • Uses useState, useEffect, or event handlers? → Client Component
  • Uses browser APIs? → Client Component
  • Fetches data that must be real-time on client? → Client Component
  • Everything else? → Server Component (the default)

Rule of thumb: Push 'use client' as deep in the component tree as possible. The more Server Components you have, the better your performance.

Next lesson: When to Use Client Components — a practical decision guide for the boundary between server and client.

📱

Get this course's notes on Telegram!

Free cheat sheets, summaries & practice exercises

Get Notes Free →
!