Composition is how you combine Server and Client Components without collapsing your entire tree into client-side JavaScript. This lesson covers the key patterns for mixing the two effectively.
The "Wrap, Don't Import" Pattern
A Client Component cannot directly import and render a Server Component's file — but it can accept a pre-rendered Server Component through its children prop (or any other prop). This is the core composition pattern in the App Router: build interactive "shells" as Client Components, and pass server-rendered content into them from a parent Server Component.
This lets you keep something like a modal's open/close logic as a small Client Component, while the actual content inside the modal — which might need to fetch data — stays a Server Component, rendered from above and simply passed down.
// components/Modal.tsx (Client Component — interactive shell)
"use client";
import { useState } from "react";
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>}
</>
);
}
// app/page.tsx (Server Component — provides content)
import Modal from "@/components/Modal";
import ProductDetails from "@/components/ProductDetails"; // a Server Component
export default function Page() {
return (
<Modal>
<ProductDetails productId="42" />
</Modal>
);
}
Modal never imports ProductDetails directly — it receives the already-rendered result as children, so ProductDetails (and any data fetching it does) never becomes client JavaScript.
Composition Rules to Remember
Server Component → can import → Server or Client Components
Client Component → can import → only Client Components
Client Component → can receive → Server Components as children/props (pre-rendered)
Server Components can freely import and render both Server and Client Components.
Client Components can only directly import other Client Components.
A Server Component's output can still reach a Client Component if it's passed in as children or another prop.
This pattern is sometimes described as "lifting state up, pushing content down."
Composition Patterns Cheat Sheet
Common shapes for combining Server and Client Components.
Pattern
Use Case
Client shell + Server children
Modals, tabs, accordions with server-rendered content
Server page + Client leaf component
A page with one small interactive widget
Client Component + Client children only
Fully interactive widgets (date pickers, forms)
Server Component fetching, Client Component displaying via props
Charts, interactive tables
Interleaving Server and Client Components
A realistic page often interleaves both types several times: a Server Component page renders a Client Component <Tabs> shell, which receives several Server Component panels as children, one of which contains a small Client Component <LikeButton> for interactivity.
app/page.tsx (Server) renders <Tabs> (Client), passing panels as children.
Each panel (Server) renders article content plus a <LikeButton> (Client) for interactivity.
Only <Tabs> and <LikeButton> end up in the client JavaScript bundle.
Context Providers Must Be Client Components
React Context relies on client-side subscription behavior, so any component calling createContext/useContext's provider must be a Client Component. The common pattern is a small Providers Client Component near the root layout that wraps children (which can still include Server Components beneath it).
// app/providers.tsx
"use client";
import { ThemeProvider } from "./theme-context";
export default function Providers({ children }: { children: React.ReactNode }) {
return <ThemeProvider>{children}</ThemeProvider>;
}
Common Mistakes
Trying to import a Server Component's file directly inside a Client Component instead of passing it as a prop.
Wrapping the entire app in a Client Component Providers and assuming everything inside automatically becomes client-rendered too.
Forgetting Context providers must be Client Components, then being confused by a build error.
Over-lifting state to the top of the tree instead of composing smaller, focused Client Components.
Key Takeaways
Client Components can't directly import Server Components, but can receive them as children/props.
Server Components can import both Server and Client Components freely.
Common composition pattern: interactive Client "shells" wrapping server-rendered content.
Context providers must be Client Components, typically isolated near the root layout.
Good composition keeps most of the tree server-rendered while isolating interactivity precisely.
Pro Tip
When a design calls for something interactive wrapping a lot of content (a modal, an accordion, a carousel), build the interactive behavior as a minimal, content-agnostic Client Component that just renders children — this keeps the actual content fully flexible and server-renderable.
You now understand composition patterns. Next, learn the children pattern in more depth — the prop that makes nearly all of this composition possible.