React 19 Server Components: The Complete Guide
By Eoin Villarin, 2026-04-10, updated 2026-10-28
React Server Components change where your code runs. Learn the client boundary, serialisable props, data fetching, caching and the security patches every 2026 app needs.
What are React Server Components and how do they differ from client components?
React Server Components run only on the server, next to the data, and send rendered output to the browser instead of their code. They can be async and read databases or secrets, but cannot use state, effects or event handlers. Client components, marked 'use client', add JavaScript to the bundle and hydrate in the browser.
Key takeaways
- Server components became stable with React 19 in December 2024, and React 19.3 is the current release as of October 2026.
- The 'use client' directive marks a module and all its transitive dependencies as client code, not just one file.
- Client component props must be serialisable: Date, Map, Set and promises pass, but ordinary functions and class instances do not.
- CVE-2025-55182, disclosed on 3 December 2025, was a CVSS 10.0 unauthenticated remote code execution flaw in React Server Function payload decoding.
- Wrap database and SDK calls in React's cache function to deduplicate requests within a single server request.
React Server Components change where your code runs, and that single shift ripples through data fetching, bundling, caching and security in ways the API surface does not make obvious. Most teams adopt them because a framework switched them on by default, then spend weeks untangling boundary errors they never fully understood.
This guide covers the mental model, what can cross the server-client boundary, composition patterns that hold up in production, data fetching and caching, and the security lessons from the critical vulnerabilities disclosed in December 2025.
Everything here reflects the state of things in October 2026: React 19.3 is the current release, and Next.js 16.4, released on 6 October 2026, ships with it. For the routing side of a migration, read the App Router migration guide alongside this one.
What are React Server Components and what problem do they solve?
The client-side default has a built-in round trip. Ship JavaScript, boot it, render a spinner, request data, render again. The user waits through all of it, and the data request cannot even start until the bundle has arrived and executed.
Server components remove that sequencing. The component runs on the server, next to the data, and the browser receives the rendered result rather than the code that produced it. A component that formats a product description with a heavy markdown library sends the finished HTML, not the library.
Where server components stand in late 2026
Server components became stable with React 19 in December 2024. The React 19.3 release in September 2026 lets a server component render a context from a client module directly, without a wrapper provider component.
How do server and client components differ?
Think of your component tree as split by a boundary. Above it, components run only on the server. Below it, components also run in the browser. The table below is the quickest way to decide which side a component belongs on.
| Capability | Server component | Client component |
|---|---|---|
| Await data in the component body | Yes, components can be async | Only via use() with a promise passed from the server |
| useState, useEffect and event handlers | No | Yes |
| Read secrets, databases and the filesystem | Yes | No |
| Adds JavaScript to the bundle | No | Yes |
| Browser APIs such as window and localStorage | No | Yes, after hydration |
| Included in the initial HTML | Yes | Yes, client components are still server-rendered |
Server components
- Can be
asyncand await data directly in the component body. - Never re-render in the browser. There is no state, no effects and no event handlers.
- Can safely import server-only code, including database clients and secrets. Adding
import 'server-only'to those modules makes the build fail if one is ever pulled into client code. - Contribute nothing to the JavaScript bundle.
Client components
Marked with 'use client', these behave the way React components always have. State, effects and event handlers all work. They are still rendered to HTML on the server for the first load, then ship to the browser and hydrate. "Client" describes where the code ends up, not where it first renders.
The directive marks an entry point, not a file
This is the detail people miss most often. The React documentation for use client explains that the directive marks a module and all of its transitive dependencies as client code. Everything that file imports becomes part of the client bundle, even modules without their own directive.
Putting the directive high in the tree quietly pulls most of your application back into the browser, which is exactly what you were trying to avoid.
What can cross the server-client boundary?
A server component can render a client component and pass it props, but those props have to be serialisable. React's supported list is broader than many older articles suggest, and it includes dates, maps, sets and promises.
| Passes | Does not pass |
|---|---|
| Strings, numbers, bigint, booleans, null and undefined | Ordinary functions and event handlers |
| Plain objects and arrays of serialisable values | Class instances, including your own models |
| Date, Map, Set, typed arrays and ArrayBuffer | Objects with a null prototype |
Promises, which a client component can read with use() | Symbols not registered with Symbol.for |
| JSX elements, including server-rendered children | Database connections and other live handles |
Server Functions marked with 'use server' | Anything that depends on a closure |
Children is the escape hatch
You can pass server-rendered content into a client component as children. The client component wraps the finished output without ever needing its code, so an interactive shell such as an accordion can hold static server-rendered content.
// app/faq/page.tsx (server component)
import { Accordion } from './accordion'; // marked 'use client'
import { getFaqs } from '@/lib/data';
export default async function FaqPage() {
const faqs = await getFaqs();
return (
<Accordion>
{faqs.map((faq) => (
<section key={faq.id}>
<strong>{faq.question}</strong>
<p>{faq.answer}</p>
</section>
))}
</Accordion>
);
}
Server Functions are the way back
Data flows down as props, and actions flow back up through Server Functions marked with 'use server'. Treat every Server Function as a public HTTP endpoint: validate input with a schema, check authentication inside the function, and never trust an ID just because it came from your own UI. The Zod validation guide covers the schema side.
Composition patterns that hold up in production
Most of the benefit comes from a handful of habits. Skip them and you end up with a client app wearing a server costume.
- Push the boundary down. Make the small interactive leaf a client component rather than the page that contains it.
- Fetch high, render low. Read data in the server component and pass plain values down.
- Wrap, do not absorb. Use children to keep server content inside client shells.
- Stream the slow parts. Suspense boundaries let fast content paint while a slow query resolves.
- Provide context from the server. Since React 19.3 a layout can load the current user once and render the context directly.
import { Suspense } from 'react';
import { AddToCart } from './add-to-cart'; // marked 'use client'
export default async function ProductPage({ params }) {
const { slug } = await params;
const product = await getProduct(slug);
return (
<main>
<ProductHeader title={product.title} images={product.images} />
<AddToCart productId={product.id} price={product.price} />
<Suspense fallback={<p>Loading reviews...</p>}>
<Reviews productId={product.id} />
</Suspense>
</main>
);
}
async function Reviews({ productId }) {
const reviews = await getReviews(productId); // slow third-party API
return <ReviewList reviews={reviews} />;
}
The product details and the button paint immediately, and the reviews stream in when the slow API answers.
How should you fetch data in server components?
Fetching moves into the component itself, which removes a whole category of plumbing. There is no separate data function, no prop drilling from a route loader, and no client cache to keep in sync for read-only data.
Colocating a fetch next to the component that needs it stops being wasteful once requests are deduplicated within each render.
Deduplication is not automatic for every data source. Wrap database and SDK calls in React's cache function, which memoises results for a single server request, and create the cached function once in a shared module rather than inside a component.
// lib/data.ts
import 'server-only';
import { cache } from 'react';
import { db } from './db';
export const getUser = cache(async (id: string) => {
return db.user.findUnique({ where: { id } });
});
Waterfalls still exist
Awaiting sequentially in nested components recreates the waterfall you were trying to remove. Where requests are independent, start them together and await the group.
// Sequential: total time is the sum of both requests
const user = await getUser(id);
const orders = await getOrders(id);
// Parallel: total time is the slower of the two
const [account, history] = await Promise.all([getUser(id), getOrders(id)]);
Caching across requests with use cache
Request-level deduplication is not the same as caching between visitors. In Next.js 16, the Cache Components model handles that with a 'use cache' directive and a cacheLife profile on the component or function you want to keep. The Next.js 16.4 release notes recommend Cache Components for every app, enable it for new projects and say it will become the default in Next.js 17.
What did the RSC security vulnerabilities change?
On 3 December 2025 the React team disclosed CVE-2025-55182, an unauthenticated remote code execution flaw rated CVSS 10.0 in how React decoded payloads sent to Server Function endpoints. It affected the react-server-dom packages in React 19.0, 19.1.0, 19.1.1 and 19.2.0, and frameworks including Next.js, React Router and Waku. The React security advisory notes that an app could be exposed even without Server Functions of its own, simply by supporting server components.
Follow-up denial of service and source code exposure issues were patched in the following weeks, and Next.js has since moved to a formal security release process. Its 14 October 2026 update fixed another critical issue in use cache functions that return typed arrays, patched in 16.4.1. The lessons:
- Patch the framework, not just React. The vulnerable code lives in the bundler integration packages.
- Watch the advisories. Critical RSC fixes have arrived several times since the first disclosure.
- Never pass secrets as props. Anything passed to a client component is serialised into the page payload.
Common mistakes in a first migration
- Reaching for
'use client'to silence an error. The error is usually telling you the boundary is in the wrong place. - Assuming server means fast. A slow query is slow wherever it runs. Server components move the work, they do not optimise it.
- Passing whole database records to client components. Select only the fields the component needs, because everything else ends up in the HTML payload.
How to migrate an existing app to React Server Components
- Audit interactivity. List the components that genuinely need state, effects or browser APIs. In content-heavy apps they are usually a minority.
- Move reads to the server. Replace useEffect fetching with async server components and delete the loading state that came with it.
- Push
'use client'to the leaves. Split mixed components so only the interactive part carries the directive. - Isolate browser-only libraries. Wrap chart, map and editor libraries in their own client components.
- Measure the result. Compare JavaScript shipped per route before and after with your bundler's analyser, then check real-user performance using the techniques in the React performance guide.
When not to bother
An app that is mostly interactive behind a login, with no SEO requirement, gains much less. Content-heavy and commerce sites gain the most, which is why the pattern fits headless commerce so well. For this year's wider changes, see what's new in Next.js and React in 2026.
Frequently asked questions
Are React Server Components stable?
Yes. Server components have been stable since React 19 shipped in December 2024, and React 19.3 is the current release. The low-level APIs that frameworks and bundlers use to implement them can still change between minor versions, which is why most teams should rely on a maintained framework and keep it patched rather than wiring those packages up themselves.
Do server components replace server-side rendering?
No. Server-side rendering turns a component tree into HTML for the first page load, and it applies to client components too. Server components are a separate idea: they never hydrate and never ship their code to the browser. Frameworks use both together, rendering server and client components to HTML and then hydrating only the client parts.
Can I use server components without Next.js?
Yes. Next.js is not the only option. React Router, Waku, Parcel and the Vite RSC plugin also support server components, and all of them appeared in the December 2025 advisory's list of affected tools. Choose based on your routing and hosting needs, and make sure whichever framework you pick ships security patches quickly.
Do React Server Components help SEO?
Indirectly, yes. Content rendered on the server is present in the initial HTML, so crawlers do not depend on JavaScript execution to see it, and shipping less JavaScript usually improves loading and responsiveness. Server components do not replace good metadata, canonical URLs and internal links, which still decide how well a page ranks.
Next steps: get your server component architecture right
Server components move execution, not just rendering. Keep the boundary low, pass only serialisable props, use children to nest server content inside client shells, cache deliberately and keep your framework patched.
If you are planning a migration and want a second opinion on the architecture, get in touch through the contact form, or browse recent case studies to see how I approach builds for clients in Australia, the US and beyond.