recorded decision · public · no signup

accepted2026-06-22reactjs/rfcs

RFC: React Server Components

What was chosen

  • React Server Components allow developers to build apps spanning server and client, combining client-side interactivity with server rendering performance.adr
  • Server Components run only on the server, resulting in zero impact on client bundle size and improved startup time.adr
  • Server Components can directly access server-side data sources like databases, file systems, or microservices.adr
  • Server Components seamlessly integrate with Client Components, passing data as props for interactive parts of a page.adr
  • Server Components preserve client state, focus, and animations when reloaded or refetched.adr
  • Server Components are rendered progressively and incrementally stream UI units to the client, enabling intentional loading states.adr
  • The `.server.js` / `.client.js` filename convention was replaced with a `'use client'` directive for Client Components.adr

Constraints

  • Server Components may not use state, effects, or browser-only APIs because they execute once per request on the server.adr
  • Client Components may not import Server Components or use server-only data sources.adr

Consequences

  • Introducing a new form of components means more to learn for developers.adr

The recorded why

RFC: React Server Components

⚠️ NOTE: We strongly recommend watching our talk introducing Server Components before reading this RFC.

Table Of Contents

Summary

⚠️ NOTE: We strongly recommend watching our talk introducing Server Components before reading this RFC.

This RFC discusses an upcoming feature for React called Server Components. Server Components allow developers to build apps that span the server and client, combining the rich interactivity of client-side apps with the improved performance of traditional server rendering:

  • Server Components run only on the server and have zero impact on bundle-size. Their code is never downloaded to clients, helping to reduce bundle sizes and improve startup time.
  • Server Components can access server-side data sources, such as databases, files systems, or (micro)services.
  • Server Components seamlessly integrate with Client Components — ie traditional React components. Server Components can load data on the server and pass it as props to Client Components, allowing the client to handle rendering the interactive parts of a page.
  • Server Components can dynamically choose which Client Components to render, allowing clients to download just the minimal amount of code necessary to render a page.
  • Server Components preserve client state when reloaded. This means that client state, focus, and even ongoing animations aren’t disrupted or reset when a Server Component tree is refetched.
  • Server Components are rendered progressively and incrementally stream rendered units of the UI to the client. Combined with Suspense, this allows developers to craft intentional loading states and quickly show important content while waiting for the remainder of a page to load.
  • Developers can also share code between the server and client, allowing a single component to be used to render a static version of some content on the server on one route and an editable version of that content on the client in a different route.

Changes Since v1

Basic Example

This example renders a simple Note with a title and body. It renders a non-editable view of the Note using a Server Component and optionally renders an editor for the Note using a Client Component (a traditional React component). First, we render the Note on the server.

// Note.js - Server Component

import db from 'db'; 
// (A1) We import from NoteEditor.js - a Client Component.
import NoteEditor from 'NoteEditor';

async function Note(props) {
  const {id, isEditing} = props;
  // (B) Can directly access server data sources during render, e.g. databases
  const note = await db.posts.get(id);
  
  return (
    <div>
      <h1>{note.title}</h1>
      <section>{note.body}</section>
      {/* (A2) Dynamically render the editor only if necessary */}
      {isEditing 
        ? <NoteEditor note={note} />
        : null
      }
    </div>
  );
}

This example illustrates a few key points:

  • This is “just” a React component -- it takes in props and renders a view. There are some constraints of Server Components - they can’t use state or effects, for example - but overall they work as you’d expect. More details are provided below in Capabilities & Constraints of Server and Client Components.
  • Server Components can directly access server data sources such as a database, as illustrated in (B). This is implemented via a generic mechanism that supports arbitrary asynchronous data sources with async / await.
  • Server Components can hand off rendering to the client by importing and rendering a “client” component, as illustrated in (A1) and (A2) respectively. Client Components contain a 'use client' directive at the top of the file. Bundlers will treat these imports similarly to other dynamic imports, potentially splitting them (and all the files they import) into another bundle according to various heuristics. In this example, NoteEditor.js would only be loaded on the client if props.isEditing was true.

In contrast, Client Components are the typical components you’re already used to. They can access the full range of React features: state, effects, access to the DOM, etc. The name “Client Component” doesn’t mean anything new, it only serves to distinguish these components from Server Components. To continue our example, here’s how we can implement the Note editor:

// NoteEditor.js - Client Component

'use client';

import { useState } from 'react';

export default function NoteEditor(props) {
  const note = props.note;
  const [title, setTitle] = useState(note.title);
  const [body, setBody] = useState(note.body);
  const updateTitle = event => {
    setTitle(event.target.value);
  };
  const updateBody = event => {
    setBody(event.target.value);
  };
  const submit = () => {
    // ...save note...
  };
  return (
    <form action="..." method="..." onSubmit={submit}>
      <input name="title" onChange={updateTitle} value={title} />
      <textarea name="body" onChange={updateBody}>{body}</textarea>
    </form>
  );
}

