# React Server Components (RSC) Strategies in React Router: Framework vs Data Mode Explained

> Explore React Router RSC strategies: Framework Mode for Vite and Data Mode for custom bundlers. Understand the entry files and core APIs for efficient server rendering.

- Repository: [Remix/react-router](https://github.com/remix-run/react-router)
- Tags: deep-dive
- Published: 2026-03-06

---

**React Router provides two distinct RSC strategies—Framework Mode for turnkey Vite integration and Data Mode for custom bundler setups—both built around three standardized entry files ([`entry.rsc.tsx`](https://github.com/remix-run/react-router/blob/main/entry.rsc.tsx), [`entry.ssr.tsx`](https://github.com/remix-run/react-router/blob/main/entry.ssr.tsx), [`entry.client.tsx`](https://github.com/remix-run/react-router/blob/main/entry.client.tsx)) and unstable core APIs.**

React Router’s implementation of **React Server Components** (RSC) offers developers a choice between convention-based automation and low-level API control. According to the `remix-run/react-router` source code, these strategies map to the library’s two primary architectural modes, sharing common entry-point contracts while differing in bundler integration depth.

## RSC Framework Mode: The Turnkey Vite Integration

**RSC Framework Mode** provides a high-level, opinionated integration built atop the standard Framework Mode router. It targets developers who want a plug-and-play RSC experience with minimal custom bundler configuration.

### Core Components and APIs

The Framework Mode strategy centers on the `unstable_reactRouterRSC` Vite plugin, located in [`packages/react-router-dev/vite/rsc/plugin.ts`](https://github.com/remix-run/react-router/blob/main/packages/react-router-dev/vite/rsc/plugin.ts). This plugin registers virtual route modules, injects HMR runtime, and resolves the three entry files automatically.

Key APIs include:

- **`routeRSCServerRequest`** – Handles full document requests by fetching the RSC payload, rendering HTML via `RSCStaticRouter`, and returning a `Response`.
- **`RSCStaticRouter`** – Server-side router that receives the RSC payload and renders the component tree to a readable stream.
- **`RSCHydratedRouter`** – Client-side router that rehydrates server-rendered HTML and enables post-hydration server actions.
- **`createCallServer`** – Low-level helper used by [`entry.client.tsx`](https://github.com/remix-run/react-router/blob/main/entry.client.tsx) to wire up the `callServer` function for mutations.

### Vite Configuration

To enable RSC Framework Mode, register the plugin in your Vite configuration:

```typescript
// vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import rsc from "@vitejs/plugin-rsc";
import { unstable_reactRouterRSC as reactRouterRSC } from "@react-router/dev/vite";

export default defineConfig({
  plugins: [
    react(),
    // RSC plugin must appear before the official @vitejs/plugin-rsc
    reactRouterRSC(),
    rsc({
      entries: {
        client: "src/entry.browser.tsx",
        rsc:    "src/entry.rsc.tsx",
        ssr:    "src/entry.ssr.tsx",
      },
    }),
  ],
});

```

The `entries` object tells the plugin where to find the three entry points. If you place files named [`entry.rsc.tsx`](https://github.com/remix-run/react-router/blob/main/entry.rsc.tsx), [`entry.ssr.tsx`](https://github.com/remix-run/react-router/blob/main/entry.ssr.tsx), or [`entry.client.tsx`](https://github.com/remix-run/react-router/blob/main/entry.client.tsx) in your `app/` directory, the plugin automatically detects and uses them instead of the defaults.

### Customizing Entry Files

For custom behavior such as logging, authentication, or custom response headers, override the default entry files:

```typescript
// app/entry.rsc.tsx
import defaultEntry from "@react-router/dev/config/default-rsc-entries/entry.rsc";

export default {
  async fetch(request: Request) {
    console.log("RSC request →", request.url);
    const ctx = new RouterContextProvider(); // optional per-request context
    return defaultEntry.fetch(request, ctx);
  },
};

```

The same pattern applies to [`entry.ssr.tsx`](https://github.com/remix-run/react-router/blob/main/entry.ssr.tsx) and [`entry.client.tsx`](https://github.com/remix-run/react-router/blob/main/entry.client.tsx). The default implementations reside in `packages/react-router-dev/config/default-rsc-entries/`.

### Server-Component-First Routes

In Framework Mode, routes can export a `ServerComponent` instead of the standard default component:

```tsx
// src/routes/dashboard/route.tsx
import { Outlet } from "react-router";

export async function loader() {
  return { user: await fetchUser() };
}

// This runs **only** on the server
export function ServerComponent({ loaderData }: { loaderData: { user: User } }) {
  return (
    <>
      <h1>Welcome, {loaderData.user.name}</h1>
      <Outlet />
    </>
  );
}

```

When a route returns a Server Component, any client-only logic must be moved to a separate `"use client"` module and re-exported.

## RSC Data Mode: The Low-Level API Strategy

**RSC Data Mode** provides a lower-level API that mirrors the standard Data Mode router. It does not provide file-system routing or hot-module-replacement infrastructure. Instead, you manually configure the three entry points and call the core RSC APIs directly.

### Core APIs

All Data Mode APIs are prefixed with `unstable_` and located in [`packages/react-router/lib/rsc/server.rsc.tsx`](https://github.com/remix-run/react-router/blob/main/packages/react-router/lib/rsc/server.rsc.tsx):

- **`unstable_matchRSCServerRequest`** – Matches a request against a user-provided route config and returns an `RSCServerPayload`.
- **`unstable_routeRSCServerRequest`** – Handles the full request/response lifecycle, similar to `routeRSCServerRequest` but expects manual route configuration.
- **`unstable_RSCStaticRouter`** – Server-side router that renders the payload to HTML.
- **`unstable_RSCHydratedRouter`** – Client-side router for hydration.

### Implementation Example

A minimal Data Mode setup requires manual route configuration and entry point wiring:

```tsx
// src/routes/config.ts
import type { unstable_RSCRouteConfig as RSCRouteConfig } from "react-router";

export function routes() {
  return [
    {
      id: "root",
      path: "",
      lazy: () => import("./root/route"),
      children: [
        {
          id: "home",
          index: true,
          lazy: () => import("./home/route"),
        },
      ],
    },
  ] satisfies RSCRouteConfig;
}

```

```tsx
// src/entry.rsc.tsx
import {
  unstable_matchRSCServerRequest as matchRSCServerRequest,
  unstable_RSCStaticRouter,
} from "react-router";
import { routes } from "./routes/config";

export default async function handler(request: Request) {
  return matchRSCServerRequest({
    request,
    routes: routes(),
    // Use @vitejs/plugin-rsc runtime helpers
    createTemporaryReferenceSet,
    decodeAction,
    decodeFormState,
    decodeReply,
    loadServerAction,
    generateResponse(match) {
      return new Response(renderToReadableStream(match.payload), {
        status: match.statusCode,
        headers: match.headers,
      });
    },
  });
}

```

```tsx
// src/entry.ssr.tsx
import {
  unstable_routeRSCServerRequest as routeRSCServerRequest,
  unstable_RSCStaticRouter,
} from "react-router";
import { routes } from "./routes/config";

export async function generateHTML(request: Request, serverResponse: Response) {
  return routeRSCServerRequest({
    request,
    serverResponse,
    // Same RSC runtime helpers as above
    createTemporaryReferenceSet,
    decodeAction,
    decodeFormState,
    decodeReply,
    loadServerAction,
    // Render HTML from the static router
    async renderHTML(getPayload) {
      const payload = getPayload();
      return renderToReadableStream(<RSCStaticRouter getPayload={getPayload} />);
    },
  });
}

```

```tsx
// src/entry.browser.tsx
import {
  unstable_createCallServer as createCallServer,
  unstable_getRSCStream as getRSCStream,
  unstable_RSCHydratedRouter as RSCHydratedRouter,
} from "react-router";

setServerCallback(
  createCallServer({
    createFromReadableStream,
    createTemporaryReferenceSet,
    encodeReply,
  })
);

createFromReadableStream(getRSCStream()).then((payload) => {
  hydrateRoot(
    document,
    <RSCHydratedRouter payload={payload} />,
  );
});

```

**Key Difference:** In Data Mode you import the low-level helpers (`createTemporaryReferenceSet`, `decodeAction`, etc.) from `@vitejs/plugin-rsc` directly, whereas the Framework plugin injects them automatically.

### When to Choose Data Mode

Select RSC Data Mode when:

- You need to integrate with a non-Vite bundler (e.g., Webpack, Rollup).
- You want to keep the router configuration separate from the file system (e.g., generating routes from a database).
- You prefer to control the three entry points yourself without the extra virtual module layer.

## Shared Concepts Across Both Strategies

Regardless of which RSC strategy you choose, React Router enforces a consistent architecture:

**Entry-File Detection**

The framework plugin looks for `app/entry.rsc.ts(x)`, `entry.ssr.ts(x)`, and `entry.client.ts(x)`. If missing, default generated files from `packages/react-router-dev/config/default-rsc-entries/` are used.

**Virtual Route Modules**

In Framework Mode, each route’s exports transform into a virtual module (`?route-module` suffix). This enables HMR for server-only changes and provides a unified export shape. The transformation logic lives in [`packages/react-router-dev/vite/rsc/virtual-route-modules.ts`](https://github.com/remix-run/react-router/blob/main/packages/react-router-dev/vite/rsc/virtual-route-modules.ts).

**Hot Module Replacement (HMR)**

The plugin injects a small runtime (`unstable_rsc/runtime`) that forwards updates to the client via `window.__reactRouterRouteModuleUpdates`. See the `addRefreshWrapper` implementation in [`packages/react-router-dev/vite/rsc/plugin.ts`](https://github.com/remix-run/react-router/blob/main/packages/react-router-dev/vite/rsc/plugin.ts).

**Client-Server Boundaries**

Use the `"use client"` directive or the `server-only` / `client-only` imports from `@vitejs/plugin-rsc` to separate concerns. The documentation explicitly advises against the old `.server/.client` file-naming convention when using RSC Framework Mode.

**Server Functions**

Server-only functions annotated with `"use server"` can be called from client components. The RSC payload includes a `callServer` implementation set up by `createCallServer` in [`entry.client.tsx`](https://github.com/remix-run/react-router/blob/main/entry.client.tsx).

## Summary

- **Two distinct strategies** exist for React Server Components in React Router: **Framework Mode** for turnkey Vite integration and **Data Mode** for custom bundler control.
- Both strategies require three specific entry files: [`entry.rsc.tsx`](https://github.com/remix-run/react-router/blob/main/entry.rsc.tsx) (payload generation), [`entry.ssr.tsx`](https://github.com/remix-run/react-router/blob/main/entry.ssr.tsx) (HTML rendering), and [`entry.client.tsx`](https://github.com/remix-run/react-router/blob/main/entry.client.tsx) (hydration).
- **Framework Mode** uses the `unstable_reactRouterRSC` Vite plugin located in [`packages/react-router-dev/vite/rsc/plugin.ts`](https://github.com/remix-run/react-router/blob/main/packages/react-router-dev/vite/rsc/plugin.ts) to handle virtual modules and HMR automatically.
- **Data Mode** exposes low-level APIs like `unstable_matchRSCServerRequest` and `unstable_routeRSCServerRequest` from [`packages/react-router/lib/rsc/server.rsc.tsx`](https://github.com/remix-run/react-router/blob/main/packages/react-router/lib/rsc/server.rsc.tsx) for manual configuration.
- Both approaches support Server Components via `ServerComponent` exports (Framework) or manual payload handling (Data), with client boundaries defined by `"use client"` directives.

## Frequently Asked Questions

### What is the difference between RSC Framework Mode and RSC Data Mode in React Router?

**RSC Framework Mode** provides a high-level, convention-based integration with Vite, featuring automatic virtual route modules, hot module replacement, and file-system routing. **RSC Data Mode** offers low-level APIs that require manual configuration of routes and entry points, making it suitable for custom bundlers or non-Vite environments. Both use the same three entry files ([`entry.rsc.tsx`](https://github.com/remix-run/react-router/blob/main/entry.rsc.tsx), [`entry.ssr.tsx`](https://github.com/remix-run/react-router/blob/main/entry.ssr.tsx), [`entry.client.tsx`](https://github.com/remix-run/react-router/blob/main/entry.client.tsx)) but differ in how those files are resolved and executed.

### How do I enable React Server Components in a React Router project?

To enable RSCs, install the `@react-router/dev` package and add the `unstable_reactRouterRSC` plugin to your [`vite.config.ts`](https://github.com/remix-run/react-router/blob/main/vite.config.ts). Create the three required entry files ([`app/entry.rsc.tsx`](https://github.com/remix-run/react-router/blob/main/app/entry.rsc.tsx), [`app/entry.ssr.tsx`](https://github.com/remix-run/react-router/blob/main/app/entry.ssr.tsx), [`app/entry.client.tsx`](https://github.com/remix-run/react-router/blob/main/app/entry.client.tsx)) or rely on the defaults provided in `packages/react-router-dev/config/default-rsc-entries/`. For custom behavior, override the default entries by exporting a `fetch` function in [`entry.rsc.tsx`](https://github.com/remix-run/react-router/blob/main/entry.rsc.tsx) or the respective handlers in the other entries.

### What are the three entry files required for React Router RSC strategies?

React Router RSC requires three specific entry points: **[`entry.rsc.tsx`](https://github.com/remix-run/react-router/blob/main/entry.rsc.tsx)** generates the RSC payload for server requests and is located at [`packages/react-router-dev/config/default-rsc-entries/entry.rsc.tsx`](https://github.com/remix-run/react-router/blob/main/packages/react-router-dev/config/default-rsc-entries/entry.rsc.tsx) by default; **[`entry.ssr.tsx`](https://github.com/remix-run/react-router/blob/main/entry.ssr.tsx)** renders the payload to HTML for the initial document response; and **[`entry.client.tsx`](https://github.com/remix-run/react-router/blob/main/entry.client.tsx)** hydrates the HTML in the browser and sets up the `callServer` function for post-hydration actions. Both RSC strategies (Framework and Data) rely on this same entry-point contract.

### When should I use RSC Data Mode instead of Framework Mode?

Choose **RSC Data Mode** when you need to integrate with a non-Vite bundler like Webpack or Rollup, when you want to generate routes from a database or API rather than the file system, or when you require fine-grained control over the build pipeline without the virtual module layer that Framework Mode injects. Data Mode exposes low-level APIs like `unstable_matchRSCServerRequest` and `unstable_routeRSCServerRequest` from [`packages/react-router/lib/rsc/server.rsc.tsx`](https://github.com/remix-run/react-router/blob/main/packages/react-router/lib/rsc/server.rsc.tsx), requiring you to manually wire up the RSC runtime helpers from `@vitejs/plugin-rsc`.