Modern Web · React

React 19 Server Components

How React Server Components actually work in React 19: rendering only on the server, the use client boundary, why they can't be imported into Client Components, and why this is a framework feature, not a plain-React one.

John Kihiu12 min read

The thing that took me longest to internalize about React Server Components is that they aren't a React feature you enable — they're a build-time and routing convention that a framework has to implement, with React's role being the component model both sides share. Understanding where the server/client line actually sits matters more than memorizing the "use client" directive.

The mental model: server-only, zero JS shipped

A Server Component runs exclusively on the server (or at build time), produces its output as part of the initial response, and never re-renders in the browser — its code, and any dependencies it imports, never end up in the client JS bundle at all. That's what makes it different from server-side rendering a normal component: SSR still ships the component's code to hydrate later, an RSC doesn't ship at all. Because it never runs client-side, a Server Component can do things a Client Component structurally can't: query a database directly, read a filesystem, or use a secret API key, without any of that code being reachable by the browser.

TSX · a server component
// app/dashboard/page.tsx — no "use client", runs only on the server
import { db } from '@/lib/db';

export default async function DashboardPage() {
  const projects = await db.query('select id, name from projects limit 20');
  return (
    
    {projects.map((p) => (
  • {p.name}
  • ))}
); }

The "use client" boundary

Everything is a Server Component by default in a framework that supports RSC; you opt a file into being a Client Component by adding the "use client" directive at its top. That marks the boundary: from that file downward, the component and everything it imports gets bundled and shipped to the browser, and can use hooks like useState and browser APIs that a Server Component can't.

TSX · a client component
'use client';
import { useState } from 'react';

export function LikeButton({ postId }: { postId: string }) {
  const [liked, setLiked] = useState(false);
  return ;
}

Composition: children flow one way

The rule that trips people up first: a Client Component cannot import a Server Component. Once you cross into "use client", everything imported from there is bundled for the browser, and a Server Component's code (database calls, server-only imports) can't safely live in that bundle. The escape hatch is passing Server Components down as children or props — a Client Component can render a Server Component that was handed to it by its parent, it just can't import one directly itself.

Composition via children, not imports

A client-side layout or modal wrapper can still show server-rendered content: render the server component higher up the tree and pass it in as `children`, rather than importing it inside the client file.

Why this needs a framework, not just React

Server Components depend on a bundler and router that understand the server/client split — deciding which modules get sent to the browser, serializing the server-rendered output into something the client can stitch together, and wiring up the network boundary between the two. Plain React (a Vite SPA, Create React App) has no server runtime and no bundler-level concept of this split, so there's no "just enable RSC" flag to flip. In practice, this means Next.js's App Router today, with other frameworks and bundlers building equivalent support over time.

Be honest about what "using React 19" gets you here

Upgrading a plain React app's package.json to React 19 does not give you Server Components. RSC requires the framework layer to actually implement the server/client boundary — the React version is necessary but nowhere near sufficient.

Wrapping up

Server Components are best understood as "components that opt out of the client bundle by default," with Client Components as the deliberate exception rather than the norm. The mental flip — start server-first, add "use client" only where you need interactivity or browser APIs — is the real shift, and it only works because a framework like Next.js has built the machinery underneath React to make that boundary real.

John Kihiu
Acumatica ERP Developer · Laravel Engineer

Independent software engineer in Nairobi specialising in Acumatica customisations, Laravel backends, and tax fiscalisation integrations across East and Southern Africa.