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
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?
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.
Front-End Developer with 3+ years of experience building modern, responsive websites and web applications, focused on React, Next.js, performance, and user experience.
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:
The component can be asynchronous.
Data is retrieved directly from the database.
There is no need for useEffect to perform the initial data fetching.
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.
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.
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.
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.
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.
RSC is not a magic solution that makes every React application faster.
Problem
Role of RSC
Sending unnecessary code to the browser
Can reduce client JavaScript
Fetching data directly on the server
Makes it possible
Accessing secrets
Keeps sensitive logic in the server environment
Initial content display
Can work alongside SSR
User interaction
Still requires Client Components
Client state management
Does not replace state management tools
Real-time data
Requires additional solutions
Authorization
Must 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.
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.
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.