LearnAI ToolsCareerPractice BuildsPlayContact
Lesson 2416 min read

Composition Patterns

Practical patterns for mixing Server and Client Components without accidentally bloating your client bundle.

Push Client Boundaries Down

The single most valuable composition habit: keep "use client" as low (as deep) in the component tree as possible. Instead of marking an entire page as a Client Component for one interactive button, extract just that button into its own small Client Component and leave everything else as Server Components.

Client Boundary Too High

tsx
'use client'; // now the ENTIRE page ships to the browser
export default function ProductPage({ product }) {
const [qty, setQty] = useState(1);
return (
<div>
<h1>{product.name}</h1>
<p>{product.description}</p>
<input value={qty} onChange={e => setQty(e.target.value)} />
</div>
);
}

Client Boundary Pushed Down

tsx
// page.tsx — stays a Server Component
export default function ProductPage({ product }) {
return (
<div>
<h1>{product.name}</h1>
<p>{product.description}</p>
<QuantityPicker />
</div>
);
}
// QuantityPicker.tsx
'use client';
export default function QuantityPicker() {
const [qty, setQty] = useState(1);
return <input value={qty} onChange={e => setQty(e.target.value)} />;
}

The Children-Slot Pattern

As covered in the previous lesson, a Client Component can accept Server Components through its children (or any prop), as long as the parent Server Component is the one that renders them. This lets a Client Component provide interactive "chrome" — a modal, an accordion, a tab container — around content that stays server-rendered.

// Accordion.tsx — Client Component, purely structural/interactive
'use client';
export default function Accordion({ title, children }) {
const [open, setOpen] = useState(false);
return (
<div>
<button onClick={() => setOpen(!open)}>{title}</button>
{open && <div>{children}</div>}
</div>
);
}
// page.tsx — Server Component
import Accordion from './Accordion';
import ServerRenderedArticle from './ServerRenderedArticle';
export default function Page() {
return (
<Accordion title="Read more">
<ServerRenderedArticle />
</Accordion>
);
}

Sharing Data Without Prop Drilling

For data needed deep in a Client Component tree, prefer fetching it once in a Server Component near the top and passing it down, rather than fetching independently in many nested Client Components. If several unrelated Client Components need the same piece of state, a Context provider (itself a small Client Component) is the standard React answer.

A Complete Example

A typical, well-composed page: a Server Component page fetches data, renders mostly static Server Components, and drops in a handful of small Client Components exactly where interactivity is required.

// app/blog/[slug]/page.tsx — Server Component
import LikeButton from './LikeButton'; // Client Component
export default async function PostPage({ params }) {
const post = await getPost(params.slug); // runs on the server
return (
<article>
<h1>{post.title}</h1>
<p>{post.body}</p>
<LikeButton postId={post.id} initialLikes={post.likes} />
</article>
);
}

Common Beginner Mistakes

Wrapping the whole layout.tsx in "use client"

This makes every page under that layout ship extra JavaScript — instead, extract just the interactive nav toggle or theme switch into its own small Client Component.

Fetching the same data separately in five nested Client Components

Fetch it once in a Server Component higher up and pass it down as props, or lift it into shared client state if it needs to change after the initial load.

FAQs

As small as the actual interactive behavior requires — a single button, input, or toggle is a completely normal size for a Client Component.

Yes — most component libraries that use hooks or state need "use client" wherever they're first imported, so wrapping them in a small local Client Component is a common technique.

Summary

Good composition means pushing "use client" as deep as possible and using the children-slot pattern to keep Server Components server-rendered even inside interactive UI. Next, you'll zoom out to the rendering strategies these components participate in — SSR, SSG, ISR, and CSR.

Next Lesson →

Rendering Strategies