Skip to main content
DailyKiUpdate
Technology

React Server Components Explained: What Actually Runs Where

Learn how React Server Components work in Next.js App Router, what runs on the server vs browser, how "use client" creates client boundaries, and how to optimize JavaScript bundles.

By Rajendra KumarUpdated November 3, 202611 min read254 views
React Server Components Explained: What Actually Runs Where

React Server Components Explained: What Actually Runs Where

If you've ever felt confused about which parts of a Next.js App Router application run on the server and which parts run in the browser, you're not alone.

React Server Components (RSC) introduced a different mental model for building React applications. It isn't simply a new API or another rendering technique. It changes how we think about the relationship between the server, browser, JavaScript bundles, data fetching, and component boundaries.

The simplest way to think about it is:

Your component tree can run in two environments: the server and the browser.

With the Next.js App Router, components are Server Components by default. You explicitly opt into browser-side execution when you need interactivity using "use client".

Let's break down what actually happens.


1. The Old Model: Everything Runs on the Client

Before React Server Components became common, a typical React application followed a simpler model.

You write a component:

import { useEffect, useState } from "react";

export default function Posts() {
  const [posts, setPosts] = useState([]);

  useEffect(() => {
    fetch("/api/posts")
      .then((res) => res.json())
      .then(setPosts);
  }, []);

  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

The component's JavaScript is sent to the browser.

The browser then:

  1. Downloads JavaScript.

  2. Executes React.

  3. Runs useEffect.

  4. Makes the API request.

  5. Receives the data.

  6. Updates state.

  7. Renders the posts.

This works well, but there is a cost.

Every component that needs to run in the browser contributes to the client-side JavaScript bundle.

For large applications, that can mean:

More JavaScript → more downloading → more parsing → more execution → more work for the browser.


2. The New Model: Server Components by Default

The Next.js App Router changes this model.

Consider:

// app/posts/page.tsx

export default async function PostsPage() {
  const response = await fetch("https://api.example.com/posts");
  const posts = await response.json();

  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

Notice something important:

There is no "use client".

Therefore, this is a Server Component.

The component can execute on the server and fetch its data before the UI is sent to the browser.

Conceptually:

Browser
   │
   │ Request /posts
   ▼
Next.js Server
   │
   ├── Execute PostsPage
   ├── Fetch posts
   ├── Render component
   │
   ▼
Response
   │
   ▼
Browser

The browser receives the rendered result, but it does not need the JavaScript implementation of PostsPage to render those posts interactively.


3. What Does "Runs on the Server" Actually Mean?

A Server Component can execute in a server environment.

That means you can do things such as:

export default async function Dashboard() {
  const users = await getUsers();

  return (
    <div>
      <h1>Users</h1>

      {users.map((user) => (
        <div key={user.id}>{user.name}</div>
      ))}
    </div>
  );
}

You can also access server-side resources that shouldn't be exposed to the browser.

For example:

const data = await db.user.findMany();

The database query can happen on the server.

You don't need to expose database credentials to the browser.

This is one of the important architectural benefits of Server Components.


4. Server Components Don't Mean "HTML Only"

This is a common misunderstanding.

A Server Component isn't simply an old-fashioned server-rendered HTML template.

React Server Components use a protocol that allows React to describe the component tree and communicate the result between the server and client.

The important distinction is:

Server Component
       │
       ├── Executes on server
       ├── Can access server resources
       ├── Can fetch data
       └── Doesn't become client JavaScript

While:

Client Component
       │
       ├── Executes in browser
       ├── Can use state
       ├── Can use effects
       └── Can respond to user interaction

5. Opting Into the Client

Now suppose you want a button that changes when the user clicks it.

You need browser-side interactivity.

That's where "use client" comes in.

"use client";

import { useState } from "react";

export function LikeButton() {
  const [liked, setLiked] = useState(false);

  return (
    <button onClick={() => setLiked(!liked)}>
      {liked ? "Liked" : "Like"}
    </button>
  );
}

The "use client" directive tells React/Next.js:

This module belongs to the client-side part of the application.

Now the component can use:

  • useState

  • useEffect

  • event handlers

  • browser APIs

  • client-side state

  • other client-only functionality

For example:

window.localStorage

or:

navigator.geolocation

These require a browser environment.


6. The Most Important Rule: The Client Boundary

This is probably the most important concept to understand.

When you write:

"use client";

you're creating a client boundary.

For example:

"use client";

import Header from "./Header";
import ProductList from "./ProductList";

The modules imported into this client module can become part of the client-side dependency graph.

Conceptually:

ClientComponent
      │
      ├── Header
      └── ProductList

If those components don't need to run on the client, unnecessarily importing them from the client boundary can increase the client bundle.

That's why you generally want to keep client boundaries small.


7. But Here's the Part That Confuses Everyone

A Server Component can be passed as children to a Client Component.

For example:

"use client";

export function Modal({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <div className="modal">
      {children}
    </div>
  );
}

Now your Server Component can do this:

import { Modal } from "./Modal";
import { ServerContent } from "./ServerContent";

export default function Page() {
  return (
    <Modal>
      <ServerContent />
    </Modal>
  );
}

At first glance, you might think:

"Modal is a Client Component, so ServerContent must now be a Client Component too."

Not necessarily.

The important distinction is how the component enters the client boundary.

When ServerContent is provided as children, the server can render that content and provide it to the Client Component.

Conceptually:

                 SERVER
                   │
          ┌────────┴────────┐
          │                 │
     ServerContent       Client Modal
          │                 │
          └───────┬─────────┘
                  │
                Browser

This pattern is extremely useful for keeping interactive components small.


8. Server Component vs Client Component

Here's a practical comparison:

Feature

Server Component

Client Component

Default in App Router

Yes

No

Runs on server

Yes

Can participate in server rendering, but is also shipped for client execution

Runs in browser

No

Yes

useState

No

Yes

useEffect

No

Yes

onClick

No

Yes

Browser APIs

No

Yes

Direct server data access

Yes

No

Database access

Yes

No

Client-side interactivity

No

Yes

Ships component JS to browser

Generally no

Yes

The key word is interactivity.

If a component doesn't need browser-side interaction, you should consider keeping it on the server.


9. Where Should Data Fetching Happen?

One of the biggest advantages of Server Components is that data fetching can happen close to where the data is actually needed.

For example:

export default async function ProductPage({
  params,
}: {
  params: Promise<{ id: string }>;
}) {
  const { id } = await params;

  const product = await getProduct(id);

  return (
    <div>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
    </div>
  );
}

You don't necessarily need:

useEffect(() => {
  fetch(...)
}, []);

for every piece of server-owned data.

Instead:

Request
   ↓
Server Component
   ↓
Fetch data
   ↓
Render UI
   ↓
Browser

This can simplify application architecture significantly.


10. Does "use client" Mean "This Only Renders in the Browser"?

No.

This is another common misconception.

A Client Component can still be rendered as part of the initial server-rendered response.

The important distinction is that its client-side JavaScript is also included so that it can become interactive in the browser.

For example:

"use client";

export default function SearchBox() {
  return <input placeholder="Search..." />;
}

The initial UI can appear in the server-rendered page, but the browser also receives the JavaScript necessary for the component's client-side behavior.

So don't think:

Server Component = HTML
Client Component = browser-only HTML

A better mental model is:

Server Component
    ↓
Server execution
    ↓
No client JS for that component

Client Component
    ↓
Server rendering can participate
    ↓
+ client JS for interactivity

11. Think in Terms of Islands of Interactivity

A useful mental model is to imagine your application as a large server-rendered structure with small interactive islands.

For example, consider a blog page:

Blog Page
│
├── Header                 → Server
├── Article                → Server
├── Author Information     → Server
├── Related Posts          → Server
│
├── Like Button             → Client
├── Comment Form            → Client
└── Share Button            → Client

Most of the page doesn't need JavaScript.

Only the interactive pieces do.

This is the architecture that Server Components encourage.


12. Example: E-Commerce Product Page

Imagine an e-commerce product page.

You might have:

Product Page
│
├── Product Details         → Server
├── Product Description     → Server
├── Reviews                 → Server
├── Related Products        → Server
│
├── Image Gallery           → Client
├── Quantity Selector       → Client
└── Add To Cart             → Client

The product information can be fetched and rendered on the server.

The quantity selector needs state.

The add-to-cart button needs interaction.

So there's no reason to turn the entire product page into a Client Component.

Instead, keep the interactive parts small.


13. What Happens to the JavaScript Bundle?

This is where Server Components can provide a major architectural advantage.

Imagine a page containing:

100 components

Suppose only 10 need browser-side interactivity.

With a traditional client-heavy architecture:

100 components
      ↓
Client JavaScript
      ↓
Browser

With Server Components:

100 components
      │
      ├── 90 Server Components
      │       ↓
      │    Server
      │
      └── 10 Client Components
              ↓
          Browser JS

The goal isn't to eliminate JavaScript.

The goal is to send only the JavaScript that the browser actually needs for interactivity.


14. Common Mistake: Putting "use client" Everywhere

You may see code like this:

"use client";

export default function ProductPage() {
  return (
    <div>
      <h1>Product</h1>
      <p>Description</p>
    </div>
  );
}

But if the component doesn't need:

  • state

  • effects

  • event handlers

  • browser APIs

  • client-side libraries

then it may not need to be a Client Component.

A better approach is to keep the page on the server:

export default async function ProductPage() {
  const product = await getProduct();

  return (
    <div>
      <h1>{product.name}</h1>
      <p>{product.description}</p>

      <AddToCartButton productId={product.id} />
    </div>
  );
}

And isolate the interactive part:

"use client";

export function AddToCartButton({
  productId,
}: {
  productId: string;
}) {
  function handleAddToCart() {
    // client-side interaction
  }

  return (
    <button onClick={handleAddToCart}>
      Add to Cart
    </button>
  );
}

Now the client boundary is much smaller.


15. Can a Client Component Import a Server Component?

This is where you need to be careful.

A Client Component should not simply import a Server Component module and expect that Server Component to continue being a server-only component.

For example, avoid thinking of this as a normal component import:

"use client";

import ServerComponent from "./ServerComponent";

Instead, a common pattern is to pass server-rendered content through props such as children:

<ClientWrapper>
  <ServerComponent />
</ClientWrapper>

This preserves the separation between the server-rendered content and the client component.


16. Server Components Are Not a Replacement for Client Components

You still need Client Components.

For example:

Server

export default async function Dashboard() {
  const data = await getDashboardData();

  return (
    <div>
      <DashboardStats data={data} />
      <Filter />
    </div>
  );
}

Client

"use client";

import { useState } from "react";

export function Filter() {
  const [filter, setFilter] = useState("all");

  return (
    <select
      value={filter}
      onChange={(e) => setFilter(e.target.value)}
    >
      <option value="all">All</option>
      <option value="active">Active</option>
      <option value="inactive">Inactive</option>
    </select>
  );
}

The two environments work together.

That's the important idea.


17. A Simple Mental Model

When deciding whether something should be a Server or Client Component, ask one question:

Does this component need to run in the browser?

If the answer is no, keep it on the server when practical.

If the answer is yes, use "use client".

Server Component

Use it for:

  • Data fetching

  • Database access

  • Static content

  • SEO-oriented content

  • Server-side computation

  • Accessing server-only resources

Client Component

Use it for:

  • useState

  • useEffect

  • Click handlers

  • Forms requiring client state

  • Browser APIs

  • Interactive UI

  • Client-side subscriptions


18. The Architecture to Remember

Think about a Next.js application like this:

                    NEXT.JS APPLICATION
                           │
                           ▼
                    Server Component Tree
                           │
             ┌─────────────┴─────────────┐
             │                           │
       Server Components          Client Components
             │                           │
       Data fetching                 Interactivity
       DB access                     useState
       Static UI                     useEffect
       SEO content                   Events
             │                           │
             │                           ▼
             │                    Browser JavaScript
             │
             ▼
       Server-rendered result

The component tree isn't simply:

Server OR Client

It's:

Server
  +
Client
  +
Clear boundaries

19. The Biggest Mental Shift

The biggest change with React Server Components is this:

Old mental model

"React components run in the browser unless I do something special."

New mental model

"Components are server-first, and I explicitly introduce client-side JavaScript where interaction requires it."

That is the fundamental shift.

Instead of asking:

"How do I make this component work on the server?"

start asking:

"Does this component actually need to run on the client?"

That question will often lead to a simpler architecture.


20. Final Takeaway

React Server Components aren't about completely eliminating client-side React.

They're about moving as much work as possible away from the browser while keeping interactive parts where they belong.

A good Next.js App Router application often looks like:

                    PAGE
                     │
       ┌─────────────┴─────────────┐
       │                           │
   SERVER                         CLIENT
   COMPONENTS                     COMPONENTS
       │                           │
   Data fetching                 useState
   Database                      useEffect
   SEO                           Events
   Static UI                     Browser APIs
       │                           │
       └─────────────┬─────────────┘
                     │
                  Browser

The goal isn't:

"Use Server Components everywhere."

The goal is:

"Keep the server-rendered tree as large as practical, and push client-side interactivity toward the leaves."

Once you understand that boundary, the Next.js App Router becomes much easier to reason about.

Server Components handle the data and structure. Client Components handle the interaction.

That's the mental model worth remembering.

Share this article

Comments