Expo vs Bare React Native: Which Should You Choose?
If you've chosen React Native, the next decision is how to run it: through Expo's framework and services, or as a bare React Native project you configure yourself. This choice used to be a hard trade-off between convenience and capability; modern Expo has narrowed the gap dramatically, but real differences remain around native control, build infrastructure, and team shape.
Expo
Expo wins as the default for most new projects — and that's now the official recommendation from the React Native team as well. You get a curated set of native modules, config plugins that modify native projects without you touching Xcode or Android Studio, cloud builds and app-store submission through EAS, and over-the-air updates for shipping JS fixes instantly. For small teams without dedicated native engineers, Expo removes entire categories of build-system pain. Continuous native generation means the native projects are reproducible artifacts, which keeps upgrades far saner than hand-maintained ones.
Bare React Native
Bare React Native wins when you need to own the native layer completely. Deep custom native modules, unusual build configurations, embedding React Native screens inside an existing native app, or integrating SDKs that fight with Expo's project structure — these are simpler when you manage the iOS and Android projects directly. Teams with in-house native engineers may prefer bare for the direct control, and organizations that can't route builds through a third-party cloud service avoid the EAS dependency entirely by owning their build pipeline.
What Expo actually adds — and what it no longer restricts
Expo is three things layered on React Native: a library of well-maintained native modules covering camera, notifications, sensors and more; a build philosophy called continuous native generation, where the native iOS and Android projects are generated from config rather than hand-edited; and EAS, a paid cloud service for builds, submissions, and over-the-air updates.
The historical objection — "Expo can't use custom native code" — is largely obsolete. Development builds let you include any native module, and config plugins script native project changes declaratively. What remains genuinely different is philosophy: in Expo, the native projects are disposable output you regenerate; in bare React Native, they're source code you maintain. That single distinction explains most of the practical differences teams feel during upgrades and native integrations.
Cost, team shape, and maintenance
Expo's economics favor small teams. Cloud builds mean nobody needs a Mac configured for iOS signing; over-the-air updates mean JS-level bugs get fixed in hours instead of waiting on app-store review; and React Native version upgrades — historically the most dreaded chore in this ecosystem — are substantially tamer when native projects are regenerated rather than manually migrated. EAS is a paid service, but for most teams it costs far less than the engineering time it replaces.
Bare projects shift those costs in-house. You maintain build configurations, provisioning, and native dependencies yourself, which is fine — even preferable — when you have native engineers who want that control, and burdensome when you don't. The honest framing: bare React Native isn't harder, it's just more of your team's time spent on infrastructure instead of product.
Migration paths and lock-in
The lock-in here is softer than most framework decisions. Ejecting from Expo — running prebuild once and committing the generated native projects — converts an Expo app into a bare one in an afternoon; you keep all your JS code and most Expo libraries, which work fine in bare projects. Going the other direction, adopting Expo in an existing bare app, is more work but well-documented and increasingly common.
EAS deserves separate consideration: builds and OTA updates are a service dependency with ongoing costs, and while alternatives exist — local builds, self-hosted update servers, other CI providers — switching takes real setup. My practical advice is to treat Expo the framework as near-zero lock-in and EAS the service as a vendor relationship you evaluate like any other, with an exit sketch in your back pocket.
Decision walkthrough by scenario
Startup MVP with a small JS-focused team: Expo, without hesitation — the weeks you don't spend on build infrastructure go into product, and OTA updates are a genuine growth-stage advantage for iterating fast. New app for a company with existing native iOS and Android engineers: still consider Expo first, but bare is defensible if those engineers will actively own the native layer.
Brownfield — adding React Native screens to an existing native app: bare React Native's integration path is the established route here. App with unusual requirements — custom build steps, exotic SDKs, hardware integrations: start with Expo development builds and config plugins, and drop to bare only when you hit a documented wall, not a rumored one. In my client work, the teams that regret bare chose it for flexibility they never used; the teams that regret Expo are rare and usually just needed to adopt development builds.
Decision checklist
- → Does your team include engineers comfortable owning Xcode and Android Studio projects?
- → Do you need over-the-air updates to ship fixes without store review?
- → Are you embedding React Native into an existing native app?
- → Is routing builds through a third-party cloud service acceptable to your organization?
- → Do any of your required SDKs genuinely lack Expo config plugin support?
- → How much team time can you afford to spend on build infrastructure?
- → Have you verified your 'Expo can't do X' assumptions against current Expo, not 2020's?
Frequently asked questions
Can Expo apps use custom native code?
Yes. Modern Expo supports any custom native module through development builds, and config plugins let you script native project modifications without hand-editing them. The old limitation — being confined to Expo's preset module list — applied to the classic managed workflow of years past. Today the practical question isn't whether Expo supports your native need, but whether a config plugin exists or you need to write one.
Is it hard to eject from Expo to bare React Native?
No — it's one command and some cleanup. Running prebuild generates the native iOS and Android projects, which you then commit and maintain yourself. Your JavaScript code is unchanged, and most Expo libraries continue working in bare projects. The bigger consideration is what you give up going forward: simplified upgrades and regenerated native projects. Many teams find they never need to eject at all.
Should I use Expo for a production app in 2026?
For most teams, yes — Expo is the officially recommended way to start a React Native app, and plenty of serious production apps ship on it. The genuine exceptions are brownfield integrations into existing native apps and projects whose native requirements conflict with continuous native generation. If you're starting fresh with a JS-centric team, Expo is the pragmatic default; choose bare deliberately, not defensively.