Before we dive into Server Components, let’s take a moment to understand where they fit in the broader evolution of web development.
In traditional web apps, each user interaction (like clicking a link) triggered a full page reload. This MPA model was simple but slow and jarring—especially as web applications became more dynamic.
Then came SPAs, powered by frameworks like React, Angular, and Vue. These apps load a single HTML shell and dynamically update the DOM with JavaScript. The result? Smooth, app-like experiences in the browser.
However, SPAs introduced new challenges—especially in SEO, performance, and initial page load times. As apps grew, so did their JavaScript bundles, causing longer time-to-interactive and reduced performance on low-end devices or poor networks.
Rendering Strategies in SPAs
Developers adopted different rendering techniques to balance performance, user experience, and SEO. The three most common strategies are:
Despite these rendering strategies, developers often find themselves making trade-offs between performance, SEO, and developer experience. While SSR and SSG address some issues, they still rely heavily on client-side JavaScript to hydrate and run the app. As applications continue to scale, the need for a more efficient, flexible solution has become clear. This is where React Server Components enter the picture.
React Server Components bring a hybrid, component-level rendering model. You decide which components should run on the server, without sending any JavaScript to the client, and which should remain interactive and run on the client.
This enables you to:
Here are some standout benefits:
React doesn't stream HTML. It streams a custom serialization format (called Flight).
Let's take a real-world example with Server and Client component setup.
// components/UserInfo.server.tsx
// Server Component – rendered only on the server
export default async function UserInfo({ userId }) {
const user = await getUserFromDatabase(userId);
return <div>{user.name}</div>;
}
// components/EditUserForm.client.tsx
// Client Component – interactive
'use client';
import { useState } from 'react';
export default function EditUserForm({ userId }) {
const [name, setName] = useState('');
const handleSubmit = () => {
// send updated name to server
};
return (
<form onSubmit={handleSubmit}>
<input value={name} onChange={(e) => setName(e.target.value)} />
<button type="submit">Save</button>
</form>
);
}
// app/page.tsx (React Server Component Tree Root)
import UserInfo from '@/components/UserInfo.server';
import EditUserForm from '@/components/EditUserForm.client';
export default function Page({ params }) {
return (
<>
<UserInfo userId={params.id} /> {/* rendered and streamed as HTML */}
<EditUserForm userId={params.id} /> {/* reference sent for hydration */}
</>
);
}
In this case, conceptually React sends the following payload via Flight.
[
["<div>John Doe</div>"],
["EditUserForm", { userId: "123" }]
]
It might send plain HTML output for parts of the UI like <div>John Doe</div>, which can be inserted directly into the DOM without needing JavaScript. For interactive parts — like a form that requires state or event handling — the server includes a reference to a Client Component (e.g., EditUserForm) along with its props (e.g., { userId: "123" }). The client then takes this serialized data, inserts the static content into the page, and hydrates only the Client Components, loading the necessary JavaScript to make them interactive. This approach results in faster load times and smaller JavaScript bundles, as only the interactive parts of the app need to be shipped and executed in the browser.
While React Server Components sound very similar to traditional server-side rendering, they follow a fundamentally different model. Let’s take a closer look at how RSC compares to SSR—and why that difference matters.
Although both approaches render content on the server , they solve different problems and operate in fundamentally different ways, here’s a quick comparison:
Now that we understand how React Server Components differ from traditional SSR and why they matter, let’s look at how you can start using them.
The easiest way to get started is with Next.js 13+ and the App Router, which provides built-in support for RSC, enabling you to mix Server and Client Components seamlessly within your app. This setup not only simplifies routing and layouts but also gives you fine-grained control over what runs on the server and what ships to the client.
If we take the same example we saw earlier, this would be its folder structure.
app/
├── page.tsx
components/
├── UserInfo.server.tsx // Server Component (static)
└── EditUserForm.client.tsx // Client Component (interactive)
Key Concepts:
Use Server Components for:
Use Client Components for:
React Server Components are a powerful leap forward in web architecture. They offer a way to:
They’re not a silver bullet—but when used correctly, RSC can dramatically improve both developer experience and user experience.
As React continues to unify the strengths of SSR, SSG, and CSR, Server Components will likely become a key pillar in future production apps.