This looks like a regular React component because it is: Client Components are just regular components. The 'use client' directive at the top marks this component (and any modules it imports) as code that must execute on the Client.

An important consideration is that when React renders the results of Server Components on the client it preserves the state of Client Components that may have been rendered before. Specifically, React merges new props passed from the server into existing Client Components, maintaining the state (and DOM) of these components to preserve focus, state, and any ongoing animations.

Motivation

Server Components address a number of challenges in React that we’ve seen across a wide range of apps. Initially we searched for targeted solutions to these problems as this can often lead to a simpler solution. However we weren’t satisfied with the approaches this led us to. The fundamental challenge was that React apps were client-centric and weren’t taking sufficient advantage of the server. If we could allow developers to easily leverage their server more, we could solve all of these challenges and provide a more powerful approach to building apps, small or large.

These challenges fall into two main buckets. First, we wanted to make it easier for developers to fall into the “pit of success” and achieve good performance by default. Second, we wanted to make it easier to fetch data in React apps. If you have used React before you have likely wished for at least some of the following capabilities:

Zero-Bundle-Size Components

Developers constantly have to make choices about using third-party packages. Using a package to render some markdown or format a date is convenient for us as developers, but it increases code size and hurts performance for our users. On the other hand, rewriting these features ourselves is time-consuming and error-prone. While advanced features such as tree-shaking can help somewhat, we still end up having to ship some extra code to the user. For example, today rendering our Note example with Markdown might require over 240K of JS (over 74K gzipped individually).

Note that this is just one set of libraries you might use and there may be smaller or larger alternatives. Even if you prefer alternative libraries that are only X bytes, your users still have to download those X bytes. The point of this example is not to pick on any particular library but to demonstrate that using libraries is helpful as developers but increases bundle size and can hurt application performance:

// NOTE: *before* Server Components

import marked from 'marked'; // 35.9K (11.2K gzipped)
import sanitizeHtml from 'sanitize-html'; // 206K (63.3K gzipped)

function NoteWithMarkdown({text}) {
  const html = sanitizeHtml(marked(text));
  return (/* render */);
}

However, many parts of an application aren’t interactive and don’t need full data consistency. For example, “detail” pages often show information about a product, user, or other entity and don’t need to update in response to user interaction. The Note example here is a perfect example.

Server Components allow developers to render static content on the server or during the build, taking full advantage of React’s component-oriented model and freely using third-party packages while incurring zero impact on bundle size. If we render the above example as a Server Component, we can use the exact same code for our feature but avoid sending it to the client - a code savings of over 240K (uncompressed):

// Server Component === zero bundle size

import marked from 'marked'; // zero bundle size
import sanitizeHtml from 'sanitize-html'; // zero bundle size

function NoteWithMarkdown({text}) {
  // same as before
}

Full Access to the Backend

One of the most common pain points when writing React apps is deciding how to access your data — or where to store your data in the first place. There are many great approaches to data-fetching with React, and many great databases and data stores to choose from. However, each of these approaches have some challenges. A common theme is that developers have to expose additional endpoints to power their UI, or use existing endpoints that weren’t always designed with that UI in mind. We wanted a solution that would both make it easier for anyone to get started and that could tame complexity in larger apps too.

For example, if you’re creating a new app (or even your very first app!) and aren’t sure where to store your data, you can start with your file system:

import fs from 'fs';

async function Note({id}) {
  const note = JSON.parse(await fs.readFile(`${id}.json`));
  return <NoteWithMarkdown note={note} />;
}

More sophisticated apps can similarly take advantage of direct backend access to use databases, internal (micro)services, and other backend-only data sources:

import db from 'db';

async function Note({id}) {
  const note = await db.notes.get(id);
  return <NoteWithMarkdown note={note} />;
}

Automatic Code Splitting

If you’ve worked with React for a while you may be familiar with the concept of code splitting, which allows developers to break their application into smaller bundles and send less code to the client. Common approaches are lazily loading a bundle per route and/or lazily loading different modules based on some criteria at runtime. For example, applications may lazily load different code (in different bundles) depending on the user, content, feature flags, etc.:

// PhotoRenderer.js
// NOTE: *before* Server Components

import { lazy } from 'react';

// one of these will start loading *when rendered on the client*:
const OldPhotoRenderer = lazy(() => import('./OldPhotoRenderer.js'));
const NewPhotoRenderer = lazy(() => import('./NewPhotoRenderer.js'));

function Photo(props) {
  // Switch on feature flags, logged in/out, type of content, etc:
  if (FeatureFlags.useNewPhotoRenderer) {
    return <NewPhotoRenderer {...props} />; 
  } else {
    return <OldPhotoRenderer {...props} />;
  }
}

Code splitting can be very helpful in improving performance, but there are two main limitations to existing code-splitting approaches. First, developers have to remember to do it at all, replacing regular import statements with React.lazy and dynamic imports. Second, this approach delays the point at which the application can start loading the selected component, offsetting some of the benefit of loading less code!

