CodeVix Labs
Engineering Team
TL;DR: React Server Components (RSC) let you render parts of your UI on the server, send finished HTML plus a lightweight description to the browser, and ship far less JavaScript. They pair well with client components for interactivity. Used deliberately, react server components cut bundle size and speed up data-heavy pages; used carelessly, they add confusion. This guide explains the model with examples and shows when it is worth adopting.
What are React Server Components?
React Server Components are a rendering model where certain components run only on the server. They never ship their JavaScript to the browser. Instead, the server executes the component, resolves any data it needs, and streams a serialized result to the client, which React uses to build the page. The component code itself — and the libraries it imports — stay on the server.
This is different from traditional server-side rendering (SSR). With classic SSR, React renders HTML on the server for the first paint, but then hydrates that HTML by sending the same component code to the browser so it can become interactive. React server components change the deal: for a server component, there is no hydration and no client bundle at all. Only the components that genuinely need interactivity — forms, dropdowns, anything using state or effects — ship to the browser as client components.
In practice you write both kinds in the same tree. Server components are the default in frameworks that support the model, and you opt a file into the browser with a directive at the top.
How do server and client components differ?
The mental model is simpler than it first appears: server components do data and structure, client components do interaction. Here is a direct comparison.
| Capability | Server Component | Client Component |
|---|---|---|
| Ships JavaScript to browser | No | Yes |
| Can fetch data directly (DB, API, secrets) | Yes | No (must call an API) |
Can use useState, useEffect, event handlers | No | Yes |
| Access to browser APIs (window, localStorage) | No | Yes |
| Can import heavy server-only libraries safely | Yes | No |
| Default in App Router-style frameworks | Yes | Opt in with "use client" |
A key rule: a server component can render a client component and pass it props (including data), but a client component cannot import a server component. It can, however, accept one as a children prop. This is what lets you keep an interactive shell on the client while its content stays on the server.
What does a React server component look like in code?
Here is a server component that fetches data directly. Note there is no useEffect, no loading state, and no API route — the component simply awaits its data because it runs on the server.
// app/products/page.tsx (Server Component by default)
import { db } from '@/lib/db';
import AddToCart from './add-to-cart';
export default async function ProductsPage() {
const products = await db.product.findMany();
return (
<ul>
{products.map((p) => (
<li key={p.id}>
{p.name} — ${p.price}
<AddToCart productId={p.id} />
</li>
))}
</ul>
);
}
The interactive part — the button — is a client component:
// app/products/add-to-cart.tsx
'use client';
import { useState } from 'react';
export default function AddToCart({ productId }: { productId: string }) {
const [added, setAdded] = useState(false);
return (
<button onClick={() => setAdded(true)}>
{added ? 'Added' : 'Add to cart'}
</button>
);
}
The browser downloads JavaScript for AddToCart only. The product list, the database client, and any ORM code never reach the user. On a catalog with hundreds of items, that difference in shipped code is meaningful.
Why do React server components matter for performance?
Three benefits tend to show up in real products:
- Smaller bundles. Data-fetching logic, formatting libraries (date, markdown, syntax highlighting), and ORM code stay on the server. Users on mid-range phones and slow networks feel this most.
- Fewer round trips. Because a server component can query the database directly, you avoid the classic pattern of render → empty state → fetch → re-render. Data is ready when the markup is.
- Streaming. The server can stream the page in chunks, showing fast content immediately while slower sections resolve behind a boundary. This helps perceived speed and, indirectly, Core Web Vitals.
The honest caveat: these gains are real but not automatic. If you mark most of your tree with "use client", you get SSR with extra steps and little benefit. The value comes from keeping the client boundary small and pushing data and static structure to the server.
When should you use them — and when not?
React server components are production-ready inside frameworks that support them, most notably Next.js with the App Router. They are the recommended default there. But the model is not a fit for every situation.
| Situation | Good fit for RSC? |
|---|---|
| Content, dashboards, catalogs, data-heavy pages | Strong fit |
| Marketing sites and blogs | Strong fit |
| Highly interactive apps (editors, canvases, games) | Partial — large client islands are fine |
| Single-page app served from static hosting, no Node server | Poor — you need a server runtime |
| Legacy Create React App / pure client SPA | Requires migration to a supporting framework |
If your team is choosing between plain React and a framework that enables RSC, our comparison of Next.js vs React walks through the trade-offs in more depth. If you are hiring for this work, note that RSC fluency is now a real differentiator among senior engineers — see our guide on how to hire Next.js developers.
What are the common mistakes and gotchas?
Teams new to the model tend to hit the same walls:
- Marking everything as a client component. One
"use client"at the top of a shared layout can pull most of your app onto the client. Push the directive down to the smallest leaf that truly needs it. - Passing non-serializable props. Props from a server component to a client component must be serializable — you cannot pass functions, class instances, or a database connection across the boundary.
- Reaching for browser APIs in server components. No
window,localStorage, or event handlers on the server. The compiler will usually stop you, but the error can be confusing at first. - Leaking secrets. Server components can read environment secrets safely, but if you pass them as props into a client component, they ship to the browser. Keep secrets server-side.
- Data mutations. RSC is about rendering. For writes, pair it with Server Actions or a normal API, and design those endpoints carefully — see our note on idempotency in API design so retries do not double-charge or double-submit.
Getting the client boundary right is genuinely the hardest part, and it is where an experienced team earns its keep. At CodeVix Labs we build QA-first Next.js and TypeScript products for founders worldwide, and drawing that boundary well — small islands, serializable props, secrets contained — is one of the first things our reviews check. If you want a second opinion on your architecture, get in touch or see how we work across our services.
Frequently asked questions
Are React Server Components the same as server-side rendering?
No. SSR renders your components to HTML on the server and then sends the component code to the browser to hydrate it. React server components go further: their code never reaches the browser at all, so there is no hydration for them and no client bundle. RSC and SSR are complementary and are typically used together.
Do I need Next.js to use React Server Components?
You need a framework or bundler that implements the RSC protocol and provides a server runtime. Next.js with the App Router is the most mature production option today, and other frameworks are adopting the model. You cannot use RSC in a plain client-only React app with no server.
Will React Server Components make my app faster automatically?
Not automatically. The gains come from keeping the client boundary small so you ship less JavaScript and fetch data on the server. If most of your tree is marked "use client", you see little benefit. Measure with real-device metrics before and after.
Can I migrate an existing React app incrementally?
Often yes, but it usually means moving to a supporting framework and adopting its routing model, then converting routes one at a time. Whether that is worth it depends on your app — our take on rewrite vs refactor can help you decide before committing.
Ready to discuss your project?
Book a free 15-minute technical audit with our engineering team.