Modern React development has changed dramatically over the last few years. For a long time, most React applications followed the same basic rendering model:
That approach made highly dynamic applications possible, but it also introduced a growing problem across the frontend ecosystem: too much JavaScript.
As React applications became larger and more complex, websites started shipping massive client-side bundles even when pages contained mostly static content. React Server Components were introduced to help solve that problem.
Today, frameworks like Next.js use Server Components extensively to improve performance, reduce hydration costs, and lower the amount of JavaScript sent to the browser.
Understanding how they work is becoming an increasingly important skill for frontend developers.
"use client" directiveReact Server Components are React components that render entirely on the server instead of the browser.
Unlike traditional React components, Server Components do not send their JavaScript to the client. Instead, the server renders the component output and streams the result to the browser.
This means users receive the UI without downloading unnecessary JavaScript for that portion of the application.
A simple example of a Server Component in Next.js looks like this:
async function BlogPosts() {
const posts = await fetch("https://api.example.com/posts").then(res =>
res.json()
);
return (
<div>
{posts.map(post => (
<article key={post.id}>
<h2>{post.title}</h2>
</article>
))}
</div>
);
}
export default BlogPosts;
One thing that immediately stands out is that the component itself is asynchronous.
Traditional client-rendered React components cannot directly await data during rendering like this. Server Components can because they execute entirely on the server before anything reaches the browser.
This creates a much simpler rendering model for many types of applications.
To understand why Server Components matter, it helps to understand how React applications traditionally worked.
For years, React heavily relied on client-side rendering. The server would return a mostly empty HTML document containing a root element and a JavaScript bundle.
A minimal example might look like this:
<!DOCTYPE html>
<html>
<head>
<title>React App</title>
</head>
<body>
<div id="root"></div>
<script src="/bundle.js"></script>
</body>
</html>
After the browser downloaded the JavaScript bundle, React would generate the application interface entirely on the client side.
This architecture became popular because it enabled highly interactive user experiences. Navigation felt fast, interfaces became dynamic, and developers could build sophisticated applications directly in the browser.
However, as applications grew larger, several performance problems became increasingly common:
Many websites started shipping hundreds of kilobytes of JavaScript simply to display mostly static content.
React Server Components are part of React’s broader shift toward a more server-first architecture.
One of the biggest frontend performance problems today is unnecessary JavaScript.
Many websites contain mostly static content:
Yet developers often hydrate entire applications anyway.
Hydration is the process where React attaches interactivity to server-rendered HTML. You can read more about hydration here:
The problem is that hydration requires JavaScript.
Before a page becomes interactive, the browser still needs to:
This becomes especially problematic on mobile devices.
Even relatively simple websites can feel sluggish when they ship large bundles unnecessarily.
This growing focus on performance is one reason frameworks like Astro have become increasingly popular:
Modern frontend development is increasingly focused on sending less JavaScript to the browser.
Modern versions of Next.js use React Server Components by default inside the App Router.
In older React applications, most components automatically became client-rendered. In Next.js App Router, the opposite is now true.
Components are treated as Server Components unless explicitly marked otherwise.
For example:
export default function Page() {
return <h1>Hello world</h1>;
}
This component renders entirely on the server.
No JavaScript for this component is shipped to the browser unless interactivity is required.
This default behavior dramatically reduces JavaScript bundle sizes across many applications.
One of the most important concepts in modern Next.js development is understanding the difference between Client Components and Server Components.
Server Components are ideal for:
Client Components are necessary for:
A Client Component requires the "use client" directive at the top of the file:
"use client";
import { useState } from "react";
export default function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
{count}
</button>
);
}
Without "use client", hooks like useState and browser event handlers are unavailable because the component executes on the server.
This separation encourages developers to think more carefully about what truly needs to run in the browser.
"use client" DirectiveThe "use client" directive is one of the most important parts of the modern Next.js architecture.
It tells React:
This component must execute in the browser.
Once a component becomes a Client Component, all of its child components also become client-rendered unless separated intentionally.
This is why developers should avoid placing "use client" too high in the component tree.
For example, marking an entire layout as client-rendered can dramatically increase bundle sizes unnecessarily.
A better approach is isolating interactivity into smaller components:
"use client";
export default function ThemeToggle() {
return <button>Toggle Theme</button>;
}
Only the toggle requires client-side JavaScript.
The surrounding layout can remain server-rendered.
This approach keeps applications significantly more performant.
One of the biggest advantages of Server Components is simplified data fetching.
Older React applications commonly fetched data in the browser using useEffect():
useEffect(() => {
fetch("/api/posts")
.then(res => res.json())
.then(setPosts);
}, []);
While this works, it creates several issues:
Server Components simplify this dramatically:
async function Posts() {
const posts = await fetch("https://api.example.com/posts").then(res =>
res.json()
);
return (
<ul>
{posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
The server fetches the data before the page reaches the browser.
This improves:
For content-heavy websites, this model is often significantly cleaner.
One of the primary goals of Server Components is reducing unnecessary hydration.
Hydration still exists in applications using Server Components, but fewer components require client-side JavaScript.
For example, static content like:
often does not need hydration at all.
Only interactive portions require client-side rendering.
This dramatically reduces:
Modern frontend frameworks are increasingly moving toward this architecture.
You can also see this trend in:
The overall direction of frontend development is becoming increasingly performance-focused.
Reducing JavaScript has major SEO implications.
Google increasingly prioritizes metrics related to user experience and page performance, including Core Web Vitals.
Large JavaScript bundles can negatively impact:
By reducing hydration and moving rendering work to the server, Server Components help improve many of these metrics.
This is especially important for:
If you're interested in performance optimization, these articles pair well with this topic:
One of the most common mistakes developers make is overusing Client Components.
Adding "use client" too high in the component tree causes large portions of the application to become client-rendered unnecessarily.
Another common mistake is trying to access browser APIs inside Server Components:
window.innerWidth
This fails because Server Components execute on the server, not inside the browser.
Understanding where code executes is one of the most important parts of working with modern React frameworks.
React Server Components represent one of the biggest architectural changes in modern React development.
For years, frontend frameworks assumed most rendering should happen in the browser. Now the ecosystem is moving toward a more balanced server-first approach.
The goal is not eliminating client-side rendering entirely.
The goal is reducing unnecessary JavaScript.
Server Components help accomplish that by:
As frameworks like Next.js continue evolving, understanding Server Components will become increasingly important for frontend developers building modern web applications.
The future of frontend development is becoming more performance-focused, more server-aware, and more intentional about what truly belongs in the browser.