Pass Server Components as children to Client Components
Composing Server + Client Components
You cannot import a Server Component into a Client Component, but you can pass one as `children` or a prop. That keeps interactive shells thin and server work on the server.
What you'll learn
- Push the `'use client'` boundary as low as possible
- Pass Server Components through `children`
- Avoid the import-server-from-client pitfall
The most subtle rule of the App Router is this: a Client Component cannot import a
Server Component. But it can render one if the Server Component is passed in from a
parent server context as children or a prop.
The Forbidden Pattern
// ClientShell.tsx
'use client'
import ServerChild from './ServerChild' // BAD — bundles ServerChild for the client
export default function ClientShell() {
return <div><ServerChild /></div>
} That import pulls ServerChild into the client bundle and strips out anything
server-only (database calls, secrets), usually causing a build error.
The Allowed Pattern
Render the server piece from a server parent and hand it down through children:
// app/page.tsx (server)
import ClientShell from './ClientShell'
import ServerChild from './ServerChild'
export default function Page() {
return (
<ClientShell>
<ServerChild />
</ClientShell>
)
} ClientShell only sees children: ReactNode — an opaque React element. It can render it,
position it, animate it, but it does not import the server module.
// ClientShell.tsx
'use client'
import { useState } from 'react'
export default function ClientShell({ children }: { children: React.ReactNode }) {
const [open, setOpen] = useState(true)
return (
<div>
<button onClick={() => setOpen(o => !o)}>Toggle</button>
{open && <div className="panel">{children}</div>}
</div>
)
} The Mental Model
Decide where the client boundary belongs, then push it as low as possible. Server
Components own the data; Client Components own the interaction. They meet at children.