In a recent blog post we shared a preview of React Router with RSC support. On the surface, it might look like this simply means that Server Components are coming to React Router.
However, the implications are greater than you might expect.
With RSC, so much of what's required to implement React Router's Framework Mode is now provided by React itself. Similar to the architectural shift we made when Remixing React Router, we're introducing a more powerful RSC-powered Data Mode that brings most of Framework Mode's features to our lower level library APIs.
With this new architecture, Framework Mode can add RSC support in an opt-in/non-breaking fashion, all while becoming simpler under the hood and less coupled to any particular bundler. In this blog post, we'll dive into some of the details of how this works and what it means for you.
To learn more about how to setup React Server Components with React Router, check out our "React Server Components" docs.
Building server-rendered React apps has always involved solving a few key challenges:
Different tools have solved these problems in different ways. React Router's Framework Mode provides one approach. React Server Components offer another. And now, React Router is bringing these worlds together.
Let's look at how React Router Framework Mode and React Server Components each tackle these common problems:
Framework Mode: You return data from loader functions, and React Router handles getting it to your components.
// Framework Mode
export async function loader() {
const user = await getUser();
return { user };
}
export default function Profile() {
const { user } = useLoaderData();
return <h1>{user.name}</h1>;
}
React RSC: You pass props to "use client"
components from server components.
// React RSC
async function ProfileServer() {
const user = await getUser();
return <ProfileClient user={user} />;
}
React Router RSC: You get both options! Use whichever pattern fits your needs.
Framework Mode: Return promises from loaders and use the <Await>
component.
// Framework Mode
export function loader() {
let slowDataPromise = getSlowData();
return {
criticalData: await getCriticalData(),
slowData: slowDataPromise,
};
}
export default function Page() {
const { criticalData, slowData } = useLoaderData();
return (
<>
<h1>{criticalData.title}</h1>
<Suspense fallback={<Spinner />}>
<Await resolve={slowData}>
{(data) => <SlowContent data={data} />}
</Await>
</Suspense>
</>
);
}
React RSC: Pass promises as props and use await
on the server and use(promise)
on the client.
// React RSC
// page.tsx
export async function Page() {
const slowDataPromise = getSlowData();
const criticalData = await getCriticalData();
return (
<>
<h1>{criticalData.title}</h1>
<Suspense fallback={<Spinner />}>
<PageClient slowDataPromise={slowDataPromise} />
</Suspense>
</>
);
}
// page.client.tsx
"use client";
export function PageClient({ slowDataPromise }) {
const slowData = use(slowDataPromise);
return <PageWidget data={slowData} />;
}
React Router RSC: Again, both patterns are available to you.
Framework Mode: A routes.ts
config file processed by a bundler plugin generates a manifest that maps routes to chunks.
React RSC: Dynamic imports on the server combined with "use client"
directives mean the browser only downloads what it needs.
React Router's Framework Mode is powered by some clever bundler integration:
routes.ts
config fileThis approach has served us well, but it requires significant bundler integration and complexity.
React Router RSC takes a different approach that's actually simpler. Instead of relying on a bundler plugin and custom manifest system:
routes.ts
bundler plugins or build-time generation required)Notice that these are now just plain data structures and function calls. The result? A simpler, more direct architecture that allows React Router to be a library again for complex full stack apps, only possible thanks to RSC.
It's worth calling out that this simplification is useful even if you're not using server components. Even if all of your routes are client components, as they are today, this new architecture means that advanced usage of React Router will no longer require a bundler-specific Framework Mode plugin.
If you're using React Router in Framework Mode: Our intention is for the future of Framework Mode to be built on top of React Router RSC, so your existing code will continue to work. When the transition happens, it will be seamless from your perspective. No migration necessary!
If you're using React Router in Data/Declarative Mode: You can continue using our existing non-RSC library APIs to build more traditional SPAs and SSR apps, both now and into the future. In a future release, you'll also have the option of using an RSC-powered version of Data Mode.
If you're starting a new React Router project: React Router RSC is still unstable, so we currently recommend using the existing Framework Mode or data/declarative APIs.
If you're feeling adventurous: We'd encourage you to try our initial unstable RSC APIs and provide feedback, file issues, or even help fix any bugs you run into. We'd love to hear what you think!
We are planning RSC-enabled Framework Mode for Vite. This depends on some low-level bundler work, but when it lands, Framework Mode becomes much simpler:
routes.ts
config is used to generate RSC-friendly route config"use client"
partsThat's it. No more manifest generation, no more complex server entries. Your route modules and logic stay exactly the same, with new entry.browser.ts
, entry.ssr.ts
, and entry.rsc.ts
modules aligning with their respective module graphs.
React Router is getting simpler because React itself is doing more heavy lifting and expecting more from bundlers. RSC brings powerful patterns for data loading, streaming, and code splitting that complement what React Router already does well.
Not only is React Router becoming more powerful — inheriting React's new abilities to render components purely on the server — but it's now able to be used as a simple library for even more use cases, from SPAs all the way up to complex code-split SSR apps.
While we still intend to provide additional Framework Mode conveniences on top of RSC, these now become completely optional. We're confident that many of you will want to use React Router's RSC APIs directly to keep things more minimal and under your control. This also means that, unlike today, we'll much more easily be able to support full stack SSR apps across all RSC-enabled bundlers, not just Vite.
The future of React Router is bright, and we're excited to build it with you.