Server-side code removal with typeof window === 'undefined'
The pattern 'typeof window === "undefined"' is used to conditionally enable or disable code for server and client environments. When bundling for server environments, babel-preset-expo transforms this check to 'true'. By default, the check remains unchanged when bundling for web client environments. You can configure babel-preset-expo to enable this transform by passing '{ minifyTypeofWindow: true }', though this remains disabled by default for web environments since web workers do not have a window global. This transform runs in both development and production but only removes conditional requires in production.
Remove development-only code with process.env.NODE_ENV and __DEV__
To exclude development-only code from production bundles, use the process.env.NODE_ENV environment variable or the non-standard __DEV__ global boolean. Code wrapped in conditionals like 'if (process.env.NODE_ENV === "development")' or 'if (__DEV__)' will be removed from the production bundle after constants folding and minification. This optimization only runs in production builds; conditionals are kept in development builds.
Platform shaking removes platform-specific code
Expo CLI employs platform shaking during app bundling to create separate bundles for each platform (Android, iOS, web). Code that is used conditionally based on Platform.select or Platform.OS directly imported from react-native is removed from other platforms. This optimization is production-only and runs on a per-file basis. If Platform.OS is re-exported through a different module, it will not be removed during bundling. The process.env.EXPO_OS can be used to detect the platform the JavaScript was bundled for, but it does not support platform shaking imports due to how Metro minifies code.
EXPO_PUBLIC_ environment variables for custom code removal
EXPO_PUBLIC_ environment variables are inlined before the minification process and can be used to remove code from the production bundle. When a variable like EXPO_PUBLIC_DISABLE_FEATURE=true is set in the .env file, conditionals checking this variable (e.g., 'if (!process.env.EXPO_PUBLIC_DISABLE_FEATURE)') will be transformed to literals and then removed during minification. This system does not apply to server code, and library authors should not use EXPO_PUBLIC_ environment variables since they only run in application code for security reasons.
Enable experimental tree shaking in SDK 52-53
To enable experimental tree shaking in SDK 52-53 (enabled by default in SDK 54+), perform the following steps: (1) Ensure experimentalImportSupport is enabled in metro.config.js by setting config.transformer.getTransformOptions to return an object with transform.experimentalImportSupport: true. (2) Set environment variable EXPO_UNSTABLE_METRO_OPTIMIZE_GRAPH=1 in .env to keep modules around until the entire graph is created. (3) Set environment variable EXPO_UNSTABLE_TREE_SHAKING=1 in .env. (4) Bundle your app in production mode using 'npx expo export'. Both environment variables only apply in production mode.
experimentalImportSupport uses custom Babel plugin transform
Experimental import support uses a custom version of the @babel/plugin-transform-modules-commonjs plugin that drastically reduces the number of resolutions and simplifies the output bundle. This feature can be used with inlineRequires to further optimize the bundle experimentally.
Star exports automatically expanded with Expo tree shaking (SDK 52+)
With Expo tree shaking enabled (SDK 52+), star exports like 'export * from "./icons"' are automatically expanded and shaken based on usage. The optimization pass crawls the exported module and adds the exports to the current module; unused exports are removed from the production bundle. If the star export pulls in ambiguous exports such as module.exports.ArrowUp or exports.ArrowDown, the optimization pass will not expand the star export and no exports will be removed.
Recursive module optimization with Expo tree shaking (SDK 52+)
Expo tree shaking optimizes modules by recursing through the graph exhaustively to find unused imports. If a function is not used in the app, it is removed, and the module is scanned again to see if other exports used only by that function can be removed. This process recurses up to 5 times for a given module before bailing out due to performance reasons.
inlineRequires with Expo tree shaking in metro.config.js
When Expo tree shaking is enabled, you can safely enable inlineRequires in metro.config.js for production bundles to lazily load modules when they are evaluated, leading to faster startup time. This is configured by setting config.transformer.getTransformOptions to return an object with transform.experimentalImportSupport: true and transform.inlineRequires: true. Avoid using inlineRequires without Expo tree shaking as it can change the execution order of side-effects.
Sanity CMS with Visual Editing for Expo and React Native
Sanity is a CMS option that supports Visual Editing in Expo and React Native apps, with documentation available at https://www.sanity.io/docs/visual-editing/visual-editing-with-react-native.
Strapi CMS integration with Expo
Strapi is a CMS option available for Expo app integration, with documentation available at https://strapi.io/integrations/expo.
Benefits of using a CMS with Expo and React Native
Integrating a CMS into your Expo and React Native app lets you remotely manage and update content, push out new information to users instantly, and scale your content operations without releasing new app updates. Using a CMS can save you significant development time and enable non-technical users such as editors and marketers to update app content easily through a user-friendly interface.
What a Content Management System (CMS) is
A Content Management System (CMS) is a platform that allows you to create, manage, and organize digital content such as blog posts, images, and product information without the need to write custom backend code.
PostHog works with Expo Go
PostHog is compatible with Expo Go and can be integrated without requiring a development build.
Analytics requires development build with Expo Go
Most analytics SDKs require configuring custom native code. Native code is not configurable when using Expo Go. However, you can create a development build, which will allow you to use any analytics service.
Astrolytics works with Expo Go
Astrolytics is compatible with Expo Go and can be integrated without requiring a development build.
Aptabase works with Expo Go
Aptabase Analytics is compatible with Expo Go and can be integrated without requiring a development build.
Analytics services available in Expo ecosystem
The following analytics services are available for Expo and React Native projects: Google Firebase Analytics, Segment, Amplitude, AWS Amplify, Vexo, Aptabase (works with Expo Go), Astrolytics (works with Expo Go), PostHog (works with Expo Go), and Dreambase.
Clerk authentication guide available
Expo provides a guide for adding Clerk authentication and user management to Expo and React Native projects.
Google authentication configuration available
Expo provides a guide for configuring @react-native-google-signin/google-signin to add Google authentication in Expo projects.
Facebook authentication configuration available
Expo provides a guide for configuring react-native-fbsdk-next to add Facebook authentication in Expo projects.
BugSnag integration guide location
The official BugSnag integration guide for Expo apps is available at https://docs.bugsnag.com/platforms/react-native/expo/. It provides instructions for adding BugSnag to Expo apps to report JavaScript errors and includes instructions for uploading source maps for updates published with EAS Update.
BugSnag capabilities for Expo
BugSnag provides end-to-end error reporting and analytics for Expo apps. It supports React Native and offers features including release health dashboards, stability scores and targets, built-in alerts via email, Slack and PagerDuty, error grouping by root cause, business impact analysis, diagnostic data collection, full stacktraces, and automatic breadcrumbs for reproduction.
BugSnag source maps support for EAS Update
BugSnag supports uploading source maps for updates published with EAS Update, enabling proper error stack trace translation for published versions of Expo apps.
Server Function example with async function
Example Server Function defined inline:
```tsx
export default function Index() {
return (
<Button
title="Press me"
onPress={async () => {
'use server';
console.log('Button pressed');
return '...';
}}
/>
);
}
```
React Server Components experimental status
React Server Components in Expo Router are experimentally available as a beta release and subject to breaking changes. This is an early preview of a feature that will be enabled by default in Expo Router.
EAS Hosting for Server Components deployment
For web deployment, build the web project with 'npx expo export -p web' then host it locally with 'npx expo serve' or deploy to EAS Hosting. For native, deploy servers to EAS following the native deployment guide.
React Server Components prerequisites
To use React Server Components in Expo, you need a project using Expo Router and React Native New Architecture. React Native New Architecture is required and is enabled by default from SDK 52.
Request headers access in Server Components
You can access request headers using the 'unstable_headers' function from 'expo-router/rsc/headers' module. The function returns a promise that resolves to a read-only Headers object.
unstable_headers limitation with static rendering
The unstable_headers API cannot be used with build-time rendering (render: 'static') because headers dynamically change based on the request. In the future, this API will assert if the output mode is static.
Full React Server Components mode
Full React Server Components mode is experimental. In this mode, the default rendering mode for routes is server components instead of client components. Enable it with the reactServerComponentRoutes flag in app.json under expo.experiments alongside reactServerFunctions.
Full Server Components mode limitations
In full React Server Components mode: there is currently no stack routing, custom layouts (Stack, Tabs, Drawer) do not support Server Components yet, and most Link component props are not supported yet.
router.reload() for Server Components
In full React Server Components mode, you can manually trigger a reload using router.reload() from the useRouter hook to refetch data or re-render the component.
Build-time rendering with render: 'static'
The render: 'static' option in unstable_settings will render the component at build-time and never re-render it in production. This is similar to how classic static site generators work. Routes marked with static output will be rendered at build-time and embedded in the native binary, enabling rendering routes without making a server request.
Dynamic rendering default behavior
The current default rendering mode for React Server Components is 'dynamic', which renders the component at request-time and re-renders it on every request.
React Server Components known limitations
Known limitations include: Expo Snack does not support bundling Server Components; EAS Update does not work with Server Components yet; DOM components cannot use React Server Functions in production yet; production deployment is limited and not recommended; server rendering RSC payloads to HTML is not supported; generateStaticParams is only partially supported; HTML form integration with Server Functions is not supported; StyleSheet.create and Platform.OS are not supported on native (use standard objects and process.env.EXPO_OS instead); React Server Functions invoking other Server Functions are not supported on Hermes.
Library compatibility with Server Components
Not all libraries are optimized for React Server Components. You can use the 'use client' directive to mark a file as a Client Component and use it in a Server Component to workaround compatibility issues. Re-export each module individually rather than using 'export * from' as this breaks interoperability between server and client.
use client modules and dot-access limitation
Modules marked with 'use client' cannot be dot-accessed from Server Components. This means operations like StyleSheet.create or Platform.OS will not work on the server without further optimization in the react-native package.
React Suspense in Server Components
You can stream back partial UI from the server while waiting for data to load by using React Suspense. Each Suspense boundary can have its own fallback, allowing incremental UI updates as different async components resolve.
Suspense control of loading state
Using Suspense boundaries enables you to control the loading state incrementally. Without a Suspense wrapper around a component, the page waits for all components to finish rendering before updating the UI.
server-only module for security
You can use the 'server-only' module to ensure a module never runs on the client. Importing this module will assert if the module runs on the client.
Environment variables in Server Components
You can access environment variables using process.env in Server Components. Define secrets in the .env file. You do not need to restart the dev server to update environment variables as they are automatically reloaded on every request.
Server Components example with async data fetching
Example Server Component that fetches data from an API:
```tsx
import 'server-only';
import { Image, Text, View } from 'react-native';
export async function Pokemon() {
const res = await fetch('https://pokeapi.co/api/v2/pokemon/2');
const json = await res.json();
return (
<View style={{ padding: 8, borderWidth: 1 }}>
<Text style={{ fontWeight: 'bold', fontSize: 24 }}>{json.name}</Text>
<Image source={{ uri: json.sprites.front_default }} style={{ width: 100, height: 100 }} />
{json.abilities.map(ability => (
<Text key={ability.ability.name}>- {ability.ability.name}</Text>
))}
</View>
);
}
```
Client Component example with use client
Example Client Component:
```tsx
'use client';
import { Text } from 'react-native';
export default function Button({ title }) {
return <Text onPress={() => {}}>{title}</Text>;
}
```
Server Function standalone file example
Example Server Functions in standalone file:
```tsx
'use server';
export async function callAction() {
// ...
}
```
These can be imported and used in Client Components.
Server Function rendering example with profile
Example Server Function that renders components on the server:
```tsx
'use server';
import 'server-only';
import { View, Image, Text } from 'react-native';
export async function renderProfile({
username,
accessToken,
}: {
username: string;
accessToken: string;
}) {
const { name, image } = await fetch(`https://api.example.com/profile/${username}`, {
headers: {
Authorization: `Bearer ${accessToken}`,
'X-Secret': process.env.SECRET,
},
}).then(res => res.json());
return (
<View>
<Image source={{ uri: image }} />
<Text>{name}</Text>
</View>
);
}
```
Client Component invoking Server Function example
Example Client Component that invokes a Server Function:
```tsx
'use client';
import { useLocalSearchParams } from 'expo-router';
import * as React from 'react';
import { Text } from 'react-native';
import { renderProfile } from '@/components/server-actions';
function Fallback() {
return <Text>Loading...</Text>;
}
export default function Profile() {
const { username } = useLocalSearchParams();
const { accessToken } = useCustomAuthProvider();
const profile = React.useMemo(
() => renderProfile({ username, accessToken }),
[username, accessToken]
);
return <React.Suspense fallback={<Fallback />}>{profile}</React.Suspense>;
}
```
Library compatibility workaround example
Example workaround for libraries not optimized for Server Components:
```tsx
'use client';
export { One, Two, Three } from 'react-native-unoptimized';
```
Use individual exports instead of 'export *' to avoid breaking interop between server and client.
Suspense streaming example with nested components
Example using Suspense to stream partial UI:
```tsx
// app/index.tsx (Client Component)
import { Suspense } from 'react';
import { renderMediumTask, renderExpensiveTask } from '@/actions/tasks';
export default function App() {
return <Suspense fallback={<Text>Loading...</Text>}>{renderTasks()}</Suspense>;
}
// actions/tasks.tsx (Server Functions)
'use server';
export async function renderTasks() {
return (
<Suspense fallback={<Text>Loading...</Text>}>
<>
<MediumTask />
<Suspense fallback={<Text>Loading...</Text>}>
<ExpensiveTask />
</Suspense>
</>
</Suspense>
);
}
async function MediumTask() {
await new Promise(resolve => setTimeout(resolve, 1000));
return <Text>Medium task done!</Text>;
}
async function ExpensiveTask() {
await new Promise(resolve => setTimeout(resolve, 3000));
return <Text>Expensive task done!</Text>;
}
```
Request headers access example
Example accessing request headers in Server Component:
```tsx
import { unstable_headers } from 'expo-router/rsc/headers';
export async function renderHome() {
const authorization = (await unstable_headers()).get('authorization');
return <Text>{authorization}</Text>;
}
```
Full Server Components mode configuration
To enable full React Server Components mode:
```json
{
"expo": {
"experiments": {
"reactServerFunctions": true,
"reactServerComponentRoutes": true
}
}
}
```
router.reload() example
Example using router.reload() to reload Server Components:
```tsx
'use client';
import { useRouter } from 'expo-router';
import { Text } from 'react-native';
export function Button() {
const router = useRouter();
return (
<Text
onPress={() => {
router.reload();
}}>
Reload current route
</Text>
);
}
```
Static rendering configuration example
Example static rendering configuration:
```tsx
import { Text, View } from 'react-native';
export const unstable_settings = {
render: 'static',
};
export default function Index() {
return (
<View>
<Text>Hello, world!</Text>
</View>
);
}
```
generateStaticParams example
Example generating static pages at build-time:
```tsx
import { Text } from 'react-native';
export const unstable_settings = {
render: 'static',
};
export async function generateStaticParams() {
return [{ shape: 'square' }];
}
export default function ShapeRoute({ shape }) {
return <Text>{shape}</Text>;
}
```
CSS import in Server Components example
Example importing CSS in Server Components:
```tsx
import './styles.css';
import styles from './styles.module.css';
export default function Index() {
return <div className={styles.container}>Hello, world!</div>;
}
```
Meta tags in Server Components example
Example using React 19 meta tags in Server Components:
```tsx
export default function Index() {
return (
<>
{process.env.EXPO_OS === 'web' && (
<>
<meta name="description" content="Hello, world!" />
<meta property="og:image" content="/og-image.png" />
</>
)}
<MyComponent />
</>
);
}
```
Initial route with Suspense example
Example initial route using Client Component and Server Function with Suspense:
```tsx
/// <reference types="react/canary" />
import React from 'react';
import { ActivityIndicator } from 'react-native';
import renderInfo from '../actions/render-info';
export default function Index() {
return (
<React.Suspense
fallback={
<ActivityIndicator />
}>
{renderInfo({ name: 'World' })}
</React.Suspense>
);
}
```
Basic Server Function example
Example basic Server Function:
```tsx
'use server';
import { Text } from 'react-native';
export default async function renderInfo({ name }) {
return <Text>Hello, {name}!</Text>;
}
```
CSS import support in Server Components
Expo Router supports importing global CSS and CSS modules in Server Components (in full React Server Components mode). The CSS will be hoisted into the client bundle from the server.
generateStaticParams for build-time pages
You can generate static pages at build-time using the generateStaticParams function. This is useful for components that must only run at build-time and not on the server.