Alex Carter1 min readMastering React Server Components: A Mental Model
React Server Components sound complicated, but they click once you grok the module boundary. Here's the mental model that finally made it stick.
React Server Components (RSC) confused me for months. The docs were fine, but the mental model was missing. Here's the one that finally made it click for me.
The Boundary Is the Module
The key insight: the "use client" directive is a module boundary, not a
component boundary. When you write "use client" at the top of a file, you're
saying "everything imported through this file is part of the client bundle."
// ClientBoundary.tsx
"use client";
// Anything imported here becomes client-side JavaScript.
import { motion } from "framer-motion";
export function ClientBoundary({ children }: { children: React.ReactNode }) {
return <motion.div animate={{ opacity: 1 }}>{children}</motion.div>;
}But — and this is the magic — children passed into a client component can
still be server components.
The children Pattern
This is the single most useful RSC pattern. You wrap server content in a client shell:
// app/page.tsx (server component)
import { ClientShell } from "./client-shell";
import { ServerChart } from "./server-chart";
export default function Page() {
return (
<ClientShell>
<ServerChart data={heavyData} />
</ClientShell>
);
}ServerChart runs on the server. Its output is passed as children through
the client boundary. No client JavaScript for the chart logic.
Remember
You cannot import a server component into a client component file. But you
can pass it as children (or any prop) from a server component. That
distinction is everything.
When You Actually Need Client Components
- Interactivity:
onClick,onChange, form state. - Lifecycle:
useEffect,useLayoutEffect. - Browser APIs:
window,localStorage,matchMedia.
A heuristic
If a component doesn't use any of the above, it can be a server component. Default to server, opt into client.
// Quick decision flowchart, in code form
function shouldUseClient(component: ComponentSpec): boolean {
return (
component.hasEventHandlers ||
component.usesState ||
component.usesEffects ||
component.usesBrowserAPIs
);
}Data Fetching Stays on the Server
Because server components run on the server, they can read from the database directly — no API layer needed for first-party data.
The biggest RSC win: you stop building an internal REST API that exists only to serve your own frontend. The database becomes your API for initial render.
Summary
- The
"use client"directive marks a module boundary. - Server components can be passed into client components as children.
- Default to server; opt into client only when you need interactivity.
Once the module-boundary model clicks, RSC stops feeling magical and starts feeling obvious. That's the sign you've understood it.
// Comments
Powered by Giscus
Comments are powered by GitHub Discussions. Configure Giscus incomponents/blog/comments.tsxto enable live discussions.
// Related Posts
Getting Started with Next.js 14 App Router
A practical tour of the App Router: server components, layouts, data fetching, and the mental models that make it click.
2026-04-02TypeScript Patterns I Actually Use in Production
Beyond the basics: discriminated unions, branded types, and the patterns that have earned a permanent place in my toolbox.
2026-02-21How I Ship Side Projects Without Burning Out
A sustainable system for building side projects: scoping, timeboxing, and knowing when something is 'done'.