React Server Components in Production: What We Learned After 500+ Projects
Afzal
Senior React Engineer
14 March 2026
6 min readReact Server Components are not just 'components that render on the server.' That's been true since PHP. RSC is a fundamentally different component model: server components never ship JavaScript to the client, they compose with client components through a serialisation boundary, and they can be async by default. That changes how you think about data fetching, bundle size, and the mental model of your entire application.
The Server vs Client Decision Framework
The most common question we hear from teams adopting App Router: 'When do I add use client?' The default answer should always be: only when you need it. Here's the checklist we use during code review:
- Needs useState or useReducer โ Client component
- Needs useEffect, event listeners, or browser APIs โ Client component
- Uses third-party libraries that import from 'react' directly โ Client component (unless the library supports RSC)
- Reads from a database, file system, or secure API โ Server component
- Renders markdown, processes text, or does CPU-bound work โ Server component
- Is a layout wrapper with no interactivity โ Server component
Data Fetching: Parallel vs Waterfall
The biggest performance win from RSC isn't the bundle size reduction โ it's the ability to co-locate data fetching with rendering and run those fetches in parallel at the server level. Here's the pattern we use:
// page.tsx โ runs on the server, fetches in parallel
export default async function DashboardPage() {
// Promise.all kicks off both fetches simultaneously
const [user, metrics] = await Promise.all([
getUser(userId),
getMetrics(userId),
]);
return (
<div>
<UserHeader user={user} />
<MetricsDashboard metrics={metrics} />
</div>
);
}๐ก Deduplicate with React cache()
If multiple components in a tree fetch the same data, wrap your fetch function in React's cache() utility. This deduplicates identical requests within a single render, even across component boundaries.
Real Performance Numbers
67%
Average JS bundle reduction
1.8s
Avg TTFB improvement
92
Avg Lighthouse score post-RSC
40%
Fewer client-side fetches
Common Pitfalls We Kept Hitting
- Serialisation errors: Server components pass props to client components across the RSC boundary. Those props must be serialisable โ no functions, no class instances, no Dates (use ISO strings instead).
- Context doesn't cross the boundary: React context is client-only. If you need to share server-derived state with client components, pass it as props or use a server action.
- Third-party library compatibility: Many UI libraries still don't support RSC. If a library throws 'useState can only be used in client components,' you'll need to wrap it in a client component shell.
- Stale data and revalidation: Server components are cached aggressively in production. Always set appropriate revalidation using revalidatePath, revalidateTag, or fetch cache options.
Tags
You might also like
Work with us
Ready to build your product?
We help product teams across the UK, Netherlands, Australia, and North America ship faster without compromising quality. Let's talk about your project.
Talk to our team โ
