Replace AppRegistry with registerRootComponent
Change the entry file (index.js) to use `registerRootComponent` from 'expo' instead of `AppRegistry.registerComponent`. Replace `import {AppRegistry} from 'react-native'` and `AppRegistry.registerComponent(appName, () => App)` with `import {registerRootComponent} from 'expo'` and `registerRootComponent(App)`.
Prebuild automatically links expo-modules-core
Adopting prebuild will automatically add support for developing modules with the Expo native module API by linking expo-modules-core natively.
Analyze production mode bundle with Expo Atlas
To get a more accurate representation of production bundle size, start the development server in production mode using EXPO_ATLAS=true npx expo start --no-dev. This disables development-mode optimizations to match production behavior.
Analyze production bundle with Expo Atlas export
Run EXPO_ATLAS=true npx expo export to generate a .expo/atlas.jsonl file during export. Open this file with npx expo-atlas .expo/atlas.jsonl. The atlas.jsonl file can be shared and opened without project access.
Lighthouse CLI for web performance analysis
Lighthouse is a tool to measure website speed, accessibility, and performance. After creating a production build with npx expo export -p web and serving it, run the Lighthouse CLI with npx lighthouse <url> --view to analyze the site.
source-map-explorer bundle analysis paths for SDK 50
For SDK 50 projects without Expo Router server output, bundle files are located at dist/_expo/static/js/<platform>/*.js with source maps at dist/_expo/static/js/<platform>/*.js.map. For server output projects, web bundles are at dist/client/_expo/static/js/web/ to prevent exposing server code.
Avoid publishing source maps to production
Do not publish source maps to production builds. Source maps can cause security issues (exposing code structure) and performance issues (large file downloads).
Platform-specific analysis with Expo Atlas export
When using npx expo export with Expo Atlas, specify platforms with the --platform option. Expo Atlas will gather data only for the exported platforms specified.
Inspect transformed modules in Expo Atlas
Inside Expo Atlas, hold Cmd (Mac) and click on a graph node to see transformed module details. This reveals how a module is transformed by Babel, which modules it imports, and which modules import it, helping trace dependency origins across the graph.
source-map-explorer for SDK 50 and earlier
For SDK 50 and below, source-map-explorer is an alternative method to visualize and analyze the production JavaScript bundle. It is not recommended for SDK 51 and above where Expo Atlas should be used instead.
source-map-explorer NODE_OPTIONS workaround
If source-map-explorer shows error 'You must provide the URL of lib/mappings.wasm' on Node.js 18+, set environment variable NODE_OPTIONS=--no-experimental-fetch before running the analyze script.
Expo Atlas overview and purpose
Expo Atlas is a tool used to visualize the production JavaScript bundle and identify which libraries contribute to the bundle size. It helps improve bundle performance by showing the impact of each dependency on the final bundle size.
Enable Expo Atlas with development server
To use Expo Atlas with the local development server, set the environment variable EXPO_ATLAS=true when starting the dev server with npx expo start. Once the app is running on Android, iOS, or web, open Atlas through the dev tools plugin menu using Shift + M.
Export production bundle with source maps
To analyze bundle size with source-map-explorer, run npx expo export with the --source-maps flag. For native apps using Hermes, also use --no-bytecode to disable bytecode generation and allow JavaScript analysis.
source-map-explorer unmapped bytes warning
A warning like 'Unable to map 809/13787 bytes (5.87%)' is normal in source-map-explorer. It occurs because source maps exclude bundler runtime definitions like __d(() => {}, []). This is consistent and not a concern.
Apple notification for missing privacy manifest
As an alternative to manually finding required reasons, Apple notifies developers after they submit a build with missing privacy manifest files or specific reasons. You can wait until you receive a notification email from Apple and then include the required reasons listed in the email in your app's PrivacyInfo.xcprivacy file or the configuration in your app.json file.
Testing privacy manifest
You can test the privacy manifest by building your app and submitting it, either through the App Store review process or to TestFlight's external review. Apple will email you within a few minutes of submitting if your app is missing any required reasons for the APIs used.
Update Expo SDK libraries for privacy manifest support
Make sure you have updated your Expo SDK libraries to the latest versions for your SDK version using npx expo install --fix to ensure privacy manifest configuration is properly supported.
Configuring privacy manifest in app.json
You can include an iOS privacy manifest by using the privacyManifests field under expo.ios in your app config. The field takes an object with NSPrivacyAccessedAPITypes array containing objects with NSPrivacyAccessedAPIType and NSPrivacyAccessedAPITypeReasons properties.
Privacy manifest app.json configuration example
Example configuration:
{
"expo": {
"name": "My App",
"slug": "my-app",
"ios": {
"privacyManifests": {
"NSPrivacyAccessedAPITypes": [
{
"NSPrivacyAccessedAPIType": "NSPrivacyAccessedAPICategoryUserDefaults",
"NSPrivacyAccessedAPITypeReasons": ["CA92.1"]
}
]
}
}
}
}
Restricted reason APIs covered by privacy manifest
The APIs that require privacy manifest declarations currently include accessing UserDefaults, file timestamp, system boot time, disk space, and active keyboard. Apple considers this an open list that can be expanded in the future.
Expo SDK packages include their own PrivacyInfo files
All Expo SDK packages that use required reason APIs have a PrivacyInfo file included in the package directory. However, Apple does not correctly parse all PrivacyInfo files included by static CocoaPods dependencies. You may need to include the required reasons for the APIs used by those dependencies in your app's PrivacyInfo.xcprivacy file or in the configuration in app.json.
Finding required reasons for third-party libraries
You can identify the required reasons for APIs used by third-party libraries by checking if the library has a PrivacyInfo.xcprivacy file in the node_modules/package_name/ios directory. If it does, you can check the NSPrivacyAccessedAPITypes and NSPrivacyAccessedAPITypeReasons values in that file and copy those values to your configuration.
Privacy manifest file name and location
A privacy manifest is a file named PrivacyInfo.xcprivacy that is included in your iOS native project. This file is used to declare why the app includes native code that calls into certain APIs that Apple considers sensitive.
React Native TV support for Android TV and Apple TV
React Native is supported on Android TV and Apple TV through the React Native TV project, which is a comprehensive core repo fork with support for phone and TV targets, including Hermes and Fabric. Using the React Native TV library as the react-native dependency in an Expo project makes it capable of targeting both mobile (Android, iOS) and TV (Android TV, Apple TV) devices.
Apple TV system requirements
Apple TV development requires Node.js (LTS) on macOS, Xcode 16 or later, and tvOS SDK 17 or later. The tvOS SDK 17 or later is not installed automatically with Xcode and can be installed with xcodebuild -downloadAllPlatforms.
Android TV system requirements
Android TV development requires Node.js (LTS), Android Studio Iguana or later, an Android TV system image (API version 31 or later) selected in the Android Studio SDK manager, and an Android TV emulator created using that image. For Apple silicon, choose the ARM 64 image; otherwise choose the Intel x86_64 image.
Create Expo TV project from template
Create a new TV project using: npx create-expo-app MyTVProject -e with-tv (npm), yarn create expo-app MyTVProject -e with-tv (yarn), pnpm create expo-app MyTVProject -e with-tv (pnpm), or bun create expo MyTVProject -e with-tv (bun).
Create Expo TV Router project from template
Create a new TV project with Expo Router for file-based navigation using: npx create-expo-app MyTVProject -e with-router-tv (npm), yarn create expo-app MyTVProject -e with-router-tv (yarn), pnpm create expo-app MyTVProject -e with-router-tv (pnpm), or bun create expo MyTVProject -e with-router-tv (bun).
tvOS App Store credentials and provisioning profiles
tvOS uses the same bundle IDs and distribution certificates as iOS and other Apple platforms, but does not use the same provisioning profiles as iOS. Since EAS tooling only creates iOS provisioning profiles, two approaches are available: (1) For tvOS-only projects, create the bundle ID and upload the distribution certificate on the project website, then create a tvOS provisioning profile on Apple's developer website using that bundle ID and upload it to the EAS project website. (2) For projects targeting both tvOS and iOS, use local credentials for tvOS builds, as EAS-stored credentials will be needed by iOS builds.
Example TV projects
Available example TV projects include: IgniteTV (a project generated with the Ignite CLI that can be built for mobile or TV), SkiaMultiplatform (demonstrates React Native Skia on mobile, TV, and web), and NativewindMultiplatform (demonstrates using TailwindCSS styling on mobile, TV, and web).
Supported Expo SDK libraries for TV
TV applications work with the following Expo SDK libraries: AppleAuthentication, Application, Audio, Asset, AsyncStorage, AV, BackgroundTask, BlurView, BuildProperties, Constants, Crypto, DevClient, Device, Expo UI, FileSystem, FlashList, Font, GlassEffect, Image, ImageManipulator, KeepAwake, LinearGradient, Localization, Manifests, MediaLibrary, NetInfo, Network, Reanimated, SafeAreaContext, SecureStore, Skia, SplashScreen, SQLite, Svg, SystemUI, TaskManager, TrackingTransparency, Updates, Video, and VideoThumbnails. TV also works with React Navigation, React Native Skia, and many other third-party React Native libraries.
Build for Apple TV
Run the following command to build and run the app on an Apple TV simulator: npx expo run:ios (npm), yarn expo run:ios (yarn), pnpm expo run:ios (pnpm), or bun expo run:ios (bun).
Modify react-native dependency for TV in SDK 55 and earlier
In package.json for SDK 55 and earlier, modify the react-native dependency to: "react-native": "npm:react-native-tvos@0.83-stable" and add "expo": { "install": { "exclude": ["react-native"] } } to exclude react-native from version validation. The react-native-tvos version must match the Expo SDK. If upgrading an Expo TV project to SDK 55 or earlier, the react-native-tvos version must be upgraded manually—it will not be automatically updated by npx expo install expo@latest --fix.
Monorepo warning for TV projects
If you have more than one Expo project in a monorepo and one is modified for TV, all projects should be modified to use the React Native TV package as described, even if some projects are not configured to target TV. This avoids possible conflicts between project dependencies while still supporting mobile development on all projects.
DevClient support for TV platforms
The Expo DevClient library is only supported in SDK 54 and later. For Android TV, all operations are supported similar to an Android phone. For Apple TV, basic operations with a local or tunneled packager are supported, but authentication to EAS and listing of EAS builds and updates is not yet supported.
Run prebuild for TV
Set the EXPO_TV environment variable and run prebuild to make TV modifications: export EXPO_TV=1 followed by npx expo prebuild --clean. The --clean argument is recommended and required if you have existing Android and iOS directories in the project.
Revert TV changes and build for phone
Revert the TV changes and go back to phone development by unsetting EXPO_TV and running prebuild again: unset EXPO_TV followed by npx expo prebuild --clean.
Build for Android TV
Start an Android TV emulator and use: npx expo run:android (npm), yarn expo run:android (yarn), pnpm expo run:android (pnpm), or bun expo run:android (bun) to start the app on the emulator.
Render rich text with HTML
To display HTML content, you can use @expo/html-elements or a webview (react-native-webview).
Render rich text with nested Text components
You can take advantage of nesting Text components to render styles and layouts. This allows custom formatting and more control over text appearance.
Render rich text with native platform primitives
You can use Expo Modules API to write a custom renderer component with native platform primitives using third-party libraries such as Markwon on Android and AttributedString on iOS.
Render rich text with markdown
To display markdown content, you can use a markdown renderer such as react-native-enriched-markdown or another markdown renderer library.
Webview-based rich text editors tradeoffs
Webview-based rich text editors wrap an existing rich text editor built for web with JavaScript inside react-native-webview. They work on all platforms (Android, iOS, Web) and can take advantage of popular web editors, but they have a performance and UX penalty. You will not be able to use native UI components inside the editor, and implementing features like mentions or image embedding requires significant effort and duplication.
Rich text editor approaches overview
React Native has no default solution for rich text editing. Current approaches include webview-based editors, building on top of TextInput with nested Text components, markdown editors with visible styling markers, and native editors wrapped into React Native modules. Each approach has different tradeoffs between complexity, features, performance, and maintainability.
Native rich text editors for React Native
You can use native Android or iOS rich text editors wrapped into a React Native module. Options include react-native-aztec, gutenberg-mobile, react-native-live-markdown, react-native-enriched-markdown, and react-native-enriched-html. You can also wrap any native rich text editor using Expo Modules API, but if you use different ones on each platform, you need to unify their APIs and input formats.
Custom webview-based editor with web editors
You can build a custom webview-based editor with existing web-only editors like Quill, lexical, or slate. This requires handling message passing and web implementation yourself, but gives you all the options the underlying editor offers and lets you implement more features. Use message passing to pass text and onChange events to and from the webview. Model it as an uncontrolled component to prevent lag on each keystroke, and avoid serializing and sending the entire state on each keystroke.
TextInput with nested Text components limitation
React Native allows nested Text components as children of TextInput to render and edit styled text, and it is synchronous with the new React Native architecture. However, the TextInput component only returns a string in its onTextChange callback, which is a significant limitation. When editing, the callback cannot distinguish whether a new character belongs to styled or unstyled text. The onSelectionChange prop can provide this information but makes the task significantly harder, and inserting additional characters desynchronizes the selection.
Existing webview-based React Native rich text libraries
Easy-to-use existing React Native libraries for rich text editing include react-native-rich-editor, react-native-cn-quill, and @10play/tentap-editor. These are the easiest options if you need a basic rich text editor with limited configuration and don't have strict performance or UX requirements.
Markdown editors with visible styling markers
If using markdown to style text fulfills your requirement, you can render the markdown in a separate, non-editable view while editing. This is not complex to build using any markdown renderer. Libraries like react-native-markdown-editor exist for this. This editing experience suits power users or programming/tech applications, and you can explore hybrid approaches like showing markdown only in the selected chunk of text.
Rich text representation with abstract syntax trees
Rich text is usually represented using abstract syntax trees. For example, a bullet list can be a node of type bulleted-list with several children of type list-item. You can convert both HTML and markdown to a suitable AST format.
Expo restricts some Metro configuration options
Expo locks down some Metro configuration options to prevent projects from breaking. Not every option upstream Metro accepts can be customized or is supported. Expo also doesn't support loading Metro configs from YAML files (deprecated upstream) or any Metro configs located outside your repository.
Basic metro.config.js template
The basic metro.config.js file exports a Metro configuration by importing getDefaultConfig from 'expo/metro-config' and passing __dirname to it:
const { getDefaultConfig } = require('expo/metro-config');
const config = getDefaultConfig(__dirname);
module.exports = config;
Metro resolver.sourceExts and resolver.assetExts require explicit definitions
Metro requires all extensions for both source code and assets to be explicitly defined before starting the bundler. This is done by adding the `resolver.sourceExts` and `resolver.assetExts` options to the Metro configuration. Source code includes JavaScript, TypeScript, JSON, and other files used by your application. Assets are images, fonts, and other files that should not be transformed by Metro.
Add file extensions to resolver.assetExts
To add extra asset extensions to Metro, modify metro.config.js by pushing the file extension (without a leading dot) to the `config.resolver.assetExts` array. For example, to add support for SQLite databases:
config.resolver.assetExts.push('db');
Create metro.config.js to customize Metro bundler
You can customize the Metro bundler by creating a metro.config.js file at the root of your project. This file should export a Metro configuration that extends `expo/metro-config`. Import `expo/metro-config` instead of `@expo/metro-config` to ensure version consistency.
Expo CLI uses Metro for bundling
Expo CLI uses Metro during `npx expo start` and `npx expo export` to bundle JavaScript code and assets. Metro is built and optimized for React Native and is used for large-scale applications such as Facebook and Instagram.
Generate metro.config.js template
Run `npx expo customize metro.config.js` (npm), `yarn expo customize metro.config.js` (yarn), `pnpm expo customize metro.config.js` (pnpm), or `bun expo customize metro.config.js` (bun) to generate the template file.
Create module aliases with custom resolver
To redirect imports to different modules, use a custom resolver by setting `config.resolver.resolveRequest`. Due to the way Metro bundles for multiple platforms simultaneously, a custom resolver is recommended over other alias approaches. The resolveRequest function receives context, moduleName, and platform arguments, and should call context.resolveRequest with the appropriate module name.
Example of module aliases in metro.config.js
To alias 'old-module' to 'new-module':
const ALIASES = {
'old-module': 'new-module',
};
config.resolver.resolveRequest = (context, moduleName, platform) => {
return context.resolveRequest(
context,
ALIASES[moduleName] ?? moduleName,
platform
);
};