The Practical Case for React Server Components
React Server Components (RSC) have generated plenty of questions since they were introduced. In a recent live stream, Kent C. Dodds sat down with React core team members Joe Savona and Dan Abramov to dig into the architecture, its trade-offs, and how it compares to other approaches like Remix.
The conversation was framed around Dodds’ own confusions as a heavy Remix user, not as a general tutorial. That focus shaped much of the discussion, which is less about how to use RSC and more about what problems they actually solve.
What RSC Actually Solves
A simple way to describe RSC is running all components on the server and sending their rendered output to the client. That approach has a useful side effect: automatic bundle splitting. Components that aren't used on a page never ship to the client, which makes it an attractive architecture for optimizing payload size. But the team was clear that this is not the primary problem RSC is meant to solve.
The real value, according to Savona and Abramov, is that RSC expands the React model so each component can fetch the data it needs. Data dependencies are co-located with the component that uses them, and developers can write code in a familiar request-response model while still taking full advantage of both server and client environments.
This is a meaningful difference from frameworks like Remix or Astro. With RSC, components become more reusable and composable on the server. A tweet component that pulls data from the Twitter API, for example, could be packaged and shared via npm without requiring the consumer to implement its data logic separately.
Latency, Waterfalls, and N+1
One of the concerns with RSC is the potential for data-fetching waterfalls. If each nested component awaits its own data, the overall request can stall while waiting for sequential responses. Savona and Abramov acknowledged this as a genuine issue but suggested mitigation strategies: putting server-side rendering on the edge and keeping server components close to the data layer can help reduce latency.
Parallel data fetching is possible for route segments in both Remix and RSC. The team noted, however, that waterfalls may not always be the root problem. If a list renders many child items, each triggering its own query, developers run into the classic N+1 problem. Standard fixes include data-loader patterns that batch queries or caching. Observability was also emphasized as critical for spotting performance problems before they become systemic.
Where Remix Fits In
The discussion acknowledged that Remix offers solutions to some of the same problems RSC addresses. In a typical Remix app, fetches are made at the route layer or in a layout, which ensures the request happens once and data is passed down as props. This is a workable pattern, but it centralizes data dependency rather than co-locating it.
RSC takes a different route, borrowing ideas from GraphQL and Relay to let developers put data requirements next to individual components. The team noted that GraphQL isn't the right fit for every application; RSC incorporates similar benefits without forcing a query language on developers. The trade-off is that developers must handle patterns like N+1 themselves, or rely on the framework.
Runtime and Data Deduplication
A common misconception is that server components are slower than their client-side equivalents. The team pushed back on this, noting that deduplication is already solved at the framework level. They distinguished between what React itself does and what frameworks might need to add. React does not patch fetch to dedupe automatically; the plan is to provide its own version that hooks into identification and hydration. Frameworks like Next.js, however, may still patch fetch when needed.
Deduping in RSC is scoped to a single user request, not across users. This is managed through an async context that isolates one request's data from another's. Caching can be handled at the framework level as well. Next.js, for instance, offers options like cache keys to specify whether data is static and shareable across users or whether it should be generated during the build process.
Accessing Request Data and the Streaming Default
For developers who need request-specific information like cookies or cache headers, the approaches in Next.js and Remix end up looking similar. Both provide utility functions that avoid the need to manually thread the request object through the entire component tree. Remix exposes an accessor that returns the underlying request object, simplifying header manipulation for caching behavior. In Next.js, direct imports for accessing cookies are available, although the idea of importing data like that is still not standard in the broader React ecosystem.
In the RSC model, streaming becomes the default behavior. Every component acts like an implicitly deferred loader. There is some debate over how to handle headers, since different fetches can have different requirements. A coordinating system must decide what to do when multiple loadings are in flight. This is one area where the framework still needs to provide structure.
Server and Client Code Separation
The requirement to keep server and client code in separate files is one of the most visible differences in RSC. While some developers may prefer the ability to interleave the two, the separation has a practical benefit: it helps prevent accidental exposure of server-only secrets to the client.
The team stressed that this is not a fundamental limitation of RSC. The architecture is not prescriptive about how an app is structured. The file split is a trade-off that improves developer experience and guards against common mistakes, even if it sacrifices some flexibility.
Mutations and the Missing Pieces
One area where RSC has not fully caught up is mutations. Dodds pointed out that Remix has a strong progressive enhancement story with forms and handlers. The React team is working on mutations that would allow passing functions from the server to the client in a single, integrated mechanism. The goal is that a server component could specify behavior that changes with a network boundary, automatically integrating that behavior without requiring event handlers for every interaction.
Without mature patterns for mutations, RSC adoption for data-heavy, interactive apps will remain a work in progress. The team acknowledged that it is crucial to solidify these patterns so they become standard practice, rather than a collection of workarounds.
Mental Model and Component Composition
Working with RSC requires a shift in how developers think about the React tree. Instead of an entire tree rendered on the client, thinking of server components as a skeleton filled in by client components at specific interactive holes is more accurate. The server framework should go as deep into the tree as possible, and when interactivity is needed, a client component can accept slots that extend the reach of server-rendered code.
This composition model also opens up granular optimization opportunities. A code highlighter could be a server component that renders a client component, avoiding the need to load parsers on the client for every page. Developers benefit from being able to write one technology, one language, and one set of idioms across both sides of the wire, while making component-level trade-offs without chang



