React

First impressions of React Server Components in an experimental build

RCT

React Server Components were demoed late last year and remain experimental, but the idea, components that render on the server and never ship their code to the client at all, is different enough from anything in React's existing model that we wanted to understand it before it goes mainstream rather than reading about it secondhand once it does.

Why this is a bigger shift than it looks

The mental model shift is bigger than it first appears. You have to think explicitly about which components are server-only, client-only, or shared, and the boundaries aren't always where you'd first guess. A component that fetches data and renders a list is an obvious server component candidate; a button with an onClick handler obviously needs to be a client component. The harder cases are the ones in between, like a component that fetches data on the server but needs a small piece of client-side state for a toggle, which forces you to split what used to be one component into two, with the server component passing serializable props down to a client child.

Data fetching gets dramatically simpler when a component can just `await` a database call or an internal API directly inside its render function, with no separate API route to maintain, no client-side loading state to manage, and no waterfall of sequential fetch requests once you nest a few of these components inside each other. In our proof of concept, a dashboard page that would normally require three separate client-side fetches, each with its own loading and error state, collapsed into three server components each awaiting its own data source directly, with the framework handling the streaming and composition.

  • The mental model shift is bigger than it first appears, since you have to explicitly decide which components are server-only, client-only, or shared, and the natural boundary isn't always obvious in advance.
  • Data fetching gets dramatically simpler when a component can await a database call directly, with no separate API layer and no client-side loading state to manage.
  • Bundle size drops meaningfully for server components, since their code, and any heavy server-side dependency they import, never gets sent to the browser at all.
  • Secrets and internal API credentials used inside a server component are structurally impossible to leak to the client, since the component's source code never crosses the network boundary.

Where the experimental build fell short

Tooling support is essentially nonexistent outside Facebook's own experimental setup right now, which makes this firmly a 'watch and wait' technology rather than something to bet a client project on. There's no stable bundler integration, no production-ready framework built on top of it yet, and the boundary rules between server and client components are enforced more by convention and documentation than by tooling that will catch a mistake for you. We hit several confusing error messages during the proof of concept that came down to accidentally importing a client-only hook into what we'd intended to be a server component, with no clear signal pointing at the actual problem.

  • Tooling support is essentially nonexistent outside Facebook's own experimental setup right now, which makes this a 'watch and wait' technology rather than something to build a client project on.
  • The server and client component boundary is currently enforced by convention more than tooling, and mistakes produce confusing errors rather than clear guidance.

How this changes how we think about React going forward

Even without shipping it anywhere, prototyping Server Components has already changed how we talk about where logic belongs in a React app. It's made the tradeoffs in patterns like `getServerSideProps`, which we already use heavily, feel more like a stepping stone than an end state, since Server Components generalize the same idea, server-rendered data with no client bundle cost, down to the individual component level rather than the whole page. We've started asking, on new feature work, whether a given component's logic would naturally live on the server once this pattern is stable, even while writing it the conventional way today.

Comparing this to the data-fetching patterns we already use

We already lean heavily on `getServerSideProps` for pages that need fresh data on every request, and it's worth being precise about what Server Components actually improve on that pattern rather than treating them as a wholesale replacement. `getServerSideProps` fetches data at the page level and passes the result down as props to every component on that page, which means a deeply nested component that needs its own data either has to receive it threaded through several layers of props it doesn't otherwise care about, or fetch it client-side after the initial render, adding a second round trip. Server Components let that same deeply nested component fetch its own data directly, at whatever level of the tree it actually lives, without polluting the props of every component above it and without a second client-side round trip. The page-level fetch pattern isn't wrong, but it optimizes for the top of the tree in a way that Server Components generalize down to any component at any depth.

Streaming is the other piece worth calling out. Server Components are designed to work with React's Suspense boundaries so that slower parts of a page can stream in after the faster parts have already reached the browser, rather than the whole page waiting on its slowest data dependency the way a single `getServerSideProps` call effectively does today. In our proof of concept, a page with one fast query and one deliberately slow one rendered the fast section immediately and streamed the slow section in about a second later, with no client-side loading spinner logic written by us at all; the framework handled the placeholder and swap automatically once wired to a Suspense boundary.

Security is a nice side effect, not just a footnote

It's worth dwelling on the security implication a bit longer than we did in our first pass at this, because it's easy to undersell. A server component's source code, including any import it pulls in, a database client configured with credentials, an internal API key, a query that references an internal table name, never gets serialized and sent to the browser at all. That's a structurally different guarantee than "we were careful not to expose this," which is the best most current patterns can offer; a bug in a `getServerSideProps` function can still accidentally return a sensitive field in its props object, and that mistake ships to the client bundle. A Server Component that never crosses the server-client boundary in the first place can't leak that way, even if the underlying application logic has the same kind of mistake in it.

Building the proof of concept, concretely

Our proof of concept was deliberately small: a three-page internal dashboard showing project status, pulled from an internal database, with one page rendered almost entirely as server components and a comparison version of the same page built the conventional way with `getServerSideProps` and client-side data fetching for a nested widget. Setting it up meant working directly against Facebook's experimental fork rather than a stable public release, which involved pinning exact package versions and expecting things to break between updates, not a workflow any of us would tolerate for real client work but a reasonable cost for a two-week internal exploration. Even within that narrow scope, the difference in code volume was noticeable; the server-component version of the page needed roughly 40 percent less code overall, almost entirely because of the loading-state and error-boundary boilerplate that disappeared once data fetching moved server-side and synchronous from the component's own perspective.

How this affects hiring and training conversations, longer term

One thing we hadn't fully considered before running the proof of concept is what a stable version of this pattern would mean for how we train new React hires. The current mental model, components as pure functions of props and state, rendering on the client, with data fetched separately and passed down or fetched client-side, is deeply embedded in how we currently onboard people to React. A world where some components are quietly server-only, with a different set of rules about what APIs they can call and what they're allowed to import, adds a genuine new axis to teach, and we expect that whenever this pattern does stabilize, updating our internal onboarding materials will be a non-trivial undertaking in its own right, not just a matter of swapping one data-fetching pattern for another in a training doc.

We don't expect to ship Server Components in production this year or probably next, and we wouldn't recommend any team put client work on top of something this early and this unstable. But the underlying ideas are worth understanding now, both because they'll likely shape whatever official framework support eventually ships, and because the exercise of drawing an explicit line between server and client logic is a useful discipline on its own, independent of whether the specific API survives to a stable release in its current form.

← Back to the journal

Have a project in mind?
Let’s talk.

Tell us where you are and where you want to go. We'll map the fastest route between the two.

Currently accepting new clients