Beta: Universal React Server Components in Expo Router

Product10 minutes read

Evan Bacon

Evan Bacon

Engineering

This is a developer preview of universal React Server Components. For the first time ever, you can use React Server Components & Server Actions in native apps.

Universal React Server Components

Today we’re releasing the first developer preview of universal React Server Components in Expo Router! For the first time ever, developers can use React Server Components and Server Functions in both their native apps and websites.

This developer preview is meant for library authors to add support to their packages and will help us collect feedback. Production exports are not supported yet.

Why servers, why data fetching?

The top categories of apps using Expo and React Native are Finance, Shopping, Business, Food & Drinks, Sports, and News. Essentially all categories that are dominated by commerce and external content. That’s not to mention how impactful AI integrations have become—and there’s no signs of stopping. React Native itself is developed and used at Meta for Facebook Marketplace, Quest store, and Facebook/Instagram on Quest.

While React Native can be used to build any type of native app, it’s undeniably fantastic at experiences that connect users through/to a server.

But for the entire lifespan of React Native, it’s only ever had support for client-side rendering, and no first-class data fetching. In contrast, react-dom frameworks have supported a multitude of rendering methods and data fetching opinions.

It’s clear Expo Router needs something, but we don’t want just anything—we want the best possible solution we can think of. This means environment variables for secrets, server rendering, build-time static rendering, versioned endpoints, platform-specific server rendering, streaming UI, type-safe server endpoints, edge rendering, and declarative loading states.

This is where React Server Components come in.

What are React Server Components?

React Server Components (RSC) are an official React feature that enable you to render React components either at build-time from a dev server or request-time from a production host.

Big picture—this is React’s first-class solution for data fetching and rendering.

RSC works by running React code in a server environment (think Node.js, Expo CLI, Bun, Cloudflare) as opposed to a client environment (Browser, Hermes, Expo Go). They’re bundled in a special way to enable this, in the case of Expo Router, this is done using the extremely powerful Metro bundler.

When you invoke a React Server Function, it returns an “RSC Payload” which is a JSON-like format that can be interpreted by React to create React views. On native, we can think of this “RSC Payload” like HTML—it can be generated at build-time like static-site generation, or from a server like server-side rendering.

Expo Router and React Server Components

Since we first demoed Expo RSC at React Conf earlier this year, we heard lots of excited and skeptical sentiment from the community. All of this feedback has been carefully considered while engineering this first developer preview.

The main concerns were about offline support, user interactivity, cache control, and incremental adoption.

We iterated a lot to solve for each of these cases, especially incremental adoption of React Server Components in existing projects. Safe to say, we’re pretty excited by the results!

Unlike web-only frameworks that made React Server Components the default rendering environment, Expo Router enables you to use client components by default. This means users can expect all the same instant interactivity and offline support as before, along with fine-grained control of how the server is introduced and used.

How to use React Server Components

To render React Server Components, simply create a file with the “use server” directive at the top, and export functions that return JSX. These functions are known as React Server Functions, think of them like fully typed API routes. They run on the server, can only be async, and require a network connection to resolve.

Consider a situation where we want to render a user profile in our app. We know this will require a network request and possibly a secret key. With RSC in Expo Router, we can do this easily.

First, we’ll create a file and add “use server” to the top of it.

Here, we can securely fetch data on the server with any secret environment variable from our .env files, then use our data to render React Server Components which are streamed back to the client:

Code
// functions/profile.tsx
"use server"
import { Image } from 'react-native';
export async function renderProfile(username: string) {
// Securely perform data fetching and access secrets...
const profile = await fetch('...').then(res => res.json());
// Return some React JSX——these are React Server Components.
return <Image source={{ uri: profile.picture }} />
}

In our app directory, we can import these type-safe server functions from our client components and call them to perform data fetching and server rendering.

We don’t need to use any hooks here, instead wrapping the Server Function in React Suspense to resolve the promise and show a loading state while the results stream back to the app:

Code
// app/profile.tsx
/// <reference types="react/canary" />
import { renderProfile } from '../functions/profile'
export default function Profile() {
const username = "evanbacon"
return (
<React.Suspense fallback={<Text>Loading...</Text>}>
{renderProfile(username)}
</React.Suspense>
)
}

And just like that, we’re rendering React Server Components on every platform! To refetch data, simply call the function again, or memoize the function to prevent calling it extraneously. This pairs perfectly with React Compiler to automatically memoize function calls!

Caching

You have full control over the caching and loading state of your data fetching. If you want to persist these results, that’s up to you. Errors and retries can be handled with React Error Boundaries.

This first version is very transparent about the lifecycle of data, there are no hidden caching systems. In the future, we’ll add more built-in caching strategies to streamline different use-cases.

Enabling the developer preview

You can enable the preview in an Expo Router app by setting experiments.reactServerFunctions to true in the project’s app.json.

Because Expo Go 52 now supports the React Native New Architecture, you can run React Server Components instantly in Expo Go or in the browser, without needing to perform a native build.

Learn more in the Expo docs: Using React Server Components.

Use client

When components are rendered from a server module (file marked with “use server”), they don’t have access to client-side APIs such as useState , useEffect , useContext , etc. They also don’t have access to native modules or views. To opt code out of the server, you can move it to a file that has “use client” at the top of it.

For example, if you want to stream a button or other interactive view from the server, move it to a client module:

Code
"use client"
import { Text } from 'react-native';
export function MyButton({ title }) {
return <Text onPress={() => { ... }}>{title}</Text>
}

Deployment

Production deployment is not supported during the developer preview. We’ll be enabling this soon.

Just like Expo API routes—the RSC server runs in the dev server during development. To keep your app working as expected in production, you’ll need to deploy the server code to a production server. This introduces challenges regarding versioning and platform-specific rendering. We’re actively working on a fully automated workflow for this and should have a solution for this in Q1 of 2025.

This doesn’t apply to any of the client components which are all aggressively optimized at build-time and embedded in the native binary for instant startup times without a network connection.

We’ve been working on a similar strategy for React Server Components that only need to be fetched once at build-time (think schedule or blog components), these should work fully offline in the future too.

Wider improvements

To make server components possible, we’ve needed to add many improvements across the Expo runtime, including:

  • React Native New Architecture support.
  • Native fetch streaming.
  • Universal bundling with Metro, including bundle splitting and tree shaking.
  • Adding missing networking primitives such as URL, and FormData.

Along with improving the development infrastructure creating tools like Expo Atlas, network debugging, jest testing for react-server environments, multi-dimensional server environments, and much more.

Regardless of whether you use React Server Components or not, you’ll be able to benefit from many of the new improvements.

What’s next?

The next step is to get libraries migrated over to RSC. This mostly involves adding the “use client” directive. Packages like React Navigation will need more work to support concurrent rendering which can be used for advanced server-side rendering on web, and reduced network waterfalls on all platforms.

In the future, we’ll enable support for using server components in the routes directory of Expo Router. This will streamline loading/error states, along with enabling build-time optimizations and better offline support.

Feedback

Thanks for reading, give Expo Router’s React Server Components support a try and report issues on the expo/expo repo. If a library doesn’t work, open a PR to add “use client” directives. You can learn more about adding support in the testing server components doc.

Developer trust is our top priority at Expo, we appreciate your patience as we continue to develop the preview into a production-ready solution over the course of 2025!

react server components
Expo Router
tree shaking
metro
data fetching
application security

React Native CI/CD for Android, iOS, and Web

Learn more