In 2026, React Server Components (RSC for short) are no longer an experimental concept. According to the State of React 2026 survey, over 50% of developers want to use RSC in production. But what exactly is it? How does it differ from traditional SSR? How does it stack up against alternatives outside the Next.js ecosystem?
This article covers three dimensions: what it is → how to write it → how it compares to alternatives.

Part 1: What Is RSC and What Problem Does It Solve
The Starting Point
Traditional React apps are purely client-side rendered (CSR). The browser downloads the entire JS bundle, builds the DOM tree, fetches data, and renders the UI on the client. This process has several pain points:
– Users wait through a long chain: JS download → compile → execute → data fetch → render
– Low-end devices struggle with large JS bundles
– Most components don’t need interactivity (article body text, list items), yet their JS still ships to the browser
React introduced Server-Side Rendering (SSR) to address these issues. SSR renders components into HTML on the server before sending it to the browser, letting users see content faster. But this is essentially “CSR wrapped in HTML” — the browser still downloads the full JS bundle and hydrates it to turn static HTML into an interactive React app.
RSC takes a completely different approach. RSC doesn’t render all components on the server — it lets some components run exclusively on the server without sending any JS to the browser.
RSC vs SSR vs CSR: The Fundamental Difference
The key to understanding RSC is that it’s orthogonal to SSR:
| / | CSR | SSR | RSC |
|---|---|---|---|
| Render location | Browser | Server → Browser | Server (partial) |
| JS shipped to browser | All | All (requires hydration) | Only Client Components |
| Data fetching | Client-side API | Server can pre-fetch | Direct DB/FS access |
| Interactivity | Full | Full (after hydration) | Not supported |
| Bundle size impact | Full bundle | Full bundle | Interactive parts only |
How RSC Works Under the Hood
RSC’s rendering flow breaks down into a few steps:
1. Server receives a request and starts rendering from the root component. The root component must be a Server Component
2. When it encounters a Server Component, it executes normally and generates a React element tree
3. When it hits a Client Component (marked with `’use client’`), it doesn’t execute the internal logic. Instead, it generates a placeholder plus a module reference for the browser to handle later
4. The server serializes the result into a special format (called the Flight payload) and streams it to the browser
5. The browser reconstructs the React tree — Server Component parts use the serialized result directly, while Client Components download JS by module reference and render
The critical difference: A Server Component’s output isn’t HTML — it’s a serialized React element tree. This lets React precisely reconstruct the component structure on the client while skipping non-interactive parts at zero cost.
Part 2: Code Examples
Basic Server Component
By default, every component in the Next.js App Router is a Server Component. No extra declaration needed:
```tsx
// app/posts/page.tsx — This is a Server Component
import { db } from '@/lib/database'
export default async function PostsPage() {
const posts = await db.post.findMany({ take: 20 })
return (
<div>
<h1>Latest Posts</h1>
{posts.map(post => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</article>
))}
</div>
)
}
```
What happens here:
– Runs on the server, database queries have zero round-trips
– Only final HTML is sent to the browser
– No JS bundle — this component has no interactivity
Client Component
When you need interactivity, add `’use client’`:
```tsx
// app/components/LikeButton.tsx
'use client'
import { useState } from 'react'
export default function LikeButton({ postId }: { postId: number }) {
const [liked, setLiked] = useState(false)
return (
<button onClick={() => setLiked(!liked)}>
{liked ? '❤️ Liked' : '🤍 Like'}
</button>
)
}
```
Mixing Server and Client Components Correctly
Server Components can’t import Client Components directly for rendering — but they can pass them through `children` (composition pattern). This is the most essential design pattern in RSC:
```tsx
// app/posts/page.tsx (Server Component)
import { db } from '@/lib/database'
import LikeButton from './LikeButton' // Client Component
export default async function PostsPage() {
const posts = await db.post.findMany({ take: 20 })
return (
<div>
{posts.map(post => (
<PostCard key={post.id} post={post}>
<LikeButton postId={post.id} />
</PostCard>
))}
</div>
)
}
```
```tsx
// ❌ Wrong: Can't import Server Component inside Client Component
'use client'
import ServerComponent from './ServerComponent' // Error!
export default function ClientComponent() {
return <ServerComponent />
}
```
```tsx
// ✅ Correct: Pass through children
'use client'
export default function ClientComponent({ children }: { children: React.ReactNode }) {
return <div className="card">{children}</div>
}
```
Data Fetching in Server Components
One of RSC’s biggest advantages is that data fetching can go directly into the component — no extra abstraction needed:
```tsx
// Server Component — direct DB query, zero client-side JS
export default async function UserDashboard({ userId }: { userId: string }) {
const user = await db.user.findUnique({ where: { id: userId } })
const posts = await db.post.findMany({ where: { authorId: userId } })
return (
<div>
<h1>{user.name}'s Dashboard</h1>
<p>{posts.length} posts total</p>
</div>
)
}
```
Compare with the client-side approach — you need an extra API route and state management:
```tsx
'use client'
import { useEffect, useState } from 'react'
export default function UserDashboard({ userId }: { userId: string }) {
const [user, setUser] = useState(null)
const [posts, setPosts] = useState([])
useEffect(() => {
fetch(`/api/users/${userId}`)
.then(r => r.json())
.then(data => {
setUser(data.user)
setPosts(data.posts)
})
}, [userId])
if (!user) return <div>Loading...</div>
return (
<div>
<h1>{user.name}'s Dashboard</h1>
<p>{posts.length} posts total</p>
</div>
)
}
```
Server Actions: Form Handling
React 19 introduced Server Actions, which let form submissions call server functions directly:
```tsx
// app/actions.ts
'use server'
import { db } from '@/lib/database'
import { revalidatePath } from 'next/cache'
export async function createPost(formData: FormData) {
const title = formData.get('title') as string
const content = formData.get('content') as string
await db.post.create({ data: { title, content } })
revalidatePath('/posts')
}
```
```tsx
// app/posts/new/page.tsx — Server Component
import { createPost } from './actions'
export default function NewPostPage() {
return (
<form action={createPost}>
<input name="title" placeholder="Post title" required />
<textarea name="content" placeholder="Content" />
<button type="submit">Publish</button>
</form>
)
}
```
No API routes, no fetch calls.
Part 3: Competitor Comparison
RSC isn’t the only “reduce client-side JS” solution.
RSC vs Astro
Core difference in approach:
– RSC: Renders some components on the server, preserves full React interactivity
– Astro: Zero JS by default, loads interactive components on demand (Islands architecture), works with React/Vue/Svelte/any framework
Decision guide:
| Scenario | RSC | Astro |
|---|---|---|
| Content sites (blogs/docs) | Works | Better fit |
| High-interactivity apps (dashboards/SaaS) | Better fit | Needs extra work |
| Multi-framework teams | React only | Any framework |
| Need full React ecosystem | Native support | Limited support |
Astro is more aggressive on content sites — it ships zero JS by default. A pure display blog page outputs only HTML+CSS, no JS at all. RSC can also achieve zero JS in this scenario, but the Next.js framework layer still carries some runtime overhead.
RSC vs Qwik
Qwik’s core idea is “resumability” — it doesn’t send any interaction code until the user triggers an interaction and only then downloads the relevant JS snippet.
Key differences:
– RSC: Data fetching and rendering happen on the server, Client Components ship full JS
– Qwik: All component JS is lazy-loaded to the extreme — clicking a button only downloads that button’s handler
Qwik is more aggressive on first-load bundle size, but it requires learning a completely new framework ($ prefix, serialization, symbol system), with a much smaller ecosystem than React. If your team already knows React, Qwik’s migration cost is high.
RSC vs Traditional SSR
Many React developers are already using SSR. What does RSC add?
– SSR still sends the full JS bundle for hydration
– RSC completely eliminates JS for non-interactive components
– SSR limits data fetching to fixed APIs like `getServerSideProps`, while RSC allows fetching at any component level
Data from developerway.com: on simple content pages like product pages, RSC reduced LCP from 2.1s (SSR) to 1.1s. But on interactive-heavy dashboard scenarios, RSC was actually slower than CSR — server-side rendering has time overhead too.
Decision Framework
“`
Is your app interaction-heavy?
├── Yes → RSC needs many Client Components
│ Consider: traditional CSR + code splitting
│
└── No → Mostly content display?
├── Yes → Using React?
│ ├── Yes → RSC is a solid choice
│ └── No → Consider Astro
└── No → Hybrid
RSC works, but plan your boundaries
“`
Summary
RSC is the biggest architectural shift in React since Hooks. It’s not an “optimization trick” — it’s a new way of thinking: let the server do what the server does best, and let the browser do what the browser does best.
In 2026, if you’re in the Next.js ecosystem building content-driven or boundary-clear applications, RSC is worth serious consideration. But if you need zero client-side JS in interaction-heavy apps, Astro or Qwik might be a better starting point.
The key isn’t whether RSC is good or not — it’s what your application is and who your team is.
📖 Recommended Reading
Take a look at these articles related to React. You might find them interesting
Building Desktop Apps with Electron: A Practical Guide to Integrating React and Vue
Introductory Analysis: Why Next.js 15 Became the Standard for Agentic AI Apps
React Compiler 1.0 Is Here: Can We Finally Delete useMemo and useCallback?
React 19 vs Vue 3.6: Two Radically Different Frontend Philosophies