Reactmorsx.io

React Server Components: One Year of Real-World Experience

The Promise vs. The Reality

When React Server Components were first announced, the pitch was elegant: components that run on the server, stream their output to the client, have direct access to databases and file systems, and never ship their implementation code to the browser. Zero-bundle-cost components. Seamless server-client composition. The best of both worlds.

After a year of teams actually building with RSC in production, the verdict is nuanced. The promise is largely delivered — but the mental model required to use RSC effectively is significantly more complex than the initial marketing suggested, and the boundary between server and client has introduced a new category of bugs and architectural decisions that teams are still developing intuitions for.

This article is an attempt to share what we have learned from shipping RSC-based applications across several production systems.

What RSC Actually Gets Right

Let us start with the genuine wins, because there are several.

Data fetching colocation is transformative. The ability to write a component that fetches its own data directly — from a database, an internal API, a file — and renders it server-side, without any global state management, waterfall API calls, or client-side loading states, is genuinely better for a large class of UI. The component is self-contained. The data access is explicit. The render is deterministic. For read-heavy UI that does not require interactivity, this model is clearly superior to the fetch-on-client patterns it replaces.

Bundle size reductions are real. Components that exist purely to format and display data — markdown renderers, code syntax highlighters, date formatters, chart libraries — can live entirely on the server. Their implementation never reaches the browser. On applications with heavy data visualisation or content rendering, the measurable improvements in Time to Interactive are significant. We have seen reductions of 40-60% in JavaScript bundle size for content-heavy applications.

Cache integration is excellent when it works. Next.js's caching primitives built on top of RSC — particularly the granular invalidation model — allow for sophisticated caching strategies that were previously complex to implement. The ability to invalidate specific cached data segments without clearing an entire cache is a meaningful improvement over previous approaches.

The Mental Model Problem

The most common source of frustration with RSC is the boundary between Server Components and Client Components, and specifically the constraints around passing data across that boundary.

The rule seems simple: you can pass serialisable data from server to client, but you cannot pass functions or non-serialisable objects. In practice, this boundary is less intuitive than it sounds.

The 'use client' boundary is not a file boundary, it is a component tree boundary. When you mark a component 'use client', it and all of its children run on the client. But the component's props must be serialisable. This means that a Server Component that renders a Client Component cannot pass a function as a prop — not a callback, not an event handler, not a component itself.

This constraint forces architectural patterns that feel awkward at first. You cannot simply pass an onClick handler from a server-rendered parent to a client child. You cannot pass a React component as a prop across the boundary. You learn to invert: the client component takes in data, not behaviour, and defines its own behaviour internally.

Async components only work in Server Components. The ability to await inside a component body is a RSC-only feature. Client components that need async data must use useEffect, Suspense, or data fetching libraries. This is fine once you internalise it, but it catches developers who are used to writing async logic in components and then try to make that component interactive.

Context does not cross the boundary. React Context — the global state mechanism that most applications rely on for theming, authentication state, and UI state — does not work in Server Components. Consuming context in a Server Component silently fails or throws. This means any component that needs to read from context must be a Client Component.

Patterns That Work

After a year of accumulated experience, several patterns have emerged as reliable.

Leaf interactivity. Keep Server Components at the top of the tree — layouts, data fetching, routing logic — and push Client Components to the leaves where interactivity actually happens. A button that toggles state should be a Client Component; the page that contains it does not need to be.

Data down, events up, but the "events up" part stays client-side. Server Components pass data to Client Components as serialisable props. Client Components handle all user interaction internally. If user interaction needs to trigger a server-side mutation, use Server Actions — which are explicitly designed for this purpose and handle the serialisation boundary correctly.

Islands of interactivity. Think of each 'use client' component as an island. Islands are self-contained. They receive their initial data from the server, manage their own state, and communicate back to the server through server actions. Well-designed islands are narrow and focused. Badly designed islands are large and coupled to too much.

Suspense boundaries as architectural components. Suspense is not just a loading spinner mechanism. It is the mechanism for composing concurrent server and client rendering. Placing Suspense boundaries thoughtfully — around components that fetch independently, around sections that can stream — is an architectural decision that affects both performance and user experience.

The Debugging Tax

One area where RSC has real and ongoing cost is debugging. The server-client split introduces several categories of problem that are harder to debug than equivalent client-side issues.

When a RSC renders on the server and produces an error, the error appears in the server logs, not in the browser console. If you are not instrumenting your server-side rendering errors, they can be silent from the user's perspective. Setting up proper server-side error tracking — and distinguishing between render errors and data errors — requires explicit attention.

Hydration errors — where the HTML generated on the server does not match what the client would generate — are another common pain point. They manifest as cryptic React warnings and can cause subtle visual glitches. Most hydration errors come from time-dependent rendering (rendering new Date() server-side produces a different result than client-side) or from accessing browser-only APIs in shared components.

Where RSC Falls Short

Honest assessment requires acknowledging where RSC is not the right tool.

Highly interactive applications — dashboards with real-time updates, complex forms with interdependent state, drag-and-drop interfaces — see limited benefit from RSC. These applications are inherently client-heavy, and the overhead of managing the server-client boundary provides little return.

Small applications where the architectural investment in RSC outweighs the performance benefit. A simple marketing site or internal tool does not need a sophisticated server-client composition model.

Teams with limited React experience. RSC adds substantial complexity to what was already a complex framework. Teams that are still building intuition for React fundamentals will find RSC a significant additional cognitive load.

The Verdict

React Server Components are a genuine advancement for the category of applications they are designed for: data-intensive, content-rich web applications where performance and data access patterns are first-class concerns.

The mental model is genuinely different from anything that came before in React. It takes months of production experience to develop reliable intuitions for where the server-client boundary should sit. Teams that invest in that learning are building applications that are faster, more maintainable, and better structured than what they could achieve with purely client-side patterns.

Teams that adopt RSC expecting a seamless upgrade from client-side React will have a difficult time. RSC is not an incremental improvement. It is a different way of thinking about component composition, and it rewards teams who commit to understanding it deeply.

More articles

How AI Agents Are Rewriting the Rules of Software Development

Autonomous coding agents have moved from novelty to necessity. We break down exactly how agentic AI is changing how software gets built, tested, and shipped in 2026 — and what it means for the humans still in the loop.

Read more

WebAssembly in 2026: From Browser Curiosity to Universal Runtime

WebAssembly has quietly grown from a performance trick for browser games into a serious contender for server-side compute, edge functions, and plugin sandboxing. Here is where it stands today and why it matters.

Read more

Tell us about your project

Our offices

  • Copenhagen
    1 Carlsberg Gate
    1260, København, Denmark
  • Billund
    24 Lego Allé
    7190, Billund, Denmark