On this page
The debate is over, but the hiring implications aren't.
The React Native core team no longer recommends starting new projects with react-native init. Expo is the recommended default for new projects in 2026. Approximately 83% of projects built with EAS Build use the New Architecture, and 70% of new React Native projects start with Expo's managed workflow. The old "Expo is for prototypes, bare is for production" advice is dangerously out of date.
But here's the problem with hiring: Expo-managed workflow and bare React Native still produce fundamentally different developer skill sets. A developer who has built exclusively in Expo has never configured a Podfile, never opened Xcode's build settings, never debugged a CocoaPods conflict, and never written a native module in Swift or Kotlin. Drop that developer into a bare workflow codebase with custom native bridges, and they'll be underwater within the first week.
This guide explains what each workflow involves in 2026, when each is the right choice for your project, and how the decision affects whom you should hire.
What Expo and Bare React Native Actually Mean in 2026
The terminology has shifted. In 2026, there are three workflows, not two.
Expo Managed Workflow. You never touch native code. Expo handles builds, signing, native module configuration, and over-the-air updates. Your developer writes TypeScript and uses Expo SDK APIs. When they need a native capability (camera, push notifications, maps, biometrics), they install an Expo SDK package or use a config plugin. They never open Xcode or Android Studio.
Expo Bare Workflow. You get Expo's tooling (EAS Build, EAS Update, Expo Router, Expo SDK) and full access to the native iOS and Android project files. Your developer can write custom native modules, modify Podfiles, and configure Gradle directly. This is the middle ground that didn't exist in 2020.
Bare React Native (no Expo). You manage everything yourself. Native builds happen locally in Xcode and Android Studio. OTA updates require a third-party solution (CodePush, which Microsoft has deprecated, or a custom system). Upgrades require manual changes to native configuration files. This is increasingly rare for new projects but still common in legacy codebases.
The practical difference for your developer:
# Expo managed: Build and submit to stores from the command line
# No Xcode, no Android Studio, no local certificates
eas build --platform all --profile production
eas submit --platform ios
eas submit --platform android
# Time: 30-60 minutes for first production build setup
# Bare React Native: Build locally
# Requires Xcode installed, certificates configured, provisioning profiles managed
open ios/MyApp.xcworkspace # configure signing, archive manually
cd android && ./gradlew bundleRelease # configure keystore, build manually
# Time: 2-4 hours for first production build (certificates, signing, environment)The 30-60 minute vs 2-4 hour difference in the first build repeats at smaller increments each day of development. Over a 6-month project, Expo's managed workflow saves 15-20% of total development time.
The Complete Feature Comparison
Before diving into benchmarks and code, here's the comprehensive side-by-side that covers every dimension hiring managers and CTOs ask about:
| Factor | Expo Managed | Expo Bare | Bare React Native (no Expo) |
|---|---|---|---|
| Setup time | Minutes (single CLI command) | 30-60 min (Expo + native project access) | Hours (Xcode, Android Studio, CocoaPods, Gradle) |
| Native code access | Through Expo Modules API and config plugins | Full access to ios/ and android/ directories | Direct access to all native project files |
| Build process | Cloud builds via EAS (no Xcode/Android Studio needed) | Cloud or local builds | Local builds requiring native tooling on every dev machine |
| OTA updates | Built-in through EAS Update | Built-in through EAS Update | Requires third-party (CodePush deprecated) or custom solution |
| Navigation | Expo Router (file-based, deep linking automatic) | Expo Router or React Navigation | Choose and configure manually |
| Native module dev | Swift/Kotlin through Expo Modules API | Swift/Kotlin directly or via Expo Modules | Swift/Kotlin/Java with manual linking |
| Upgrade path | CNG regenerates native project from config | Semi-managed through Expo CLI | Manual native file changes per version |
| Runtime performance | Identical (same New Architecture) | Identical | Identical |
| App size (minimal) | Under 3 MB on App Store | Under 3 MB | Under 2 MB |
| Learning curve | Low (JavaScript/TypeScript only) | Medium (JS + some native awareness) | High (JS + native tooling expertise) |
| Mac required for iOS | No (EAS cloud builds) | Optional (EAS or local) | Yes (Xcode is macOS only) |
| Device testing during dev | Expo Go app (scan QR, instant preview) | Custom dev client (build once, then instant) | Full native build for every test cycle |
Developer Salaries by Workflow
The workflow your project uses directly affects what you pay. These ranges reflect US-based positions from Glassdoor data for 2026:
| Experience Level | Expo-Focused Developer | Bare Workflow Developer | Bare + Native Modules (Swift/Kotlin) |
|---|---|---|---|
| Junior (0-2 years) | $72,000-$92,000 | $80,000-$100,000 | Rare at this level |
| Mid-level (3-5 years) | $95,000-$128,000 | $110,000-$140,000 | $120,000-$150,000 |
| Senior (5+ years) | $120,000-$155,000 | $135,000-$170,000 | $145,000-$185,000 |
| Bay Area / NYC premium | +$15,000-$25,000 | +$15,000-$25,000 | +$20,000-$30,000 |
The salary gap between Expo-focused and bare-workflow developers reflects the additional skills required: knowledge of native tooling, platform debugging, certificate management, and build configuration. The premium for native module development (Swift/Kotlin) is driven by the rarity of developers who bridge both JavaScript and native disciplines.
Development Cost: The Same App Built Three Ways
For a mid-complexity app (auth, data feeds, camera, push notifications, maps, offline storage) with a team of two developers over six months:
| Cost Category | Expo Managed | Bare Workflow | Difference |
|---|---|---|---|
| Environment setup | 1 day | 3 days | +2 days |
| Feature development | ~100 days | ~105 days | +5 days |
| Native configuration and debugging | ~5 days | ~15 days | +10 days |
| CI/CD pipeline setup | 2 days | 5 days | +3 days |
| App store submission | 3 days | 3 days | Same |
| Total development time | ~111 days | ~131 days | +20 days |
At $150/hour for a senior developer, those 20 extra days cost $24,000-$36,000 in developer time alone.
Monthly infrastructure costs also differ:
| Infrastructure | Expo | Bare React Native |
|---|---|---|
| Cloud builds | $29/month (EAS Production plan, unlimited) | $100-$300/month (GitHub Actions or Bitrise) |
| OTA updates | Included in EAS | $40/month (if using CodePush) or custom |
| Push notification service | Included in Expo SDK | $20-$50/month (OneSignal, FCM setup) |
| Build machines | Not needed (cloud builds) | $0 local or $200+/month CI |
| Monthly total | ~$29 | ~$160-$590 |
Over 12 months, the infrastructure cost difference is $1,600-$6,700 in Expo's favor. Combined with the development time savings, Expo's total cost advantage on a standard app is $30,000-$45,000 over a six-month project.
The Benchmarks That Actually Matter
Most "Expo vs bare" comparisons focus on feature lists. The numbers that actually affect your project are performance, app size, and build time.
| Metric | Expo Managed | Bare React Native | What It Means |
|---|---|---|---|
| Runtime performance | Identical | Identical | Both use the same New Architecture (Fabric, JSI, Turbo Modules). Benchmarks show a difference of less than 5% in 2026. Expo's overhead is in dev tools, not runtime |
| Cold startup | ~100ms slower on very old devices | Baseline | Negligible on modern devices. Not user-perceptible on any phone sold after 2021 |
| App size (minimal app) | Under 3 MB on App Store | Under 2 MB | 5-10% larger (2-4 MB difference). Irrelevant for most apps. Matters only for ultra-low-end devices in emerging markets |
| Build time (CI) | 8-15 min (EAS cloud) | 15-30 min (local or CI) | EAS cloud builds are faster because Apple Silicon runners handle iOS builds without your machine |
| Upgrade effort | Managed by Expo CLI | Manual native config changes | React Native version upgrades have historically been one of the most painful parts of the framework. Expo abstracts most of this |
| OTA updates | Built-in (EAS Update) | Requires third-party (CodePush deprecated) | OTA lets you push JS fixes without App Store review. Critical for fast bug response |
The key takeaway is that runtime performance is identical. The "Expo is slower" myth dates back to 2018, when it was true. In 2026, both workflows execute on the same Hermes engine, the same JSI bindings, the same Fabric renderer. The difference is developer experience, not app performance.
What production-scale Expo actually looks like
Benchmarks on paper are one thing. What matters is whether Expo holds up when 100,000+ people use your app every month.
Published production retrospectives from teams running Expo with the New Architecture consistently report numbers that match or exceed those of native apps. One cross-platform team shared their data after six months of production use with a six-figure user base:
| What They Measured | Result | What It Means for Hiring |
|---|---|---|
| App stability | Over 99% crash-free sessions across both platforms | Comparable to native iOS/Android apps. The "Expo isn't stable enough for production" argument doesn't hold |
| Development velocity | Roughly 14 hours per feature on average | Nearly 40% faster per feature than the same team's previous Flutter implementation. Team familiarity with React was the main driver |
| Communication overhead | Over 40% reduction in JS-to-native call latency | Bridgeless architecture (New Architecture) eliminates the serialization bottleneck that caused stuttering on older RN versions |
| CI/CD pipeline | Under 10 minutes for a production Android build | EAS Build on cloud runners. No local machine dependency |
| Critical bug response | Under 15 minutes from fix to all users receiving it | EAS Update OTA. No App Store review. This is the capability bare React Native cannot match without custom infrastructure |
The velocity number matters most for hiring decisions. Feature development speed correlates directly with the amount of native configuration overhead your developer carries. In Expo managed workflow, that overhead approaches zero. In bare React Native, it's a constant tax on every task that touches the build pipeline, certificates, or native dependencies.
Where the code difference shows up in practice
The real tradeoff between Expo and bare isn't theoretical. It shows up in the code your developer writes for everyday features. Here's a practical example: securely storing a user's session token to keep them logged in across app restarts.
// EXPO MANAGED: Secure token storage
// Total setup: install one package, write four lines
import * as SecureStore from 'expo-secure-store';
// Save token (encrypted by iOS Keychain / Android Keystore automatically)
await SecureStore.setItemAsync('session_token', token);
// Retrieve token on app launch
const savedToken = await SecureStore.getItemAsync('session_token');
// Delete on logout
await SecureStore.deleteItemAsync('session_token');
// Done. No Podfile edit. No Gradle change. No native code.
// BARE REACT NATIVE: Same feature, more moving parts
// Setup: install package, run pod install, configure Android, write adapter
import * as Keychain from 'react-native-keychain';
// Save token with platform-specific encryption
await Keychain.setGenericPassword('session', token, {
service: 'com.myapp.auth',
accessible: Keychain.ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
});
// Retrieve on app launch
const credentials = await Keychain.getGenericPassword({
service: 'com.myapp.auth',
});
const savedToken = credentials ? credentials.password : null;
// Delete on logout
await Keychain.resetGenericPassword({ service: 'com.myapp.auth' });
// But first, you also needed to:
// 1. npm install react-native-keychain
// 2. cd ios && pod install (and fix any CocoaPods version conflict)
// 3. Verify Android Keystore configuration in build.gradle
// 4. Handle the edge case where Keychain returns false instead of null on some Android versions
// 5. Test across 3+ Android manufacturers (Samsung, Xiaomi, and Pixel all behave differently)Both approaches store the token securely. Both encrypt using the OS keychain. The Expo version takes 5 minutes to implement. The bare version takes 30-60 minutes, including the native setup steps, and introduces platform-specific edge cases your developer needs to test for. Multiply that difference across every native feature in your app (push notifications, camera, biometrics, file system, haptics), and the cumulative time savings over a 6-month project are substantial.
Another real example: push notifications
Push notifications are in almost every production app. Here's what the setup looks like in each workflow:
// EXPO: Push notification setup
// Total time: ~20 minutes including testing
import * as Notifications from 'expo-notifications';
import * as Device from 'expo-device';
async function registerForPushNotifications() {
// Expo handles permission requests on both platforms
const { status } = await Notifications.requestPermissionsAsync();
if (status !== 'granted') return null;
// Get the push token (works on both iOS and Android)
const token = await Notifications.getExpoPushTokenAsync({
projectId: 'your-project-id',
});
return token.data;
}
// Listen for incoming notifications
Notifications.addNotificationReceivedListener((notification) => {
const { title, body } = notification.request.content;
// Handle the notification
});
// That's it. No native code. No Info.plist edits. No Gradle changes.
// Expo's config plugin handles APNs certificates and FCM setup.
# BARE: Push notification setup
# Total time: 2-4 hours including debugging
# 1. Install the package
npm install @react-native-firebase/app @react-native-firebase/messaging
# 2. iOS: Add GoogleService-Info.plist to ios/ directory via Xcode
# (drag and drop into Xcode, select "Copy items if needed")
# 3. iOS: Edit ios/Podfile - add Firebase pods
# pod 'Firebase/Messaging'
# 4. iOS: Run pod install (and fix version conflicts if they appear)
cd ios && pod install && cd ..
# 5. iOS: Edit AppDelegate.mm - add Firebase import and configure call
# #import <Firebase.h>
# [FIRApp configure];
# 6. iOS: Enable Push Notifications capability in Xcode
# Targets → Signing & Capabilities → + Capability → Push Notifications
# 7. iOS: Upload APNs key to Firebase Console
# 8. Android: Add google-services.json to android/app/
# 9. Android: Edit android/build.gradle - add Google services classpath
# 10. Android: Edit android/app/build.gradle - apply Google services plugin
# 11. Android: Create a notification channel for Android 13+
# Now you can write the JavaScript code (similar to Expo)
# But any mistake in steps 2-11 produces a silent failure
# where notifications simply don't arrive, with no error message.The 11-step bare setup isn't difficult for an experienced native developer. But each step is a potential silent failure point. If you miss step 6 (the Xcode capability toggle), notifications simply don't arrive on iOS, and there's no error message. If your google-services.json has a typo in the package name, Android notifications fail silently. Debugging these issues requires platform-specific knowledge that many React Native developers lack.
An Expo developer handles push notifications in 20 minutes because the config plugin manages all 11 of those steps automatically. A bare developer handles them in 2-4 hours and needs to know which Xcode panel to click and which Gradle file to edit. Both produce working push notifications. The question is whether those extra hours multiply across your entire feature set.
The same pattern repeats for camera access
// EXPO: Camera with permissions in one file
import { CameraView, useCameraPermissions } from 'expo-camera';
import { useState } from 'react';
import { View, Text, Pressable, StyleSheet } from 'react-native';
export default function CameraScreen() {
const [permission, requestPermission] = useCameraPermissions();
const [facing, setFacing] = useState<'front' | 'back'>('back');
if (!permission?.granted) {
return (
<View style={styles.container}>
<Text>Camera access is needed to take photos</Text>
<Pressable style={styles.button} onPress={requestPermission}>
<Text style={styles.buttonText}>Allow Camera</Text>
</Pressable>
</View>
);
}
return (
<CameraView style={styles.camera} facing={facing}>
<Pressable
style={styles.flipButton}
onPress={() => setFacing(f => f === 'back' ? 'front' : 'back')}
>
<Text style={styles.buttonText}>Flip</Text>
</Pressable>
</CameraView>
);
}
// Install: npx expo install expo-camera
// Permissions: handled by config plugin automatically
// No Info.plist edit. No AndroidManifest change. No pod install.
# BARE: Same camera screen requires native configuration
# 1. Install the package
npm install react-native-vision-camera
# 2. iOS: Edit ios/MyApp/Info.plist (add camera usage description)
# <key>NSCameraUsageDescription</key>
# <string>This app needs camera access to take photos</string>
# 3. iOS: Run pod install
cd ios && pod install && cd ..
# 4. Android: Edit android/app/src/main/AndroidManifest.xml
# <uses-permission android:name="android.permission.CAMERA" />
# 5. Android: Verify minSdkVersion >= 21 in android/app/build.gradle
# 6. Import and use (API is different from Expo's camera)
# import { Camera, useCameraDevice } from 'react-native-vision-camera';
# const device = useCameraDevice('back');
# <Camera device={device} isActive={true} style={StyleSheet.absoluteFill} />
# 7. Handle the common gotcha: camera preview is black on Android
# emulator because emulators don't have real cameras.
# Must test on physical device.The pattern is identical to push notifications: Expo handles the native configuration through config plugins, bare requires manual edits to platform-specific files. For a single feature, the difference is 20 minutes vs 1-2 hours. Across an app with camera, push notifications, location, biometrics, file system access, and haptics, the cumulative difference is measured in weeks.
What Expo Gives You That Bare Doesn't
These are the capabilities that make Expo the default choice for new projects. Each one represents work that your developer either gets for free (Expo) or has to build and maintain themselves (bare).
EAS Build. Cloud-based builds that handle code signing, provisioning profiles, and native dependencies. Your developer doesn't need a Mac to build for iOS. They don't manage certificates locally. The build runs on Expo's servers and produces a signed binary ready for the store.
EAS Update. Over-the-air updates that push JavaScript and asset changes directly to users without going through the App Store review process. When your developer fixes a critical bug on Friday afternoon, users get the fix within minutes, not after a 1-3-day review cycle. Here's what deploying an OTA fix looks like:
# Fix the bug in your TypeScript code
# Then push the update to all production users
eas update --branch production --message "Fix checkout crash on Android 14"
# Users get the fix on next app launch. No store review.
# Rollback if something goes wrong:
eas update:rollback --branch productionThis capability alone justifies Expo for most teams. The ability to ship a critical fix in 10 minutes instead of 3 days changes how you think about risk.
Expo Router. File-based navigation that maps directly to your screen structure. Deep linking works by default without days of configuration. If you've used Next.js, 80% of the mental model transfers.
// File structure IS your navigation structure
app/
(tabs)/
index.tsx // → /
profile.tsx // → /profile
settings.tsx // → /settings
product/
[id].tsx // → /product/123 (deep link works automatically)
_layout.tsx // Tab bar layout
// Deep link: myapp://product/456 → opens product screen with id=456
// No manual linking config. No URL scheme setup. It just works.Expo SDK. Over 70 APIs for camera, notifications, haptics, filesystem, sensors, biometrics, and more. Each is a cross-platform abstraction that handles iOS and Android differences internally. Your developer writes one API call. It works on both platforms.
Config Plugins. This is what killed the old "Expo can't do native" argument. Config plugins let you modify native iOS and Android project files from JavaScript, without ejecting and without manually editing Xcode or Gradle. Need to add a custom URL scheme, modify Info.plist, or add an Android permission? Write a config plugin. The native project is regenerated from your config on every build.
// app.json - adding a custom URL scheme without touching Xcode
{
"expo": {
"scheme": "myapp",
"plugins": [
["expo-camera", { "cameraPermission": "Allow $(PRODUCT_NAME) to access your camera" }],
["expo-notifications", { "icon": "./assets/notification-icon.png" }]
],
"ios": {
"infoPlist": {
"UIBackgroundModes": ["remote-notification", "fetch"]
}
}
}
}
// No Xcode required. The native project is generated from this config.Continuous Native Generation (CNG). This is the technical concept underlying everything above, and it's what makes Expo's managed workflow fundamentally different from bare React Native, not just "easier bare React Native."
In bare React Native, the ios/ and android/ directories are source code. Your developer edits them, commits them to Git, and maintains them across every React Native version upgrade. When React Native ships a new version, your developer has to reconcile the changes with their custom modifications manually. This process has historically been the most frustrating part of working with React Native, sometimes consuming entire days of debugging CocoaPods version mismatches and Gradle configuration conflicts.
In Expo's managed workflow, the native directories don't exist in your source code. They're generated fresh on every build from your app.json configuration and config plugins. The native project is a build artifact, not something your developer maintains.
The practical impact on upgrades:
# Bare React Native: upgrading from 0.74 to 0.76
# Read the upgrade helper diff for every file that changed
# Manually patch ios/Podfile, android/build.gradle, android/app/build.gradle
# Resolve CocoaPods conflicts (expect 2-3 per major upgrade)
# Fix native import paths that changed between versions
# Rebuild, test, fix what broke
# Budget: half a day to two full days depending on version gap
# Expo: upgrading SDK version
npx expo install expo@latest --fix
# CNG regenerates the native project with correct configuration
# Your config plugins re-apply your customizations automatically
# Budget: 20-40 minutes including smoke testing on both platformsThis is why teams that maintain bare React Native codebases often describe upgrades as their least favorite part of the job. It's also why developers who have only worked with Expo may underestimate the difficulty of maintaining a bare project. Both experiences are real. Understanding which one your project involves is essential before you hire.
When Bare React Native Is Still the Right Choice
Expo is the default, but it's not universal. There are specific situations where bare React Native (with or without Expo's tooling layer) is the right call.
Your app needs custom native modules that Expo doesn't support. If you're integrating a proprietary SDK (a specific payment processor's native SDK, a custom BLE protocol for medical devices, a hardware manufacturer's API), you need direct access to native project files. Expo's config plugins handle many cases, but some native integrations require modifications that go beyond what plugins can express.
You have an existing bare React Native codebase. Migrating a large bare codebase to Expo managed workflow is possible but non-trivial. If the team is productive and the codebase is stable, the migration cost may not be justified. Adopt Expo's tooling (EAS Build, EAS Update) incrementally without converting to managed workflow.
Your app does heavy C++ or native GPU work. Real-time video processing, custom ML inference pipelines, or game engine integrations that require direct access to native build configurations and linking against C++ libraries.
Your team has dedicated iOS and Android engineers. If your team already has native mobile expertise and is comfortable managing Xcode, Gradle, CocoaPods, and platform-specific build pipelines, bare React Native lets them work in the environment they know.
How This Changes Who You Hire
This is the section that determines your budget and your sourcing strategy.
| Your Project | Developer Profile | What They Need to Know | Salary Range (US) |
|---|---|---|---|
| New app, standard features (most apps) | Expo-focused developer | TypeScript, Expo Router, Expo SDK, EAS Build/Update, React Navigation, state management | $85,000-$128,000 |
| New app, custom hardware integrations | Bare workflow + native skills | Everything above + Swift/Kotlin, Xcode/Android Studio, Podfile/Gradle config, Turbo Modules | $130,000-$185,000 |
| Existing bare codebase, maintenance + features | Bare workflow developer | Comfortable with native project files, manual builds, native debugging. Expo knowledge is a bonus | $100,000-$145,000 |
| Existing bare codebase, migrating to New Architecture | New Architecture specialist | JSI, Fabric, Turbo Module Codegen specs, Hermes profiling, native crash debugging | $145,000-$185,000 |
| Startup MVP, need to ship in 8 weeks | Expo-first generalist | Expo managed workflow, Firebase/Supabase, fast iteration. Native skills not needed | $85,000-$110,000 |
The most expensive hiring mistake: paying $145,000+ for a bare-workflow specialist when your app runs entirely in Expo managed workflow. The inverse is equally costly: hiring an Expo-only developer at $90,000 for a project that requires custom Turbo Module development, only to discover three months in that they've never opened Xcode.
Put your workflow in the job description. This single line filters out the wrong candidates before the phone screen. "Expo managed workflow" or "Bare React Native with custom native modules" are two different jobs that happen to share the same framework name.
What to Ask in Interviews
These questions separate Expo-only developers from bare-capable developers. Use them when the distinction matters for your project.
"Have you ever configured a Podfile or resolved a CocoaPods conflict?" An Expo-only developer has never needed to. A bare developer has war stories. This single question tells you which side of the line they're on.
"How do you handle a native capability that doesn't have an Expo SDK package?" Strong answer: "I check if there's a config plugin. If not, I create a custom dev client with expo-dev-client and write or integrate the native module. If the integration is complex enough, I drop to bare workflow for that module." Weak answer: "I'd look for an npm package."
"Walk me through how you'd push a critical bug fix to production users on a Friday evening." Expo developers describe EAS Update: push JS changes; users get the fix on the next launch; rollback if needed. Bare developers describe a full release cycle: build locally, submit to TestFlight/Play Console, wait for review. Both are valid answers. The question reveals their experience with deployment workflows.
"How does the upgrade process differ between Expo managed and bare React Native?" A strong answer explains that Expo manages native dependency upgrades through the SDK version, whereas bare requires manual changes to Podfile, build.gradle, and native project configs. They should mention that bare upgrades have historically been the most painful part of React Native development, and Expo's Continuous Native Generation (CNG) model regenerates the native project from app.json on every build, largely solving this problem.
For more interview questions across all skill areas, see our interview questions guide.
Before You Choose Bare: Test Your Blocker First
The most common reason teams choose bare React Native is "we need a native library that Expo doesn't support." In 2026, that assumption is wrong more often than it's right. Expo's custom development client lets you use any native npm package without leaving the managed workflow. Before you commit to bare (and the higher developer salary and longer setup time that comes with it), test whether your suspected blocker actually blocks.
# Step 1: Create a minimal test project
npx create-expo-app blocker-test
cd blocker-test
# Step 2: Add the custom dev client (this enables any native code)
npx expo install expo-dev-client
# Step 3: Install the native library you think requires bare RN
npm install your-suspected-blocker-library
# Step 4: Build a custom dev client (runs once, takes ~10 minutes)
eas build --profile development --platform ios
# Step 5: Install the dev build on your device or simulator
# Then start the app:
npx expo start --dev-client
# Step 6: Import and use the library in a test screen
# If it works -> you don't need bare React Native
# If it crashes -> check the error. Most failures are fixable
# with a config plugin or a minor native patch
# If it fundamentally can't work -> bare is justifiedThis test takes under an hour and can save you months of unnecessary complexity. We've seen teams commit to bare React Native for a library that works perfectly in Expo's dev client, only to spend weeks dealing with native build issues they didn't need to.
Expo vs Bare: The Production Pipeline
The workflow difference isn't just about initial setup. It shows up every day in how your team builds, tests, and deploys.
# EXPO: Full production deployment pipeline
# Developer fixes a bug at 4 PM on Friday
# 1. Fix the code (TypeScript only)
git commit -m "fix: resolve cart total rounding error"
git push origin main
# 2. CI runs tests automatically (configured once in eas.json)
# Jest + React Native Testing Library: 3 minutes
# 3a. If it's a JS-only fix (most fixes): push OTA update
eas update --branch production --message "Fix cart rounding"
# Users get the fix on next app launch. Total time: ~15 minutes.
# 3b. If it requires a native change: trigger a full build
eas build --platform all --profile production
# Cloud build: ~12 minutes. Then submit:
eas submit --platform ios
eas submit --platform android
# Total time: ~30 minutes + store review (1-3 days)
# BARE: Same bug fix on a Friday afternoon
# 1. Fix the code
git commit -m "fix: resolve cart total rounding error"
git push origin main
# 2. CI runs tests (you configured GitHub Actions / Bitrise yourself)
# Jest: 3 minutes. Build verification: 15-20 minutes.
# 3. Build locally or via CI
# iOS: Open Xcode, increment build number, archive, upload to App Store Connect
# Android: Run ./gradlew bundleRelease, sign with keystore, upload to Play Console
# Total build time: 20-30 minutes
# 4. Submit for review
# No OTA option. Even a one-line JS fix requires full store review.
# Total time: 30-45 minutes + store review (1-3 days)
# The difference: Expo can ship JS fixes in 15 minutes.
# Bare RN ships the same fix in 1-3 days.For a team that ships weekly updates, this pipeline difference compounds into weeks of saved time per year. For a team that ships hotfixes for critical bugs, the ability to bypass store review is the difference between a 15-minute response and a 3-day response.
The Myths That Still Circulate
"Expo is only for small apps." This was true in 2018. In 2026, Expo supports production-scale applications with cloud builds, custom native modules, and the full New Architecture. Companies running Expo in production include apps with 120,000+ monthly active users reporting 99.2% crash-free session rates.
"React Native always performs better than Expo." Runtime performance is identical. Both run on the same Hermes engine, the same JSI bindings, the same Fabric renderer. The difference is in development tooling, not execution speed.
"Expo removes native capabilities." Expo provides native access through the Expo Modules API and config plugins. For cases that genuinely require direct access to the native project, the bare workflow retains full React Native capabilities while keeping Expo's SDK and services.
"You'll eventually need to eject from Expo." The concept of "ejecting" is largely obsolete. Expo's bare workflow and custom dev clients let you access native code without abandoning Expo's tooling. The line between managed and bare continues to blur.
What to Do Next
The decision between Expo and bare React Native should be made before you write the job description, not during the interview process. It determines who you hire, what you pay, and how fast you ship.
For most new projects in 2026, start with Expo. You'll ship faster, upgrade easier, and deploy OTA fixes in minutes instead of days. If you hit a limitation that requires bare workflow, Expo's bare workflow gives you the escape hatch without losing Expo's tooling.
For the full hiring process, including skills evaluation, interview questions, and onboarding, see our step-by-step hiring guide. For understanding what the New Architecture means for your project, see our New Architecture guide.
At Hire React Native Developers, every developer on our platform specifies their workflow expertise (Expo managed, Expo bare, or bare React Native) so you can match the right developer to your project from day one. Vetted senior developer in your team within 5 days, 2-week risk-free trial.
Hire a React Native Developer →