The App Router introduces React Server Components (RSC). By default, every component in app/ is a Server Component unless marked otherwise.

Server Components (Default)

Server Components run on the server during rendering. They:

  • Can async/await data directly
  • Cannot use hooks (useState, useEffect)
  • Cannot use browser APIs (window, localStorage)
  • Cannot attach event handlers (onClick)
  • Send zero JavaScript to the client for themselves
  // app/products/page.tsx — Server Component
async function getProducts() {
    const res = await fetch('https://api.example.com/products');
    return res.json();
}

export default async function ProductsPage() {
    const products = await getProducts();

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

Client Components

Add 'use client' at the top of a file to make it a Client Component:

  'use client';

import { useState } from 'react';

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

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

Client Components run in the browser, support hooks and event handlers, and are included in the JavaScript bundle.

When to Use Each

Use Server Component Use Client Component
Fetch data Interactive UI (clicks, forms)
Access backend resources State (useState, useReducer)
Large dependencies (stay on server) Effects (useEffect)
Static content Browser-only APIs
SEO-critical content Third-party client libraries

Rule of thumb: start with Server Components. Add 'use client' only when you need interactivity.

Composition Pattern

Server Components can import Client Components, but not the reverse:

  // app/page.tsx — Server Component
import Counter from '@/components/Counter';
import ProductList from '@/components/ProductList';

export default async function Page() {
    const products = await getProducts();

    return (
        <main>
            <ProductList products={products} />
            <Counter />
        </main>
    );
}
  

Pass Server Component output as children to Client Components:

  'use client';

export default function Modal({ children }: { children: React.ReactNode }) {
    const [open, setOpen] = useState(false);
    return (
        <>
            <button onClick={() => setOpen(true)}>Open</button>
            {open && <div className="modal">{children}</div>}
        </>
    );
}
  

'use client' Boundary

The directive applies to the entire file and all its imports. Keep Client Components small — push 'use client' as far down the tree as possible:

  app/page.tsx              Server
├── ProductGrid.tsx       Server
│   └── AddToCart.tsx     Client (only this ships JS)
└── Footer.tsx            Server
  

Server → Client props must be serializable (JSON-compatible). You cannot pass functions — define event handlers inside the Client Component.

Next: fetch data in Server Components with caching and revalidation.