Server and Client Components
The idea in one sentence
Section titled “The idea in one sentence”In the App Router every component is a Server Component until a file says "use client", and that one line draws a boundary that decides what code ever reaches the browser.
Server Components are the default
Section titled “Server Components are the default”A component in app/ runs on the server unless you opt out. That means it can be async, await data, and touch the database, the filesystem, or secrets directly — none of that code is sent to the browser.
// app/dashboard/page.tsx — a Server Component (no directive needed)import { db } from '@/lib/db';
export default async function Dashboard() { const users = await db.user.findMany(); // runs on the server only return <ul>{users.map((u) => <li key={u.id}>{u.name}</li>)}</ul>;}Because this never ships to the client, the browser downloads zero JavaScript for it. Server Components cannot use useState, useEffect, event handlers, or browser APIs — they render once, on the server.
”use client” marks a Client Component
Section titled “”use client” marks a Client Component”Put "use client" at the very top of a file and everything it exports becomes a Client Component: it runs in the browser and may use hooks, state, effects, event handlers, and browser APIs.
'use client';
import { useState } from 'react';
export default function Counter() { const [count, setCount] = useState(0); return <button onClick={() => setCount(count + 1)}>Count: {count}</button>;}The subtle part: the boundary is transitive. Every module a Client Component imports is pulled into the client bundle too, whether or not it has its own directive. You mark the boundary once, at the top of the tree; you do not repeat "use client" in every child.
Composing across the boundary
Section titled “Composing across the boundary”Two rules govern how the two kinds mix, and they are not symmetric:
- A Server Component can import and render a Client Component directly.
- A Client Component cannot import a Server Component — but it can accept one as
childrenor a prop.
That second rule is the doughnut (slot) pattern: the Client Component is the ring, and a Server Component fills the hole. The server-rendered content stays on the server even though a client component surrounds it.
// app/page.tsx — Server Componentimport ClientShell from './client-shell';import ServerData from './server-data';
export default function Page() { return ( <ClientShell> <ServerData /> {/* stays a Server Component, passed as children */} </ClientShell> );}One constraint makes this work: props crossing the boundary must be serializable. You can pass strings, numbers, plain objects, arrays, and JSX, but not functions, class instances, or Dates as raw props — they cannot survive the trip from server to client.
flowchart LR Root[Server Component root] --> Client["use client boundary"] Root --> Server[More Server Components] Client --> Bundle[Imports join client bundle] Root -. passes children .-> Client Client -. renders server child .-> Server