Sajiron
Published on Feb 10, 2025React Server Components (RSCs) are changing how we think about rendering in React. Understanding and implementing them is crucial. Instead of running everything on the client, they shift some of the workload to the server, reducing JavaScript sent to the browser and making applications faster and more efficient.
But RSCs aren’t the only piece of the puzzle. They collaborate with Server-Side Rendering (SSR), Static Site Generation (SSG), and Suspense Streaming to create a better web experience. Understanding how these techniques work together will help you make smarter architectural decisions while building modern React apps, and be part of the exciting evolution of web development.
In this guide, we’ll break down what RSCs are, how they work, and when you should use them. We’ll also examine their strengths, limitations, and comparisons to traditional rendering techniques, giving you a comprehensive understanding of RSCs.
Let’s dive in. 👇
React Server Components (RSCs) are special React components that execute only on the server. Unlike traditional Client Components, RSCs don’t send their component logic as JavaScript to the browser, making them highly efficient for rendering both static and dynamic content.
Traditionally, single-page applications (SPAs) built with React send an almost empty HTML file to the browser, containing just a <div> placeholder. Alongside this, a large JavaScript bundle is loaded, which includes the React library, application logic, and third-party dependencies. Once the JavaScript is downloaded and executed, the application fetches data, computes the UI, and renders the necessary HTML dynamically on the client. While this approach enables highly interactive applications, it can lead to performance issues due to the heavy reliance on client-side processing. It can negatively impact SEO by delaying content visibility to search engine crawlers.
On the other hand, React Server Components (RSCs) take a different approach by moving some of this processing to the server. Instead of the browser fetching data and constructing the UI, RSCs execute on the server, retrieving necessary data and sending React elements in a lightweight format (like JSON) to the client, which then renders them without extra JavaScript for hydration. This approach eliminates the need for JavaScript to handle initial rendering and offers several key advantages for your application:
No JavaScript is sent for the RSC itself.
The output is purely HTML and CSS, significantly reducing page load times.
They can fetch data directly from databases or external APIs without an intermediary REST or GraphQL API.
Improves SEO by ensuring that content is immediately available to search engine crawlers
In SSR, React renders components into full HTML, which is then sent to the client, similar to how static HTML pages are delivered. Once the client receives the page, React runs again in the browser to add interactivity, a process known as hydration.
Hydration is the process where React attaches event listeners and reactivates interactivity on a server-rendered HTML page in the browser.
How Hydration Works:
In SSR, the server sends a fully formed HTML page to the browser. Once the JavaScript bundle loads, React attaches event listeners and states to the pre-rendered HTML, making the UI interactive. However, if the client-side React state differs from the server-rendered state, React may re-render components unnecessarily, leading to UI mismatches.
React Server Components (RSCs) execute on the server and send lightweight React elements (similar to JSON) to the client for rendering instead of sending the fully formed HTML like SSR. This reduces JavaScript execution on the client and eliminates the need for hydration.
Key Differences:
Unlike SSR, it does not generate full HTML but instead sends React elements in a lightweight format (like JSON) to the client.
Since RSCs don’t generate full HTML like SSR, React does not need to hydrate them on the client.
RSCs avoid sending component logic as JavaScript, whereas SSR still requires JavaScript execution for hydration.
Example: Fetching Data with a Server Component
// app/posts/page.tsx
async function getPosts() {
const res = await fetch("https://jsonplaceholder.typicode.com/posts");
return res.json();
}
export default async function PostsPage() {
const posts = await getPosts();
return (
<div>
<h1>📢 Latest Posts</h1>
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</div>
);
}
✅ What’s happening?
getPosts()
runs on the server, preventing unnecessary client-side API calls.
No need for client-side fetching libraries like SWR
or React Query
.
The HTML response is pre-rendered, making page loads significantly faster.
React Server Components introduce Server Actions, a new way to send data from the client to the server without an API. This enables:
Direct database mutations inside components
Seamless RPC-like interactions between client and server
Example: Server Action in Next.js
'use server';
export async function savePost(post) {
await db.insert(post);
}
✅ No need for API routes—call savePost()
directly in a Client Component.
Since Server Components can’t use state or lifecycle hooks, you’ll sometimes need Client Components.
Example: Combining Server & Client Components
// Server Component
import Counter from "./Counter"; // Client Component
export default async function HomePage() {
return (
<div>
<h1>Welcome to RSC</h1>
<Counter /> {/* Client Component */}
</div>
);
}
"use client"; // Client Component
import { useState } from "react";
export default function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Clicked {count} times
</button>
);
}
If you ever encounter a scenario where you need to import a Server Component inside a Client Component, be aware that the Server Component will automatically be treated as a Client Component. This means the Server Component’s logic will run on the client.
Example: Incorrect Usage
"use client";
import ServerComponent from "./ServerComponent";
export default function ClientComponent() {
return (
<div>
<h1>Client Component</h1>
<ServerComponent /> {/* This will now behave as a Client Component */}
</div>
);
}
Solution: Pass server component as a child
// Client Component
"use client";
export default function ClientComponent({ children }) {
return (
<div>
<h1>Client Component</h1>
{children}
</div>
);
}
// App Server Component
export default async function App() {
return (
<ClientComponent>
<ServerComponent />
</ClientComponent>
);
}
useState
or useEffect
?No. RSCs run only on the server, so they cannot hold state or use lifecycle methods. If interactivity is required, use a Client Component.
Not entirely. While RSCs can fetch data directly, you may still need API routes for third-party integrations, authentication, or complex logic.
Next.js middleware runs before RSCs, allowing pre-processing like authentication before loading the page.
React Server Components (RSCs) mark a significant evolution in React development, shifting computation to the server for faster, more efficient applications. They don’t replace Client Components but serve as a powerful tool for optimizing React apps.
As React evolves, features like Server Actions, enhanced Suspense streaming, and improved bundler optimizations are reshaping how we build web applications. The goal is a seamless development experience where React balances performance and interactivity with minimal client-side overhead.
RSCs are just the beginning. With continuous advancements in the React ecosystem, developers will gain an even richer toolkit for building scalable, high-performance applications with reduced complexity.