Adding Native Modules: Config Plugin vs. Manual Native Configuration
In Expo, the need to touch native code is resolved in two ways: npm packages containing native code are linked automatically via Expo Autolinking; for packages that require additional native configuration (Info.plist permissions, Android manifest entries, Gradle settings), you write a config plugin or use the package's own plugin. The official definition is clear: a config plugin lets you automatically configure the native project generated during the prebuild process beyond the default app config props — meaning the same native project is regenerated every time prebuild runs.
In bare React Native, native packages are also linked via autolinking: the official documentation's flow is simply npm install <package> + npx pod-install + rebuilding the app, without writing a Podfile or Gradle entry by hand. What remains manual is configuration: Info.plist permission keys, AndroidManifest entries, Gradle settings. Because these live inside the native folders, they must be preserved by hand on every RN version upgrade. In short, the difference isn't in linking but in where configuration lives: in Expo, "write a config plugin once, it's applied on every prebuild"; in bare RN, "keep every native configuration in the project and preserve it through upgrades."
CNG/Prebuild: Generating Native Folders From Scratch
Continuous Native Generation (CNG) is Expo treating the native folders as "compiled output" rather than "source." When you run npx expo prebuild --clean, the android and ios folders are generated from scratch based on the app config (app.json/app.config.js) and the installed config plugins. The official documentation summarizes the practical result this way: the RN version upgrade process now consists only of updating npm dependencies and the app config, then rerunning prebuild --clean. The native folders may not be committed to git (.gitignore); CI regenerates them on every build.
In bare React Native, the android/ios folders are a permanent, manually maintained part of the project. This is exactly why React Native's web-based "Upgrade Helper" tool exists: you have to manually apply the native file differences (Podfile, build.gradle, AppDelegate) between each minor version. This provides more transparency (you always see exactly what's in the native project) but makes version upgrades noticeably more labor-intensive compared to CNG — especially since RN itself ships a minor version every few months (0.85 in April, 0.86 in June, 0.87 in August 2026).
EAS Build/Update Cost and the Free Tier
EAS's Free plan includes 15 Android + 15 iOS builds per month (30 total), OTA update delivery to up to 1,000 MAU, and 100 GiB of edge bandwidth — with a low-priority build queue. For a small-to-mid-sized project building a few times a week, this is usually enough. Once a team grows or CI is triggered frequently, there are two tiers: Starter ($19/month + $45 build credit, high-priority queue, 3,000 MAU) and Production ($199/month + $225 build credit, 2 concurrent builds, 50,000 MAU, SSO, priority support). Overage fees are $0.10/GiB for bandwidth and $0.05/GiB for storage.
An important nuance: EAS Build and EAS Update aren't Expo-specific — the official documentation defines EAS Workflows as a CI/CD service "for React Native and Expo apps." So a bare RN project can use EAS too. But without CNG, you have to manually sync the native project state (build credentials, native configuration) to EAS; in Expo projects this sync flows automatically through the app config. Not using EAS and instead building your own CI (Fastlane, Bitrise, GitHub Actions + Xcode/Gradle scripts) from scratch is also a common choice in bare RN — its cost is time and maintenance overhead, not a direct subscription fee.
OTA Updates and the Expo Go Login Change
EAS Update splits a build into two layers: the native layer embedded in the binary, and the "mutable" update layer between them — small bug fixes and JS changes reach users through the channel and deployment mechanism without waiting for a native version upgrade. The expo-updates library can also be manually integrated into a bare RN project (Expo modules can be used in bare RN too), but it isn't default/automatic — the developer sets up their own OTA solution.
Starting September 3, 2026, an important change came to the iOS version of Expo Go: login became mandatory both in the terminal's Expo CLI and in the Expo Go app to run a project. This distinction is critical — the official changelog explicitly states that development builds are exempt from this requirement, and that it currently only affects the latest Expo Go version on iOS. In other words, running a development or production build produced with EAS Build doesn't depend on the login requirement; this friction only exists in Expo Go, the quick-prototyping tool, and only on iOS. Bare RN projects have no such "quick preview app" layer at all, so this discussion doesn't touch it.
New Architecture and RN 0.87 Strict TypeScript Adoption Speed
React Native 0.87 (August 11, 2026) made the Strict TypeScript API the default JavaScript API and raised the minimum toolchain requirements to Node.js 22, Android Gradle Plugin 9, and Kotlin 2.0+; it also added experimental Swift Package Manager support. New Architecture, meanwhile, started as an experimental opt-in in 0.68; it was made default with RN 0.76 (October 23, 2024) and declared production-ready in the same announcement — so by 0.87, both setup models get it by default.
There's an important distinction here: Expo SDKs ship locked to a specific RN core version, meaning SDK 57 (June 30, 2026) carries RN 0.86, not RN 0.87. This means Expo projects haven't gotten RN 0.87's Strict TS default yet and have to wait for the next SDK release — there's no official commitment date. Expo projects get this kind of upgrade "bundled" with an SDK update, while bare RN projects can upgrade each minor RN version (0.85→0.86→0.87) independently and immediately. Third-party packages using deep imports will throw type errors on the move to Strict TS — in bare RN you experience this right away, in Expo you experience it along with the SDK upgrade.
CI/CD: EAS Workflows vs. Your Own Pipeline
EAS Workflows is a CI/CD service that automates recurring tasks like Android/iOS binary builds, publishing OTA updates, submitting to the stores, running E2E tests, and web deploys to EAS Hosting. It runs in managed cloud environments and can be triggered by GitHub events (push, PR) or cron schedules. The official documentation states it can be used for both React Native and Expo projects — meaning a bare RN project can also hook into EAS Workflows.
The difference shows up in how the "pre-packaged" jobs are designed: in Expo/CNG projects, EAS can manage the native build process end to end by reading the app config. In a bare RN project, the native project state (signing certificates, provisioning profiles, Gradle configuration) has to be manually synced to EAS — you provide here what CNG generates automatically. Alternatively, bare RN teams often build a pipeline from scratch with Fastlane + Bitrise or GitHub Actions + their own Xcode/Gradle scripts; this loses the "pre-packaged" convenience EAS offers but gives full control over the pipeline and fits enterprise on-prem requirements (e.g., a mandatory self-hosted runner).
Vendor Lock-in and Time From Setup to First Build
The official documentation treats the concept of "eject" as deprecated; all Expo projects now use Continuous Native Generation, and expo prebuild has taken its place. The practical meaning of this change: as long as you don't touch the native folders by hand, prebuild can always regenerate the same native project — meaning "leaving managed" is no longer a one-way door but a repeatable operation. If you remove a config plugin, the native folders are regenerated without it. There's flexibility in the other direction too: thanks to expo-modules-core, Expo modules can also be added to bare RN projects afterward — meaning the move "from bare to Expo tooling" isn't a one-way door either.
The setup-threshold difference is concrete: the only system requirement for an Expo project is Node.js LTS — after npx create-expo-app, you can test instantly via Expo Go or Snack without a native SDK installed. In bare RN, the official documentation points you to the "environment-setup" section: installing Xcode + CocoaPods, Android Studio + SDK + Gradle is a prerequisite for the first build. This difference determines not just the first day, but the setup cost paid every time by a developer newly joining the team and by whoever sets up the CI environment.