Skip to content
Back to Blog
Alex CarterAlex Carter1 min read

Mastering 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.

Mastering React Server Components: A Mental Model

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.


Share:XTelegramLinkedInvia @Ariyan

// Comments

Powered by Giscus

Comments are powered by GitHub Discussions. Configure Giscus incomponents/blog/comments.tsxto enable live discussions.

// Related Posts