Skip to content

Server and Client Components

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.

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.

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.

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 children or 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 Component
import 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
Where the client boundary sits
In the App Router, what is a component before any directive is added?
What does "use client" do to the modules a file imports?
How can a Client Component include Server-Component content?
Which prop can safely cross the server-to-client boundary?