The React Native ecosystem has undergone a seismic transformation over the past two years. Expo is no longer the "beginner-friendly wrapper" it once was, and bare React Native with the Community CLI has matured into something far more stable. Yet the question persists in every greenfield project kickoff: should we use Expo or bare React Native?
Having shipped production applications with both stacks throughout 2025 and into 2026, I want to lay out an honest, nuanced comparison — free of hype and tribal loyalty. Let's dig in.
A Quick State of the Union
Before we compare, let's acknowledge how much the landscape has shifted:
- React Native 0.78+ ships with the New Architecture (Fabric renderer and TurboModules) enabled by default. The old bridge is officially deprecated.
- Expo SDK 53 (released Q1 2026) fully embraces the New Architecture, ships with React Native 0.78 under the hood, and introduces significant improvements to Expo Modules API v2.
- Expo Router v4 has become the de facto routing solution — even for many bare RN projects.
- EAS (Expo Application Services) now handles builds, submissions, updates, and monitoring with enterprise-grade reliability.
The line between "Expo" and "React Native" has blurred dramatically. Expo is now best understood as a platform built on top of React Native — not a separate framework.
Project Initialization and Setup
Starting a new Expo project in 2026 is remarkably fast:
// Terminal
npx create-expo-app@latest my-app --template tabs
cd my-app
npx expo startWithin seconds you have a working app with file-based routing, TypeScript, and hot reloading — no Xcode or Android Studio required for initial development.
Compare that with a bare React Native project:
// Terminal
npx @react-native-community/cli init MyApp
cd MyApp
// iOS setup
cd ios && bundle install && bundle exec pod install && cd ..
// Run
npx react-native run-iosThe bare setup still requires Xcode (with a valid Command Line Tools installation), CocoaPods, and a properly configured Android SDK. For experienced native developers, this is trivial. For JavaScript developers joining mobile for the first time, it can be a two-hour ordeal.
Verdict: Expo wins decisively on onboarding speed. The gap has not closed.
The Architecture Under the Hood
One of the most important shifts in 2026 is that both stacks share the same underlying engine. Expo SDK 53 runs React Native 0.78 with Fabric and TurboModules. There is no performance penalty for choosing Expo over bare RN at the rendering layer.
Here's what a TurboModule looks like when built with Expo Modules API v2:
// modules/haptics/index.ts
import { NativeModule, requireNativeModule } from 'expo-modules-core';
interface HapticsModule extends NativeModule {
impact(style: 'light' | 'medium' | 'heavy'): void;
notification(type: 'success' | 'warning' | 'error'): void;
}
const HapticsModule = requireNativeModule<HapticsModule>('Haptics');
export function impact(style: 'light' | 'medium' | 'heavy' = 'medium') {
HapticsModule.impact(style);
}
export function notification(type: 'success' | 'warning' | 'error') {
HapticsModule.notification(type);
}The corresponding Swift implementation is concise:
// modules/haptics/ios/HapticsModule.swift
import ExpoModulesCore
import UIKit
public class HapticsModule: Module {
public func definition() -> ModuleDefinition {
Name("Haptics")
Function("impact") { (style: String) in
let generator: UIImpactFeedbackGenerator
switch style {
case "light":
generator = UIImpactFeedbackGenerator(style: .light)
case "heavy":
generator = UIImpactFeedbackGenerator(style: .heavy)
default:
generator = UIImpactFeedbackGenerator(style: .medium)
}
generator.impactOccurred()
}
Function("notification") { (type: String) in
let generator = UINotificationFeedbackGenerator()
switch type {
case "success":
generator.notificationOccurred(.success)
case "error":
generator.notificationOccurred(.error)
default:
generator.notificationOccurred(.warning)
}
}
}
}Compare this with the traditional bare RN TurboModule approach, which requires writing a C++ spec file, a Codegen configuration, and separate Objective-C++ / Kotlin bridging code. The Expo Modules API abstracts away roughly 60–70% of the boilerplate.
Verdict: Same engine, same performance. Expo provides a superior developer experience for writing native modules.
Native Module Ecosystem and Compatibility
This was historically Expo's Achilles' heel. In 2023, if a library required custom native code and didn't have an Expo config plugin, you were stuck ejecting. In 2026, the situation is dramatically different:
- Expo config plugins now cover the vast majority of popular libraries (react-native-firebase, react-native-maps, react-native-ble-plx, and more).
- Continuous Native Generation (CNG) — Expo's paradigm where the
ios/andandroid/directories are generated from yourapp.jsonand config plugins — is now battle-tested in thousands of production apps. - For the rare library without a config plugin, writing one is well-documented and typically takes 30–100 lines of JavaScript.
Here's an example of a config plugin that adds a custom permission to the Android manifest:
// plugins/withBluetoothPermission.js
const { withAndroidManifest } = require('@expo/config-plugins');
module.exports = function withBluetoothPermission(config) {
return withAndroidManifest(config, (config) => {
const manifest = config.modResults.manifest;
if (!manifest['uses-permission']) {
manifest['uses-permission'] = [];
}
const permissions = [
'android.permission.BLUETOOTH_SCAN',
'android.permission.BLUETOOTH_CONNECT',
'android.permission.BLUETOOTH_ADVERTISE',
];
permissions.forEach((permission) => {
if (
!manifest['uses-permission'].some(
(p) => p.$['android:name'] === permission
)
) {
manifest['uses-permission'].push({
$: { 'android:name': permission },
});
}
});
return config;
});
};That said, bare React Native still offers unrestricted access to native project files. If your team has experienced iOS/Android engineers who want direct control over Podfile configurations, Gradle build flavors, or complex multi-module native architectures, bare RN provides zero abstraction between you and the native layer.
Verdict: Expo handles 95%+ of real-world use cases. Bare RN is still preferable for deeply custom native integrations or brownfield apps.
Over-the-Air Updates
This is where Expo has a commanding, almost unfair advantage. EAS Update allows you to ship JavaScript bundle updates directly to users without going through app store review:
// Terminal — ship a hotfix in under 60 seconds
eas update --branch production --message "Fix checkout crash"On the app side, updates are handled automatically with fine-grained control:
// app/_layout.tsx
import * as Updates from 'expo-updates';
import { useEffect } from 'react';
export default function RootLayout() {
useEffect(() => {
async function checkForUpdates() {
if (__DEV__) return;
const update = await Updates.checkForUpdateAsync();
if (update.isAvailable) {
await Updates.fetchUpdateAsync();
// Optionally prompt the user or reload silently
await Updates.reloadAsync();
}
}
checkForUpdates();
}, []);
// ... rest of layout
}With bare React Native, you can achieve OTA updates via Microsoft CodePush — but CodePush has been in maintenance mode since 2024 and its future is uncertain. Some teams have rolled their own solutions, but the operational overhead is significant.
Verdict: Expo wins. EAS Update is production-grade, well-maintained, and deeply integrated into the toolchain.
Build and CI/CD Pipeline
EAS Build is a cloud-based build service that compiles your native binaries without requiring local Xcode or Android SDK installations:
// eas.json
{
"build": {
"development": {
"developmentClient": true,
"distribution": "internal",
"ios": {
"simulator": true
}
},
"preview": {
"distribution": "internal",
"android": {
"buildType": "apk"
}
},
"production": {
"autoIncrement": true
}
},
"submit": {
"production": {
"ios": {
"ascAppId": "1234567890"
}
}
}
}// Terminal
eas build --platform all --profile production
eas submit --platform all --profile productionFor bare React Native, most teams set up Fastlane combined with GitHub Actions or Bitrise. This works well but requires substantial initial configuration — managing signing certificates, provisioning profiles, Gradle signing configs, and build variant logic manually.
Verdict: EAS Build is significantly easier to set up and maintain. Fastlane + custom CI is more flexible but demands dedicated DevOps effort.
Performance Benchmarks (2026)
Let's address the elephant in the room. In synthetic benchmarks comparing a managed Expo app versus an equivalent bare React Native app (both on RN 0.78, New Architecture enabled):
- JS thread FPS: Identical (both use Hermes with the same configuration)
- UI thread FPS: Identical (both use Fabric)
- Cold start time: Expo adds ~30–80ms overhead due to its module initialization. For most apps, this is imperceptible.
- Bundle size: Expo's tree-shaking improvements in SDK 53 have reduced the "Expo tax" to roughly 1.2 MB on iOS and 800 KB on Android — down from 5+ MB in 2023.
For performance-critical applications (real-time gaming, AR/VR, high-frequency trading interfaces), bare React Native's ability to drop into fully custom C++ TurboModules and fine-tune native compilation flags still provides an edge. For the other 98% of applications, the difference is negligible.
When to Choose Expo in 2026
- You're starting a greenfield project and want maximum velocity.
- Your team is primarily JavaScript/TypeScript engineers with limited native experience.
- You want OTA updates as a first-class capability.
- You want a managed CI/CD pipeline without heavy DevOps investment.
- You're building a standard mobile application — forms, lists, navigation, media, maps, push notifications, auth.
When to Choose Bare React Native in 2026
- You're integrating into an existing native app (brownfield).
- Your app requires deeply custom native modules that go beyond what config plugins can express — e.g., custom video pipelines, hardware SDK integrations, or proprietary C++ libraries.
- You need fine-grained control over the native build system (custom Gradle plugins, Swift Package Manager dependencies, build flavors).
- Your organization has dedicated iOS and Android engineers who prefer working directly in native tooling.
- You're building for platforms beyond iOS/Android (tvOS, visionOS) where Expo support is still maturing.
The Hybrid Approach: The Best of Both Worlds
Here's what many experienced teams are doing in 2026 — and what I recommend as the default starting point:
- Start with Expo using Continuous Native Generation.
- Use Expo Router for navigation and deep linking.
- Write custom native modules using the Expo Modules API when needed.
- Write config plugins for any native configuration that CNG doesn't handle out of the box.
- Eject to bare only if you hit a genuine wall that config plugins and Expo Modules cannot solve — which, in 2026, is increasingly rare.
This approach gives you Expo's developer experience and tooling while preserving full escape hatches to native code when business requirements demand it.
A Real-World Project Structure
my-app/
├── app/ # Expo Router file-based routes
│ ├── (tabs)/
│ │ ├── index.tsx # Home screen
│ │ ├── search.tsx # Search screen
│ │ └── profile.tsx # Profile screen
│ ├── _layout.tsx # Root layout
│ └── modal.tsx # Modal route
├── modules/ # Custom Expo native modules
│ └── haptics/
│ ├── index.ts
│ ├── ios/
│ │ └── HapticsModule.swift
│ ├── android/
│ │ └── HapticsModule.kt
│ └── expo-module.config.json
├── plugins/ # Expo config plugins
│ └── withBluetoothPermission.js
├── components/ # Shared UI components
├── hooks/ # Custom React hooks
├── services/ # API and business logic
├── app.json # Expo configuration
├── eas.json # EAS Build configuration
└── tsconfig.jsonNotice: there are no ios/ or android/ directories. They're generated at build time from your declarative configuration. This is the power of CNG — your native projects become reproducible artifacts rather than hand-maintained snowflakes.
Final Verdict
In 2026, Expo is the right default choice for the vast majority of React Native projects. It is no longer a toy, a wrapper, or a compromise. It's a mature, production-grade platform that Google, Microsoft, Shopify, and countless startups rely on daily.
Bare React Native remains the right choice for a specific — and shrinking — set of use cases involving deep native customization, brownfield integration, or platform targets that Expo doesn't yet fully support.
The honest answer? Start with Expo. You'll know when — and if — you need to leave. And in 2026, the odds are that you won't.