React Server Components Explained: What They Are and Why They Matter
React Server Components let you run React code on the server without shipping JS to the browser. Here's what that actually means for your Next.js app in 2026.
What React Server Components Actually Are
React Server Components (RSC) are components that run exclusively on the server and never ship their JavaScript to the client. Not SSR with hydration. Not a server-rendered HTML string. The component itself — the function, its imports, its logic — stays on the server entirely.
That's a meaningful distinction. With traditional SSR in Next.js before the App Router, you'd render on the server and then re-render on the client to attach event handlers. You were shipping the same component code twice. RSC breaks that pattern.
In practice, a Server Component can read from a database, call a private API, or access the filesystem directly — and the user's browser never sees any of that code. The output is serialised React element data, not a JS bundle. Worth noting: this is why you can't use useState or useEffect inside an RSC. There's no browser runtime to attach to.
The mental model clicked for me when I stopped thinking of it as 'SSR but faster' and started thinking of it as 'a template language that happens to be React.' Your component is a function that returns JSX. It just runs in a Node process instead of a browser.
Server vs Client Components: The Boundary That Matters
By default in Next.js 13+ with the App Router, every component is a Server Component. You opt into client behaviour by adding 'use client' at the top of a file. That directive marks a boundary — everything imported below that boundary runs in the browser.
Here's the bit that trips people up. You can pass a Client Component as a *child* of a Server Component. You can pass Server Component output as children props into a Client Component. What you can't do is import a Server Component from inside a Client Component — that would pull server-only code into the client bundle.
Honestly, the mental overhead of managing that boundary is the hardest part of RSC. It's not complicated once it clicks, but it will bite you before it clicks. Keep interactive UI in small, isolated Client Components at the leaves of your tree. Let the heavy data-fetching, layout, and rendering logic stay on the server.
One more thing — third-party component libraries written before 2023 almost universally assume a browser environment. They'll break as Server Components. You'll need to wrap them in a Client Component or wait for the library to ship an RSC-compatible version.
A Real Code Example: Data Fetching Without useEffect
This is where RSC actually saves you work. No useEffect, no loading state, no client-side fetch waterfall. Just async/await directly in the component.
// app/users/page.tsx — this is a Server Component by default
import { db } from '@/lib/db'
export default async function UsersPage() {
// runs on the server — db credentials never reach the browser
const users = await db.query('SELECT id, name, email FROM users LIMIT 50')
return (
<main className="p-8">
<h1 className="text-2xl font-bold mb-4">Team</h1>
<ul className="space-y-2">
{users.map((u) => (
<li key={u.id} className="text-sm text-gray-700">
{u.name} — {u.email}
</li>
))}
</ul>
</main>
)
}Compare that to what you'd write pre-RSC: a useEffect that fires after mount, a loading spinner, error handling for the fetch, and a client-side API route just to proxy the database call so you don't expose credentials. That's four separate concerns collapsed into seven lines.
Quick aside: you can still co-locate Client Components in the same file tree. If that list needed a delete button with a confirmation dialog, you'd extract a <DeleteUserButton /> with 'use client' and drop it inside the server-rendered list. The server handles the data; the client handles the interaction.
Performance: What You Actually Gain
The headline benefit is bundle size. A Server Component contributes zero bytes to your JavaScript bundle. If you've got a page that renders markdown, syntax-highlights code, and formats dates — all those libraries (marked, prism, date-fns) can live on the server and never touch the client.
Look, that matters more than it sounds. The median mobile device in 2026 still takes 3-4 seconds to parse and execute a 300kb JS bundle on a throttled connection. Cutting 80kb of utility libraries off the client payload is a real win for Time to Interactive.
There's also the fetch waterfall issue. Client Components fetch sequentially because each useEffect fires after the previous render. Server Components can fetch in parallel at the server level, and you can use React's Suspense to stream chunks of the page as they resolve. Individual sections can be 48px skeleton placeholders that fill in progressively — without extra client code.
That said, RSC isn't free. Cold-start latency matters. If your server is under-provisioned or your database queries aren't indexed, you've moved the slowness to the server instead of eliminating it. Profile before you declare victory.
Common Mistakes and How to Avoid Them
The most common mistake is putting 'use client' at the top of every component 'just to be safe.' That defeats the entire model. You end up with the old App Directory behaviour where everything hydrates. Be deliberate — only the interactive bits need to be Client Components.
Second: passing non-serialisable props across the server/client boundary. Functions, class instances, and Dates don't serialise cleanly. If you need to pass a callback from a Server Component into a Client Component, you can't — that's a client-side concern. Use Server Actions for mutations instead.
Third mistake is mixing up Server Actions with Server Components. They're related but distinct. A Server Action is an async function marked with 'use server' that a Client Component can call — it's the RSC answer to API routes for form mutations. You'd use one to handle a form submission without writing a separate /api/submit route.
If you're building UI components that need to work across both environments — like the components in Empire UI — you'll want to author them without assumptions about window or document. That makes them compatible with RSC out of the box and keeps your bundle leaner.
RSC in the Broader React Ecosystem in 2026
Next.js is still the primary way most teams consume RSC. Remix has its own server model. Expo is experimenting with RSC for React Native. But the RSC spec itself is framework-agnostic — it lives in React core, not Next.js.
What's changed since the initial React 18 release is tooling maturity. The React DevTools now show you clearly which components are server-rendered versus client-rendered. Error messages are better. The server-only and client-only packages from the Next.js team let you enforce import restrictions at build time, which catches boundary violations early.
Worth noting: as of React 19, the use hook landed, which lets you unwrap promises and Context inside Client Components without useEffect. That fills a gap where RSC felt awkward for async data that's generated client-side. The two models are converging in useful ways.
If you're building anything with heavy visual design — UI kits, component libraries, design-system tooling — RSC gives you a clean place to put the non-interactive scaffolding. Check out how we've structured the glassmorphism components on Empire UI; the demo wrappers are Server Components while the interactive controls stay client-side. It keeps the demo pages fast without compromising interactivity.
Should You Use React Server Components Right Now?
If you're starting a new Next.js project in 2026, yes — the App Router with RSC is the default and it's mature enough for production. The rough edges from 2023 are mostly smoothed out. The mental model is worth learning.
If you're on an existing Pages Router app, migrating incrementally is possible but not trivial. The two routers can coexist in the same Next.js project during migration, which helps. But don't migrate just for the sake of it — if your app is working, pick a new feature area as the beachhead.
For pure component libraries or design systems — the kind you'd browse on Empire UI — RSC compatibility is more about what you *don't* do than what you do. Avoid top-level browser globals, don't assume window exists, and don't force 'use client' unless the component is genuinely interactive. That's it.
The bigger picture: RSC is the React team's answer to 'why do we ship so much JavaScript?' It's not a silver bullet, but it's the most architecturally significant change to React since hooks in 2019. Learning it now means you're not scrambling to catch up when your team's next greenfield project lands on your desk.
FAQ
They're different things. SSR renders HTML on the server and hydrates it on the client — the JS still ships. RSC components never send their code to the browser at all. Next.js uses both together.
No. Hooks like useState, useEffect, and useContext require a browser runtime. Server Components are async functions — use await for data fetching instead. Move hook-dependent logic into a Client Component.
A Server Component is a UI component that renders on the server. A Server Action is an async function marked 'use server' that handles mutations — think form submissions. They're complementary, not interchangeable.
It depends. Libraries that reference window, use browser-only APIs, or add 'use client' unnecessarily may not be RSC-compatible. Check if the library exports components without those assumptions, or wrap them in your own Client Component boundary.