The most productive Next.js apps compose Server and Client Components: server parents fetch and pass serializable props; client children handle interactivity. Think “server shell, client islands.”
Pattern: server page + client widget
// app/page.tsx — Server Component
import { LikeButton } from './like-button';
export default async function Page() {
const post = await getPost();
return (
<article>
<h1>{post.title}</h1>
<p>{post.body}</p>
<LikeButton initialLikes={post.likes} postId={post.id} />
</article>
);
}
// like-button.tsx
'use client';
export function LikeButton({ initialLikes, postId }: Props) {
const [likes, setLikes] = useState(initialLikes);
// ...
}
Children slot pattern
A Client Component can wrap {children} passed from a Server Component—static server content streams through the client shell (e.g. modal layout around server-rendered detail).
Anti-patterns
- Marking entire layouts
'use client'by default - Fetching in
useEffectwhen the server could load data first - Passing non-serializable props (class instances, functions) across the boundary
Self-check
- Who should fetch post data in the pattern above?
- What props are safe to pass from server to client?