7 min read • Aug 11, 2026
Optimizing React applications with Server Components
How SSR and RSC work, how they differ, and how they can be used together to improve initial loading performance, reduce the amount of JavaScript sent to the browser, and keep applications responsive.

Server-side rendering and React Server Components offer powerful ways to improve application performance by moving work from the browser to the server. In this article, we’ll explore how SSR and RSC work, how they differ, and how they can be used together to improve initial loading performance, reduce the amount of JavaScript sent to the browser, and keep applications responsive.
We’ll also look at the architectural decisions that help us get the most out of Server Components, such as keeping client boundaries small and passing only the necessary data to the client. While Server Components provide performance benefits out of the box, using them effectively requires structuring the application correctly.
What is Client-Side Rendering(CSR)?
Client-Side Rendering is a technique where the browser renders the UI by executing JavaScript. In a typical React application, when a user requests the webpage, the server sends a minimal HTML document along with the JavaScript bundle. The browser downloads, parses, and executes the JavaScript, which then generates the page’s content dynamically and updates the DOM. Downloading and executing JavaScript files affect the initial render time.
What is Server-Side Rendering (SSR)?
Server-side rendering (SSR) is a rendering technique where React generates the initial HTML for a page on the server. When a user requests a page, the server renders the React application and sends fully populated HTML to the browser. The browser can display this content immediately, then download the JavaScript bundle and hydrate the page.
Hydration
Hydration is the process of attaching event listeners and other JavaScript functionality to the static DOM nodes built with HTML received from the server.
Trade-offs of the server-side rendering
Although in SSR the HTML is generated on the server, components’ code still needs to be sent to the browser for execution. This means that the user views the UI almost immediately, but additional time is required to make the page interactive.
What are React Server Components (RSC)?
React addresses this problem with Server Components. React Server Components are components that are executed on the server instead of the browser and thus don’t require including their JavaScript in the bundle.
Instead of sending JavaScript to the browser, the server executes them and generates a special data format called the React Server Component Payload (RSC Payload). The RSC payload is sent to the browser, where React uses it to construct and update the UI.
This approach reduces the amount of JavaScript the browser needs to download, parse, and execute.
What is the React Server Component Payload (RSC)?
The RSC Payload is a compact representation of the Server Component tree and the information React needs to integrate Server and Client Components.
It contains information such as:
- The rendered output of Server Components
- References to Client Components and the JavaScript needed to render them
- Props passed from Server Components to Client Components
React on the client processes the payload and uses it to construct or update the UI.
Limitations of the server components
Unlike Client Components, Server Components cannot use browser-only APIs. They are designed for rendering static or data-driven UI. When browser-side interactivity is needed, client-side components can be used alongside them. If the payload includes the client-side components, they are hydrated in the browser to add interactivity where needed.
What are client components?
Client Components are regular React components that run in the browser. Because they execute in the browser, they have access to the browser environment, which allows them to respond to user interactions using event handlers such as onClick, manage state with hooks like useState, perform side effects with useEffect, and use browser APIs such as window, document, and localStorage.
Using client components
In Next.js, all components are Server Components by default. To make a component a Client Component, we add the 'use client' directive at the top of the file.
The important thing to understand is that 'use client' creates a client boundary. The component becomes a Client Component, and all the components it imports can also become part of the client bundle.
Note: This means we can’t import server components inside client components directly.
Next.js still renders ‘use client’ components on the server
Server-side rendering still applies to React components that have the ‘use client’ directive. That means we can’t access browser APIs such as window, document, etc. To access window, for example, you have to use useEffect, or, in extreme situations, you can use dynamic imports to make the component render only in the browser.
Optimization techniques
The main idea behind optimizing an application with Server Components is to move as much work as possible to the server and send only the JavaScript that the browser actually needs. It’s important to note that server components passed as children to client components won’t be included in the client boundary.
We can change the architecture of our components by separating parts that require data from the server or interactivity from the parts that don’t. And use the component composition pattern to keep client components isolated.
Nesting client components as deeply as possible
The most straightforward way to avoid creating unnecessary Client Components is to place the 'use client' directive as deeply in the component tree as possible. This allows the parent components to remain on the server while only the component that actually needs browser-side functionality runs on the client.
For example, if only the search input needs to respond to user input:
// SearchPage.jsx — Server Component
export default function SearchPage() {
return (
<main>
<h1>Products</h1>
<ProductList />
<SearchInput />
</main>
)
}// SearchInput.jsx — Client Component
'use client'
import { useState } from 'react'
export default function SearchInput() {
const [query, setQuery] = useState('')
return (
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search products"
/>
)
}Keeping client boundaries small
For example, if we have a page that displays a product list, we can make the product component client-side rather than making the whole page client-side. We can go even further and only make the add to cart button client-side, since other parts of the component won't need interactivity:
// Product.jsx — Server Component
import AddToCartButton from './AddToCartButton'
export default function Product({ product }) {
return (
<div>
<h2>{product.name}</h2>
<p>${product.price}</p>
{/* Only this part needs interactivity */}
<AddToCartButton productId={product.id} />
</div>
)
}Passing only the necessary data to the client
We can reduce the amount of data passed across the server/client boundary by passing only the necessary data to the client. For example, instead of passing the entire object, we can send only the required fields:
// Instead of passing the entire object
<LikeButton product={product} />
// Pass only the necessary data
<LikeButton
productId={product.id}
likeCount={product.likeCount}
/>Fetching data on the server
Instead of fetching data in a Client Component using useEffect, we can fetch it directly inside a Server Component. This allows the server to retrieve the data before rendering the component, so the browser doesn't need to make additional requests. We can then pass the fetched data to other components as props.
export default async function Products() {
const products = await db.products.findMany()
return <ProductList products={products} />
}Summary
React Server Components provide several performance benefits out of the box. Since Server Components execute on the server, their JavaScript doesn't need to be sent to the browser, which can reduce the client-side bundle size and the amount of JavaScript the browser needs to download and execute.
However, simply using Server Components doesn't automatically guarantee an optimized application. We still need to use them correctly and make good architectural decisions. This includes keeping client boundaries as small as possible, placing interactivity only where it is needed, passing only the necessary data to Client Components, and using component composition effectively.
The main idea is to keep as much work as possible on the server while sending only what the browser actually needs. Good component architecture allows us to take full advantage of the benefits that Server Components provide.