Server and Client Components
In the App Router every component is a Server Component by default. The 'use client' directive marks a boundary: the file and everything it imports become part of the client bundle.
| Server Component | Client Component | |
|---|---|---|
| Runs | On the server (build or request) | On the server for HTML, then hydrates in the browser |
async / await data | Yes | No |
| State, effects, event handlers | No | Yes |
| Secrets, DB, file system | Yes | No |
| Adds JS to the bundle | No | Yes |
Passing Server Components into Client ones
A Client Component cannot import a Server Component, but it can receive one as children or another prop:
tsx
// app/page.tsx (server)
<Modal>
<Cart /> {/* still rendered on the server */}
</Modal>
// modal.tsx
'use client';
export function Modal({ children }: { children: React.ReactNode }) {
const [open, setOpen] = useState(false);
return open ? <dialog open>{children}</dialog> : <button onClick={() => setOpen(true)}>Cart</button>;
}Rules of thumb
- Push
'use client'down to the leaves (a button, not the whole page) to keep the bundle small. - Props crossing the boundary must be serializable: no functions (except Server Actions), class instances or Dates with methods.
- Use the
server-onlypackage in modules with secrets so importing them from the client fails the build.
updated
did you know it?