Skip to main content

React Server Components: Complete Guide

React Server Components are functions that execute exclusively on the server and never ship JavaScript to the client. Unlike Server-Side Rendering (SSR), where you render initial HTML but hydrate it with JavaScript on the client, RSC keeps the component logic, data fetching, and secrets on the server—only the resulting HTML and data dependencies flow to the browser. This fundamental shift cuts bundle size, improves time-to-interactive, and lets you access databases and APIs directly from component code without API routes.

The React Server Components paradigm launched experimentally in React 18 and became stable in Next.js 13 App Router. Since 2025, RSC is the recommended pattern for any production React application that needs server-side data fetching or computation. This article explains the mental model, compares it to traditional SSR and CSR, and sets the foundation for the rest of the series.

What Exactly Is a React Server Component?

A server component is a React component that runs only on the server during the build or request phase. The component function itself does not exist on the client—instead, the server serializes the component's output and sends it to the browser as part of the response. This is fundamentally different from a function component you write for the browser, which must be hydrated and interactive.

The defining characteristic of server components is that they can be async. A server component can directly read a database, call an API, or access environment variables without exposing those operations to the client:

// This is a server component (no 'use client' directive)
// It runs only on the server
async function BlogPost({ slug }) {
const post = await db.posts.findOne({ slug });
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
);
}
export default BlogPost;

In this example, the db.posts.findOne() call happens on the server and never reaches the client bundle. The browser receives only the rendered HTML. This is radically simpler than the traditional SSR pattern, where you would fetch the data in getServerSideProps, pass it as props, and then import those same db functions into your client-side build.

Server Components vs. Server-Side Rendering (SSR)

SSR—the pattern used by pages with getServerSideProps or getStaticProps in Next.js Pages Router—renders components to HTML on the server and sends HTML to the browser. The browser then hydrates that HTML: JavaScript attaches event listeners, initializes state, and makes the page interactive. The component code itself must be sent to the client because hydration requires it.

Server Components go further. The component code is not sent to the client at all. The server renders the component, extracts only the serializable output, and transmits that output to the browser. This means you can write data-fetching logic directly in your component without worrying about accidentally exposing it to the client.

AspectSSR (getServerSideProps)Server Components
Component code sent to clientYesNo
Requires hydrationYesNo
Can use async/await in componentNo (in getServerSideProps only)Yes (natively in component)
Can access secrets, databasesYes (in getServerSideProps only)Yes (directly in component)
Client JavaScript bundleIncludes component codeNo component code

Why Server Components Matter for Performance and Security

Shipping less JavaScript to the browser is the single most impactful optimization for modern web applications. In 2026, the average JavaScript bundle for a React application is 250–500 KB gzipped, even with code splitting. Server Components eliminate the need to ship component logic to the client, reducing bundle size by 30–60% in typical applications (verified across production Next.js 14+ deployments, 2025).

Server Components also improve security. You can access API keys, database passwords, and authentication tokens directly in server component code without ever risking exposure. Sensitive operations remain server-side; the client receives only the safe, serialized output.

From a developer experience perspective, server components restore the simplicity of server-side rendering—you can fetch data directly in the component—but without the complexity of managing separate data-fetching functions and prop-drilling.

// Server component: direct database access, no leakage of secrets
async function UserProfile({ userId }) {
const user = await db.users.findById(userId);
const apiKey = process.env.INTERNAL_API_KEY; // Safe on server only

return (
<div>
<h1>{user.name}</h1>
<p>Email: {user.email}</p>
</div>
);
}

// Client component: no db access, no secrets
'use client';
export function UserActions() {
const [isLoading, setIsLoading] = useState(false);

const handleFollow = async () => {
setIsLoading(true);
const res = await fetch('/api/follow', { method: 'POST' });
// API route handles the secure operation
};

return <button onClick={handleFollow}>Follow</button>;
}

The RSC Payload and Mental Model

When you use server components, the server doesn't send traditional HTML. Instead, it sends a serialized format called the RSC Payload—a JSON-like structure that describes the component tree, the server component outputs, and references to client components that need to be hydrated.

The mental model is: the server renders your server components to produce this payload, inserts client component placeholders, and streams the payload to the browser. The browser deserializes the payload and renders the final HTML, hydrating only the client components that are referenced.

This payload includes:

  • The rendered output of server components (strings, HTML)
  • References to client components that need to be hydrated
  • Serialized props passed from server to client components

You do not typically see or manipulate the RSC Payload directly—Next.js handles it under the hood—but understanding that it exists helps you reason about what data can and cannot cross the server-client boundary. (See the next article in the series for a deep dive on data serialization.)

Getting Started with Server Components

In Next.js 13+ App Router, every component is a server component by default. You opt in to client components with the 'use client' directive at the top of the file:

// app/dashboard/page.tsx — server component by default
import { ClientCounter } from './client-counter';

async function Dashboard() {
const data = await fetchData();

return (
<div>
<h1>Dashboard</h1>
<ServerContent data={data} />
<ClientCounter />
</div>
);
}

export default Dashboard;
// app/dashboard/client-counter.tsx — explicitly a client component
'use client';

import { useState } from 'react';

export function ClientCounter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(c => c + 1)}>Count: {count}</button>;
}

The server renders the Dashboard component (which fetches data), embeds the ClientCounter as a reference in the payload, and sends the combined payload to the browser. The browser hydrates only ClientCounter because it has the 'use client' directive.

Key Takeaways

  • React Server Components run only on the server and their code is never shipped to the browser.
  • Server components can directly access databases, APIs, and secrets; the client receives only serialized output.
  • Unlike SSR, server components do not require hydration of component logic—only client components hydrate.
  • Server components reduce JavaScript bundle size by 30–60% in typical applications.
  • In Next.js 13+ App Router, components are server components by default; use 'use client' to opt into client rendering.
  • The RSC Payload is the serialized format that the server sends to describe the component tree and its outputs.

Frequently Asked Questions

Are server components the same as Server-Side Rendering?

No. SSR renders components to HTML on the server but hydrates the full component code on the client. Server components never send their code to the client—only the rendered output. Server components are simpler and produce smaller bundles.

Can I use hooks in a server component?

No. Hooks like useState, useEffect, and useContext are client-only because they require browser APIs and state management. If you need interactivity, use a client component or extract the interactive parts into a client component child.

Do I need to rewrite my entire app to use server components?

No. You can mix server and client components in the same application. Start by converting leaf components (components that don't have children or whose children are mostly static) to server components to gain the bundle-size benefits.

How does data flow from server components to client components?

Server components can pass serializable props to client components. The data is serialized into the RSC Payload and deserialized in the browser. Non-serializable values (functions, instances, symbols) cannot cross the boundary.

What happens if I accidentally use a browser API in a server component?

Next.js will throw an error at build time or runtime, alerting you that the API is not available on the server. This is a safety feature. Move the browser API usage to a client component or a separate client-side module.

Further Reading