A mental model for Server vs Client Components
The rule of thumb I actually use when deciding whether a component needs 'use client', and how a Server Component parent can still pass data down to it.
This is a running note — I add to it whenever I trip over the Server/Client boundary again, which is more often than I'd like to admit.
The rule of thumb #
Only add "use client" when the file itself needs one of: useState/useEffect/other hooks, browser-only APIs (window, localStorage), event handlers (onClick etc.), or a third-party library that assumes a DOM (most animation libraries, most chart libraries).
Everything else — data fetching, anything touching the filesystem, anything that doesn't need interactivity — stays a Server Component by default.
The mistake I keep making #
Marking a component "use client" because it renders inside an interactive page, not because it itself needs interactivity. A component that just formats and displays a date doesn't need to be a Client Component just because its parent is one — Server Components can still be passed as children into Client Components.
"use client";
export default function Wrapper({ children }: { children: React.ReactNode }) {
const [open, setOpen] = useState(false);
return (
<div>
<button onClick={() => setOpen(!open)}>Toggle</button>
{open && children /* ← can still be a Server Component */}
</div>
);
}Passing server data into a client component #
The part that isn't obvious at first: a Client Component can't call a server-only function like fs.readFileSync itself, but it can receive the result of one as a prop from a Server Component parent. Same idea shows up in small hooks too — see the debounce hook snippet, which only needs "use client" because it uses useEffect, nothing more.
Open questions #
- When does it actually pay off to split a "server fetch, client render" component into two files vs. just accepting one client boundary a little higher up the tree?
- Worth measuring: how much smaller is the client bundle in practice when a heavy markdown-rendering step stays server-only?
Related content
Auth Context for Next.js (Cookie-Based Sessions)
A React context wrapping an API — login, logout, signup, and password reset — that keeps the client in sync with an httpOnly session cookie by revalidating on mount.
Route Handlers can return a plain Response, not just NextResponse
TIL you don't need NextResponse for a simple Route Handler — a standard Web API Response works fine, which matters for things like hand-rolled RSS/XML endpoints.
Clonify.io: Turning a Slow WordPress Marketplace into a High-Performance Next.js SaaS Platform
Rebuilding a digital-products marketplace from WordPress to Next.js and Payload CMS, with a custom JSON caching layer and secure, subscription-aware asset delivery.