17 — Rendering Composition — Server and Client Components in One Tree
Mixing two kinds of components in one tree sounded chaotic until the default made the rule obvious. The model that clicked: Server Components are the default (render on the server, ship zero JS); Client Components are the deliberate opt-in for interactivity. [1][2] Composition is the art of placing the client boundary as low and small as possible, because everything inside it ships JavaScript.
The two component kinds
Server Components render on the server. They can fetch data directly with async/await, access backend resources, and ship zero JavaScript to the browser — their output is HTML, not a component that re-hydrates (gets its JavaScript wired back up on the client) [2][3]. They're the default in the App Router.
Client Components are opted into with the 'use client' directive. They render on the client (after an initial server render), can use useState, useEffect, event handlers, and browser APIs. They're the right home for interactivity — buttons, forms, animations, anything that responds to the user [3][4].
// Server Component (default) — data fetched on the server, zero JS shipped
export default async function Page() {
const posts = await fetchPosts();
return (
<ul>
{posts.map(p => <li key={p.id}>{p.title}</li>)}
<LikeButton postId={posts[0].id} /> {/* client island */}
</ul>
);
}// app/LikeButton.tsx — Client Component
'use client';
import { useState } from 'react';
export function LikeButton({ postId }: { postId: string }) {
const [liked, setLiked] = useState(false);
return <button onClick={() => setLiked(!p)}>{liked ? '♥' : '♡'}</button>;
}The composition rules
The rules that govern how these compose are what make the model tractable [1][2]:
- Server → Client: A Server Component can import and render a Client Component. This is the normal case — the page (server) renders an interactive widget (client) and passes it props.
- Client → Server: A Client Component cannot import a Server Component. Once you're in the client boundary, everything imported into it becomes client too.
- Passing Server Components into Client Components: The escape hatch is the children prop. A Client Component can accept a Server Component as a prop (typically children) without importing it — the Server Component is rendered on the server and handed in as already-rendered output.
Why the boundary placement matters
Every component imported into a Client Component also becomes a Client Component — the boundary is contagious downward. So the goal is to push the 'use client' directive as far down the tree as possible, isolating interactivity in small "client islands" while keeping the surrounding shell on the server [1]. A whole-page 'use client' ships the whole page's JS; a small interactive widget marked 'use client' ships only that widget's JS.
This is the strategic decision: which parts of the UI genuinely need to be interactive (state, effects, event handlers), and which can stay as server-rendered HTML. The art is in finding the smallest client islands that still deliver the interactivity.
How I use this
Every page starts as a Server Component. I add Client Components only when a piece genuinely needs interactivity or browser APIs, and I keep them small — a button, a form, a widget — rather than wrapping whole sections. When a Client Component needs server-rendered content around it, I pass that content in as children rather than importing a Server Component. The discipline pays off in bundle size: the more that stays on the server, the less JavaScript ships to the browser.
References
[1] Vercel, "Server and Client composition patterns," Next.js Docs, 2024. [Online]. Available: https://nextjs.org/docs/14/app/building-your-application/rendering/composition-patterns
[2] Vercel, "Server and Client Components," Next.js Docs, 2024. [Online]. Available: https://nextjs.org/docs/app/getting-started/server-and-client-components#how-do-server-and-client-components-work-in-nextjs
[3] Vercel, "Server and Client Components — when to use," Next.js Docs, 2024. [Online]. Available: https://nextjs.org/docs/app/getting-started/server-and-client-components#when-to-use-server-and-client-components
[4] React, "Server Components," React Docs, 2024. [Online]. Available: https://react.dev/reference/rsc/server-components
[5] "Next.js 15 tutorial — Server and Client Components," YouTube, 2024. [Video]. Available: https://www.youtube.com/watch?v=dMCSiA5gzkU
Knowledge check · Question 1 of 5
In the App Router, components are Server Components by default. How do you opt into client behavior?
Comments
Leave a Comment
You must be signed in to comment
0 Comments
No comments yet. Be the first to comment!