Edit this page
Learn about managing your native projects with Continuous Native Generation (CNG) and Prebuild.
A single native project on its own is complicated to maintain, scale, and update. In a cross-platform app, you have multiple native projects that you must maintain and keep up to date with the latest operating system releases to avoid falling too far behind in any third-party dependencies.
As your native project grows, the complexity from third-party dependencies increases, complicating upgrades and slowing down developer momentum. This discourages adding advanced native functionality and leads to less powerful apps. In cross-platform apps, this complexity is multiplied across each platform.
To address this, we've introduced the concept of Continuous Native Generation. Instead of creating native projects a single time and maintaining customizations to those native projects for the lifetime of the codebase, short-lived native projects are generated only when needed, such as when debugging or building. These projects are generated from a standard template plus configuration or custom code that defines how the template should be customized. The result in a native project that can be compiled into a native app with any customizations desired by the developer. However, the developer is responsible for only maintaining the definition of their customizations, rather than all of the native project code.
React Native apps can use CNG by using Prebuild to automate upgrades, install or uninstall libraries, apply white label customizations, share configuration across multiple apps, reduce orphaned code, and more.
Expo as a framework enables CNG by combining the following tools:
npx expo prebuild
command.expo
that's installed in the project and corresponding prebuild template.The end result is a workflow where a developer can express any native application with the app config and generate that project continuously — by running npx expo prebuild
.
Prebuild can be used by running:
-Â
npx expo prebuild
This creates the android and ios directories for running your React code. If you modify the generated directories manually then you risk losing your changes the next time you run npx expo prebuild --clean
. Instead, use config plugins , which are functions that perform modifications on native projects during prebuild.
We highly recommend using Prebuild for the reasons listed in the common questions section, but the system is fully optional, and you can stop using it at any time.
If your project does not contain android and ios directories, EAS Build will run Prebuild to generate these native directories before compilation. This is the default behavior for any project created using npx create-expo-app
.
For a project that has android and ios directories, EAS Build will not run Prebuild to avoid overwriting any changes you've made to the native directories.
If you troubleshoot your app by compiling it locally (running npx expo prebuild
, or npx expo run:android
or npx expo run:ios
), you can still use Prebuild with EAS Build to generate fresh native directories during the build process. In this scenario, add the android and ios directories to .gitignore or .easignore files:
+ /android
+ /ios
You can perform a native build locally by running:
# Build your native Android project
-Â
npx expo run:android
# Build your native iOS project
-Â
npx expo run:ios
If native directories are absent, npx expo prebuild
will run once for the specific platform. On subsequent uses of these run
commands, manually run npx expo prebuild --clean
to ensure the native code is freshly synchronized with your local configuration.
Prebuild currently supports Android and iOS. Web support is not required because there is no native project to generate for the web and the web app it runs in a web browser. Use the --platform
option to run prebuild for individual platforms:
-Â
npx expo prebuild --platform ios
Prebuild begins by initializing new native projects from a template corresponding to each Expo SDK version. This also aligns with specific React and React Native versions. You will see a warning when running npx expo prebuild
when your project's React and React Native versions differ from the expected versions from specified in the dependencies
field of the template's package.json if they differ.
You can skip changing npm package versions with the --skip-dependency-update
option:
-Â
npx expo prebuild --skip-dependency-update react-native,react
When the dependencies are changed, Prebuild will reinstall libraries using the package manager that is currently used in the project (this is inferred from the lockfile). You can force a specific package manager by providing one of: --npm
, --yarn
, --pnpm
.
All installations can be skipped by passing the --no-install
command, which is useful for testing generation quickly.
The --clean
option deletes any existing native directories before generating. Re-running npx expo prebuild
without the --clean
option will layer changes on top of the existing files, which is faster, but may not produce the same results in some cases.
For example, some config plugins aren't idempotent. When a project utilizes multiple "dangerous modifiers" to add regex changes to an app's code, it can lead to unexpected behavior. This is why using the --clean
option is the safest way to use the prebuild command and is generally recommended in most cases.
--clean
optionWhen using the --clean
option, you'll be warned if you have any uncommitted changes to your git code repository, as this option will delete and recreate all of your native project files. This prompt is optional and will be skipped when encountered in CI. You can disable this check by enabling the environment variable EXPO_NO_GIT_STATUS=1
.
There are cases where developers may want to swap between workflows often. For example, you may want to build custom functionality natively in Android Studio and Xcode, and then move that functionality into local config plugins.
You can customize how the native directories are generated by config plugins. Many config plugins already exist for lots of modifications, and community libraries often ship their own as well. You can see a list of some popular plugins for more information.
Prebuild starts from template files, which are then modified with config plugins. The template files are based on the Expo SDK version and come from the npm package expo-template-bare-minimum
. You can change the template used by passing --template /path/to/template.tgz
to the npx expo prebuild
command. This is not generally recommended because the base modifiers in @expo/prebuild-config
make some undocumented assumptions about the template files, so it may be tricky to maintain your custom template.
Note: In network environments where all packages are downloaded from a private registry and npm public registry access is blocked, a locally-available template must be passed to the prebuild command. Learn more about using a local version of the default template.
npx expo prebuild
performs several side effects outside of generating the android and ios directories. Work is in progress to eliminate these side effects — ideally, running npx expo prebuild
would generate the Android and iOS projects and leave the rest of the project untouched.
In addition to generating the native directories, prebuild also makes the following modifications:
scripts
field in the package.json to replace expo start --android
and expo start --ios
with expo run:android
and expo run:ios
dependencies
field in the package.jsonThe convenience change to the scripts
field is the only side effect that alters how a developer works on their app before/after prebuild. All other changes can be left in place and committed to git to minimize the diff when running prebuild.
Prebuild is optional and works seamlessly with all Expo tools and services. For existing React Native projects, where the native projects are managed manually, do not use npx expo prebuild
, as that may overwrite any manual customizations. Developers can continue to make (direct changes to their native projects) while adopting other Expo tools and workflows. Later on, they can move their manual customizations to app config and/or config plugins, and then adopt CNG.
Everything offered by Expo including EAS, Expo CLI, and the libraries in the Expo SDK are built to fully support bare React Native projects as this is a minimum requirement for supporting projects using npx expo prebuild
. The only exception is the Expo Go app, which can load arbitrary React Native projects only if they include JavaScript fallbacks for native code absent in the Expo Go runtime.
React Native developers who don't use Continuous Native Generation have reported that upgrading their apps to the latest version of React Native is the number one weakness of the library as per React Native Survey (2022).
When using CNG, the upgrade process simply involves upgrading the npm dependencies, app config, and re-running npx expo prebuild --clean
.
No, CNG is a versatile pattern that can be applied to any native project. While Expo Prebuild is a tool that implements CNG specifically for React Native projects, the concept itself is not limited to this framework.
Here are a few community examples of difficult native features converted into simple configuration files, which have allowed developers to build more powerful apps without compromising on iteration speed:
iOS Safari Extensions: Here, the process of creating a Safari Extension for iOS, which is a notoriously difficult feature to implement, is reduced to a few of lines of JSON.
iMessage Sticker App: This Expo config plugin can generate an entire iMessage Sticker App from a JSON object.
Cross-platform end-to-end testing: Configure native apps to support E2E testing with Detox in a single-line.
The entire Firebase suite: Here you can see the entire native Firebase suite going from a multi-step native configuration process across multiple IDEs, down to basic JSON configuration.
Cross-platform home screen widgets: This Expo config plugin can generate a home screen widget for Android and iOS.
Notification extension and code signing: This Expo config plugin generates a notification extension target on iOS and it augments the EAS credentials service to keep zero-config code signing working.
Apple App Clips: This Expo config plugin takes the process of generating an Apple App Clip from a multi-step process, ranging across multiple targets, and reduces it to a single line ["react-native-app-clip", { "name": "My App Clip" }]
.
At any point, these features can be easily added and removed, without any side effects. CNG allows developers to experiment with complex features and iterate on them quickly without worrying about the long-term maintenance costs or potential orphaned code in their project.
Absolutely! CNG is an abstract concept that can be applied to any operating system. Although Expo Prebuild officially implements CNG for Android and iOS, it also provides abstract platform support for developers to create implementations for additional platforms.
Not at all. CNG is an open pattern that can be adopted by any community. We've defined the pattern abstractly to help other communities understand how they can adopt CNG for their own projects.
CNG shares similarities with SSG in that it generates a project from a set of inputs. However, CNG differs from SSG in its output. It generates native runtime code instead of static website code. This means the native project is generated on-demand, and the generated source code and configuration are discarded once the native project is compiled into a native app.
CNG is designed to manage the entire state of a native project continuously. As a result, it's not intended for use with existing brownfield projects. However, you can use CNG to generate a new native project, which can then be integrated into an existing brownfield project.
Expo Prebuild streamlines CNG processing. Here are some issues in the React Native development cycle that are addressed by Prebuild:
Building native code requires familiarity with the platform's tooling, creating a steep learning curve. This challenge intensifies in cross-platform development due to multiple platforms. Cross-platform tooling doesn't help if you must implement many features in platform-specific native code.
When bootstrapping a native app, there's initial code and configuration that you may not understand. Yet you are not responsible for maintaining it. Eventually, you'll need to understand this code to upgrade your app safely. This challenge often leads developers to upgrade incorrectly or start a new app, copying existing source code.
With Prebuild, upgrading is much closer to upgrading a pure JavaScript application. Bump the versions in your package.json, regenerate the native project, and you should be ready to continue development.
Cross-platform configurations such as the app icon, name, splash screen, and so on must be implemented manually in native code. These implementations are often quite different for each platform.
With Prebuild cross-platform configurations are handled at the config plugin level, and the developer only needs to set a single value like "icon": "./icon.png"
to have all icon generation taken care of.
Many complex native packages require additional setup beyond installing and autolinking. For example, a camera library requires permission settings to be added AndroidManifest.xml for Android and Info.plist for iOS. This additional setup can be considered a configuration side effect of a package. Pasting the required side effect code into your project's native files can lead to difficult native compilation errors, and it's also code that you now own and maintain.
With Prebuild library authors, who know how to configure their library better than anyone, can create a testable and versioned script called a config plugin, to automate adding the required configuration side effects for their library. This means library side effects can be more expressive, powerful, and stable. For native code side effects, we also provide Android Lifecycle Listeners and AppDelegate Subscribers which come standard in the default prebuild template.
When you uninstall a package, you have to be certain you removed all of the side effects required to make that package work. If you miss anything, it leads to orphaned code that you cannot trace back to any particular package, this code builds up and makes your project harder to understand and maintain.
With Prebuild the only side effect is the config plugin in a project's Expo config (app.json), which will throw an error when the corresponding node module has been uninstalled, meaning a lot less orphaned configuration.
Here are some reasons Expo Prebuild might not be the right fit for a particular project:
Prebuild can only be used for native platforms that are supported by the Expo SDK. This means Android and iOS for the time being. Except for web, which doesn't require npx expo prebuild
since it uses the browser instead of a custom native runtime.
All native changes must be added with native modules (using React Native's built-in Native Module APIs or the Expo Modules API) and config plugins. This means if you want to quickly add a native file to your project to experiment, then you may be better off running prebuild and adding the file manually, then working your way back into the system with a monorepo. We plan to speed this process up by adding functionality to Expo Autolinking that finds native project files outside of the native directories and links them before building.
If you want to modify the configuration, such as the gradle.properties file, you'll have to write a plugin (example). This can be easily automated with helper plugin libraries, however, it is a bit slower if you need to do it often.
Not all packages support Expo Prebuild yet. If you find a library that requires extra setup after installation and doesn't yet have a config plugin, we recommend opening a pull request or an issue so that the maintainer is aware of the feature request.
Many packages, such as react-native-blurhash
, don't require any additional native configuration beyond what is handled by autolinking and so no config plugin is required.
Other packages, such as react-native-ble-plx
, do require additional setup and therefore require a config plugin to be used with npx expo prebuild
(in this case there's an external plugin called @config-plugins/react-native-ble-plx
).
Alternatively, we also have a repo for out-of-tree config plugins which provides plugins for popular packages that haven't adopted the system yet. Think of this like DefinitelyTyped for TypeScript. We prefer packages ship their own config plugin, but if they haven't adopted the system yet, the community can use the packages listed in the repo.