Back to blog
October 2026

Inside React Server Components: From Server Execution to Browser Interactivity

What happens when a React page loads? Discover how React Server Components divide execution between the server and browser, how RSC Payload works, how data crosses the network boundary, and how to design practical Server and Client Componen

  • React
  • RSC
  • React Architecture
  • Next.js
  • Rendering
  • Frontend
  • Web Performance
Inside React Server Components: From Server Execution to Browser Interactivity

Inside React Server Components: From Server Execution to Browser Interactivity

Imagine a user opening a product details page. The application needs to display the product name, image, price, and description. It also needs to retrieve inventory from a database, handle an Add to Cart button, and respond to changes in the selected quantity.

Here is an important architectural question:

Do all these responsibilities really need to execute in the browser?

React Server Components (RSC) offer a different answer. Instead of depending on a single execution environment for the entire interface, we can distribute responsibilities between the server and the browser.

But how does this separation actually work? What executes on the server? What reaches the browser? And how does React combine both environments into one interface?

Let's follow the lifecycle of a real request.

1. Two Environments, Two Sets of Capabilities

The browser and the server are different execution environments.

The browser provides capabilities such as window, localStorage, the DOM, event handlers, and direct user interaction.

The server can access databases, filesystem resources, private environment variables, and internal services.

Problems arise when we try to place every responsibility in a single environment.

For example, if a page only displays product information, why should the browser receive database connection code or libraries used exclusively for server-side data processing?

On the other hand, when a user clicks an Add to Cart button, that interaction requires client-side logic.

RSC separates these responsibilities into two composable component types:

  • Server Components: Components that execute and render in a server environment.
  • Client Components: Components that require browser capabilities and user interaction.

This separation can be understood as a Network Boundary within the component tree: a boundary that determines where different parts of the application execute.

2. What Happens When a User Opens a Page?

Imagine a user navigating to:

/products/keyboard
ShareShare on XShare on Telegram

Last updated October 2026

A

Amir Esmaeilzadeh

Front-end Developer

Front-End Developer with 3+ years of experience building modern, responsive websites and web applications, focused on React, Next.js, performance, and user experience.

NextReact Compiler Explained: Do We Still Need useMemo and useCallback?

Related posts

  • React Compiler Explained: Do We Still Need useMemo and useCallback?October 2026
  • TanStack Redact: A Smaller Runtime for React ApplicationsOctober 2026
فا

The page needs the following information and functionality:

  • Product details from a database
  • Seller information
  • Price and inventory
  • An Add to Cart button
  • A product quantity selector

With RSC, these responsibilities do not necessarily need to execute in the browser.

A reasonable component architecture could look like this:

ProductPage (Server)
│
├── ProductInformation (Server)
│
├── SellerInformation (Server)
│
├── ProductPrice (Server)
│
├── QuantitySelector (Client)
│
└── AddToCartButton (Client)

In this structure, product information is retrieved on the server, while quantity selection and cart interactions are handled in the browser.

This is more than a naming convention. It affects where code executes, how much JavaScript is sent to the browser, and how data is retrieved.

3. Where Does a Server Component Actually Execute?

A Server Component is a type of React component that renders ahead of time in an environment separate from the client application.

This environment can be:

  • A CI server during the build process.
  • A web server handling individual requests.

Therefore, a Server Component does not necessarily execute again on every request. Its execution depends on the framework architecture and how its data is used.

Example:

import { db } from "@/lib/db";

type Props = {
  productId: string;
};

export default async function ProductInformation({
  productId,
}: Props) {
  const product = await db.product.findUnique({
    where: {
      id: productId,
    },
    select: {
      name: true,
      description: true,
      price: true,
    },
  });

  if (!product) {
    return <p>Product not found.</p>;
  }

  return (
    <section>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
      <strong>${product.price}</strong>
    </section>
  );
}

There are several important details here:

  1. The component can be asynchronous.
  2. Data is retrieved directly from the database.
  3. There is no need for useEffect to perform the initial data fetching.
  4. The database connection code is not part of the Client Bundle.

One important distinction: RSC does not provide a database, ORM, or backend by itself. It provides a component execution model. You still need to implement the infrastructure responsible for accessing your data.

4. What Does the Browser Actually Receive?

This brings us to one of the central concepts of RSC: RSC Payload.

After Server Components execute, React prepares their results in a specialized data format.

This output is not simply ordinary HTML.

The RSC Payload provides information that allows the client to reconcile the rendered component tree, including:

  • The rendered output of Server Components.
  • The locations of Client Components.
  • References to Client Component JavaScript.
  • Props required by Client Components.

In simple terms, the RSC Payload tells React which parts of the interface have already been prepared on the server and which parts need client-side functionality.

Three Different Outputs

ConceptResponsibility
HTMLInitial visual content displayed by the browser
RSC PayloadComponent-tree information used by React
Client JavaScriptExecutes browser-side interactions and functionality