Server Components address these limitations in two ways. First, they make code splitting automatic: Server Components treat all imports of Client Components as potential code-split points. Second, they allow developers to make the choice of which component to use much earlier, on the server, so that the client can download it earlier in the rendering process. The net effect is that Server Components let developers focus more on their app and write natural code, with the framework optimizing delivery of the app by default:

// PhotoRenderer.js - Server Component

// one of these will start loading *once rendered and streamed to the client*:
import OldPhotoRenderer from './OldPhotoRenderer.js';
import NewPhotoRenderer from './NewPhotoRenderer.js';

function Photo(props) {
  // Switch on feature flags, logged in/out, type of content, etc:
  if (FeatureFlags.useNewPhotoRenderer) {
    return <NewPhotoRenderer {...props} />;
  } else {
    return <OldPhotoRenderer {...props} />;
  }
}

No Client-Server Waterfalls

A common cause of poor performance occurs when applications make sequential requests to fetch data. For example, one pattern for data fetching is to initially render a placeholder and then fetch data in a useEffect() hook:

// Note.js
// NOTE: *before* Server Components

function Note(props) {
  const [note, setNote] = useState(null);
  useEffect(() => {
    // NOTE: loads *after* rendering, triggering waterfalls in children
    fetchNote(props.id).then(noteData => {
      setNote(noteData);
    });
  }, [props.id]);
  if (note == null) {
    return "Loading";
  } else {
    return (/* render note here... */);
  }
}

When both a parent and child component use this approach, however, the child can’t start loading any data until the parent component finishes loading its data. There are some positive aspects of this pattern though. One upside of fetching data in individual components is that it allows an application to fetch exactly the data it needs and avoid fetching data for parts of the UI that aren’t rendered. We wanted to find a way to avoid sequential round trips from the client while still avoiding over-fetching of data that wouldn’t be used.

Server Components allow applications to achieve this goal by moving sequential round trips to the server. The problem isn’t really the round trips, it’s that they are from client to server. By moving this logic to the server we reduce the request latency and improve performance. Even better, Server Components allow developers to continue fetching the minimal data they need directly from within their components:

// Note.js - Server Component

async function Note(props) {
  // NOTE: loads *during* render, w low-latency data access on the server
  const note = await db.notes.get(props.id);
  if (note == null) {
    // handle missing note
  }
  return (/* render note here... */);
}

Waterfalls are still not ideal on the server, so we will provide an API to preload data requests as an optimization. However, since client-server waterfalls are particularly bad for performance, we find not having to worry about them an important benefit.

Avoiding the Abstraction Tax

React’s use of JavaScript rather than a templating language allows developers to use language features such as function composition and reflection to create powerful UI abstractions. However, when overused these abstractions can come at a cost of more code and more runtime overhead. UI frameworks in “static“ languages can exploit ahead-of-time compilation to compile away some of these abstractions, but this option isn’t available in JavaScript.

To address this challenge we initially experimented with one approach to ahead-of-time (AOT) optimization — Prepack — but ultimately that direction did not pan out. Specifically, we realized that many AOT optimizations don’t work because they either don’t have enough global knowledge or they have too little. For example, a component might be static in practice because it always receive a constant string from its parent, but the compiler can’t see that far and thinks the component is dynamic. Even when we could make optimizations work, we found that they were unpredictable to the developer. Without predictability it was hard for developers to rely on them.

Server components help to address this by removing the abstraction cost on the server. For example, if a Server Component with multiple layers of wrappers for configurability ends up rendering to a single element, then that's all that will be sent to the client. In the next example, Note uses an intermediate NoteWithMarkdown abstraction that renders down to a wrapping <div>, so React would send just the div (and its contents) to the client:

// Note.js
// ...imports...

async function Note({id}) {
  const note = await db.notes.get(id);
  return <NoteWithMarkdown note={note} />;
}

// NoteWithMarkdown.js
// ...imports...

function NoteWithMarkdown({note}) {
  const html = sanitizeHtml(marked(note.text));
  return <div ... />;
}

// client sees:
<div>
  <!-- markdown output here -->
</div>

Distinct Challenges, Unified Solution

As noted above, a theme of these challenges was that React was primarily client-centric. Using traditional server rendering — PHP, Rails, etc — is certainly one way to address some of these challenges, but that approach makes it much harder to build rich, interactive experiences. This reflects a long-standing tension in the world of application development that predates the web: whether to use “thin” or “thick” clients.

Ultimately, we realized that neither pure server rendering or pure client rendering were sufficient. Server rendering allows applications to easily access server-side data sources and to quickly show static content, while client rendering is critical for rich, interactive features where users expect immediate feedback. But mixing server and client rendering often means mixing technologies: writing code in two languages, using two frameworks, keeping two sets of idioms and ecosystems in mind. It also means dealing with cross-language data transfer and often requires duplicating logic, once for server-rendered views and once for int

Excerpt — the full document is at the cited source: text/0188-server-components.md