RSC(React Server Components) Guide — How They Work, Code Examples, and Framework Comparison

by JeariCk 8 min read
RSC(react server components)

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.

RSC(react server components)
RSC(react server components)

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:

/CSRSSRRSC
Render locationBrowserServer → BrowserServer (partial)
JS shipped to browserAllAll (requires hydration)Only Client Components
Data fetchingClient-side APIServer can pre-fetchDirect DB/FS access
InteractivityFullFull (after hydration)Not supported
Bundle size impactFull bundleFull bundleInteractive 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:

ScenarioRSCAstro
Content sites (blogs/docs)WorksBetter fit
High-interactivity apps (dashboards/SaaS)Better fitNeeds extra work
Multi-framework teamsReact onlyAny framework
Need full React ecosystemNative supportLimited 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

Leave a Reply

Your email address will not be published. Required fields are marked *