These three should not be treated as interchangeable formats.

5. First Load: From Server Response to Hydration

In the Next.js App Router, the initial page load involves several stages.

User Request
     |
     v
Next.js Server
     |
     v
Render Server Components
     |
     v
Generate RSC Payload
     |
     v
Pre-render HTML
     |
     v
Browser Receives Response
     |
     +------> Display HTML
     |
     +------> Reconcile RSC Tree
     |
     +------> Load Client JavaScript
                    |
                    v
                 Hydration
                    |
                    v
              Interactive UI

Let's break down the process.

Step 1: Execute Server Components

The server executes the components belonging to the Server environment and retrieves the required data.

Step 2: Generate RSC Payload

React prepares information about the rendered tree and the locations of Client Components.

Step 3: Generate Initial HTML

In Next.js, initial HTML is generated by combining the output of Server Components with the pre-rendered output of Client Components.

Step 4: Display the Page

The browser can display the HTML before client-side interactions are ready.

Step 5: Hydration

JavaScript for Client Components is downloaded and executed. React attaches event handlers to the existing DOM.

The page transitions from an initially visible result into an interactive user interface.

An important detail: during the initial Next.js page load, Client Components can also be rendered on the server to produce HTML. Therefore, Client Component does not mean browser-only execution.

6. use client: Where the Architecture Boundary Changes

Consider the following product page:

import ProductInformation from "./product-information";
import AddToCartButton from "./add-to-cart-button";

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

  return (
    <main>
      <ProductInformation product={product} />

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

In the Next.js App Router, this page is a Server Component by default.

However, the button requires user interaction:

"use client";

import { useState } from "react";

type Props = {
  productId: string;
};

export default function AddToCartButton({
  productId,
}: Props) {
  const [added, setAdded] = useState(false);

  return (
    <button onClick={() => setAdded(true)}>
      {added ? "Added to cart" : "Add to cart"}
    </button>
  );
}

The "use client" directive declares a boundary in the Client Module Graph.

From that point, imports and client-side dependencies of the module become part of the client module graph.

This is why you do not need to add "use client" to every file that belongs to the same client-side dependency graph.

A Common Architectural Mistake

Consider this structure:

Dashboard (Client)
│
├── Header
├── Sidebar
├── Statistics
├── Chart
├── Search
└── Filters

If we turn the entire Dashboard into a Client Component, non-interactive sections also become part of the Client Module Graph.

A more intentional architecture:

Dashboard (Server)
│
├── Header (Server)
├── Sidebar (Server)
├── Statistics (Server)
├── Chart (Client)
├── Search (Client)
└── Filters (Client)

The goal is not to eliminate Client Components. The goal is to introduce client boundaries only where they are actually needed.

7. An Important Rule: Don't Directly Import Server Components into Client Modules

Imagine that we have a client-side Modal and want to display database-driven information inside it.

An incorrect approach:

"use client";

import CartContent from "./cart-content";

export default function CartModal() {
  return (
    <div>
      <CartContent />
    </div>
  );
}

If CartContent is a Server Component, importing it from a Client Module does not preserve its server execution.

The correct solution is component composition.

The Modal:

"use client";

import { useState } from "react";

type Props = {
  children: React.ReactNode;
};

export default function CartModal({ children }: Props) {
  const [open, setOpen] = useState(false);

  return (
    <>
      <button onClick={() => setOpen(true)}>
        Open cart
      </button>

      {open && (
        <section>
          {children}

          <button onClick={() => setOpen(false)}>
            Close
          </button>
        </section>
      )}
    </>
  );
}

The cart component:

import { db } from "@/lib/db";

export default async function CartContent() {
  const items = await db.cartItem.findMany();

  return (
    <ul>
      {items.map((item) => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
}

The parent page:

import CartModal from "./cart-modal";
import CartContent from "./cart-content";

export default function Page() {
  return (
    <CartModal>
      <CartContent />
    </CartModal>
  );
}

Here, the Server Component is rendered by the Server parent, and its result is passed to the Client Component through children.

This is one of the most useful composition patterns in RSC architecture.

8. Props Cross the Server Boundary, But Not Every Value Can

When a Server Component passes information to a Client Component, you must follow React's serialization rules.

A simple example:

<ProductActions
  product={{
    id: product.id,
    name: product.name,
    price: product.price,
  }}
/>

This kind of data is suitable for transfer.

However, you should not assume that every JavaScript object can cross the Server and Client boundary without restrictions.

For example, passing a database connection, arbitrary class instance, or server resource to a Client Component is not an appropriate design.

Only transfer the data that the user interface actually needs.

Design a Better Data Contract

Instead of passing an entire database record:

<ClientProduct product={databaseProduct} />

Define a limited and explicit view model:

type ProductViewModel = {
  id: string;
  name: string;
  price: number;
  available: boolean;
};

const productViewModel: ProductViewModel = {
  id: product.id,
  name: product.name,
  price: product.price,
  available: product.stock > 0,
};

This gives you better control over the transferred data and reduces the UI's dependency on your internal database structure.

9. Streaming: Not Everything Needs to Be Ready at Once

A real-world page may retrieve its main information quickly while secondary sections take longer.

For example:

  • Product information: fast
  • Seller information: fast
  • Customer reviews: slower
  • Related products: slower

Do we have to wait for all of them before displaying anything?

Not necessarily.

With Streaming and Suspense, ready sections can be displayed earlier while slower sections continue loading.

import { Suspense } from "react";

import ProductDetails from "./product-details";
import ProductReviews from "./product-reviews";
import RelatedProducts from "./related-products";

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

  return (
    <main>
      <ProductDetails product={product} />

      <Suspense fallback={<p>Loading reviews...</p>}>
        <ProductReviews productId={product.id} />
      </Suspense>

      <Suspense fallback={<p>Loading recommendations...</p>}>
        <RelatedProducts productId={product.id} />
      </Suspense>
    </main>
  );
}

In this structure, independent sections can become ready at different times.

However, effective Streaming requires careful placement of Suspense boundaries, an understanding of data dependencies, and framework support. Adding Suspense alone does not guarantee better performance.

10. One Subtle Distinction: RSC Is Not the Same as Server Functions

In React projects, you may encounter these two directives:

"use client";

And:

"use server";

They do not perform the same job.

"use client" declares a Client Module Graph boundary.

"use server" is used to define a Server Function: a function that can be called from the client but executes on the server.

For example:

"use server";

import { db } from "@/lib/db";

export async function addToCart(productId: string) {
  // Authenticate the user.
  // Validate the product.
  // Check permissions.
  // Update the database.

  await db.cartItem.create({
    data: {
      productId,
    },
  });
}

This function can be called from a Client Component.

But there is an important security consideration: you should never treat a Server Function as an inaccessible internal function. Its incoming arguments must be validated and authorized like input to a server endpoint.

Also, "use server" does not create a Server Component.

11. What Does RSC Solve, and What Doesn't It Solve?

RSC is not a magic solution that makes every React application faster.

ProblemRole of RSC
Sending unnecessary code to the browserCan reduce client JavaScript
Fetching data directly on the serverMakes it possible
Accessing secretsKeeps sensitive logic in the server environment
Initial content displayCan work alongside SSR
User interactionStill requires Client Components
Client state managementDoes not replace state management tools
Real-time dataRequires additional solutions
AuthorizationMust be implemented explicitly

A Server Component can reduce client-side JavaScript, but the actual benefit depends on application structure, dependencies, and the number of Client Components.

Moving work to the server can also introduce network costs, server load, or data-fetching latency. Always measure actual application performance.

12. Where Should You Start in Next.js?

In the Next.js App Router, page.tsx and layout.tsx are Server Components by default.

A simple project structure:

app/
└── products/
    ├── page.tsx
    └── components/
        ├── product-details.tsx
        ├── product-reviews.tsx
        ├── quantity-selector.tsx
        └── add-to-cart-button.tsx

lib/
├── db.ts
└── products.ts

Example page:

import { getProduct } from "@/lib/products";

import ProductDetails from "./components/product-details";
import QuantitySelector from "./components/quantity-selector";
import AddToCartButton from "./components/add-to-cart-button";

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

  return (
    <main>
      <ProductDetails product={product} />

      <QuantitySelector />

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

In this example:

  • The page is a Server Component.
  • Product information is retrieved on the server.
  • Quantity selection is a Client Component.
  • Add to Cart is a Client Component.
  • Only the required data is passed to the client.

For implementation details and version-specific behavior, always consult the documentation for your installed Next.js version. Caching and data-fetching behavior can change between framework versions.

13. An Architecture Checklist for Real Projects

Before marking a file with "use client", ask yourself:

  • Does this component actually need state?
  • Does it contain event handlers?
  • Does it use browser APIs?
  • Can it remain on the server?
  • Does only a small child component need interactivity?
  • Are the props passed to the client really necessary?
  • Is sensitive or unnecessary data crossing the boundary?
  • Would this section benefit from Streaming?

If only a small part of a component needs interaction, make that part a Client Component instead of turning the entire page into one.

Final Thought: Design the Server and Client Boundary Intentionally

React Server Components are not simply a new way to write components. They influence application architecture, data flow, code boundaries, and how JavaScript reaches the browser.

To use RSC effectively, ask a more useful question than "Is this component a Server or Client Component?"

Which environment does this part of the application actually need, and what data really needs to cross the network boundary?

That change in perspective is the foundation of better architecture in modern React applications.


Technical References

  • React — Server Components
  • React — Directives
  • React — Server Functions
  • Next.js — Server and Client Components
  • Next.js — App Router

This article focuses on React architecture and its practical implementation with the Next.js App Router.