expo vs react-native-cli
Architecting React Native Applications: Managed Workflows vs. Pure CLI
exporeact-native-cliSimilar Packages:

Architecting React Native Applications: Managed Workflows vs. Pure CLI

expo and react-native-cli represent two distinct approaches to building React Native applications. expo provides a managed workflow with a comprehensive suite of tools, services, and pre-configured native modules, allowing developers to build apps without touching native code directly. It simplifies setup, testing, and deployment through a unified ecosystem. In contrast, react-native-cli (often referred to as the React Native CLI) offers a bare-metal approach where developers initialize a project with direct access to native iOS and Android folders. This grants full control over native dependencies and custom native code but requires deeper knowledge of Xcode, Android Studio, and native build systems. While expo has evolved to support custom native code via "Prebuild," the fundamental difference lies in the default developer experience and the level of abstraction provided out of the box.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
expo051,775921 kB7493 days agoMIT
react-native-cli0126,400-1,08010 years agoBSD-3-Clause

Expo vs. React Native CLI: Architecture, Workflow, and Control Compared

Choosing between expo and react-native-cli is one of the most critical architectural decisions in a React Native project. This choice dictates your build pipeline, how you handle native dependencies, and your team's daily workflow. While both ultimately render native UI components, they differ significantly in how they bridge the gap between JavaScript and the underlying operating systems.

🏗️ Project Initialization and Structure

expo creates a "managed" project. You do not see ios or android folders by default. The native code is hidden inside the Expo Go client app during development. You configure your app via a single app.json (or app.config.js) file.

# Expo: Creates a clean JS-only structure
npx create-expo-app@latest MyManagedApp
cd MyManagedApp
# No /ios or /android folders visible yet

react-native-cli creates a "bare" project. It immediately generates native folders (ios/, android/) containing Xcode and Gradle projects. You are responsible for managing these folders from the start.

# React Native CLI: Creates full native structure
npx @react-native-community/cli init MyBareApp
cd MyBareApp
# /ios and /android folders exist immediately with native code

🔌 Installing Native Modules

This is the most significant technical divergence. Native modules (like Bluetooth, advanced camera features, or specific payment SDKs) require linking native code.

expo relies on its SDK for common modules. If a library supports "Expo Config Plugins," you can install it via JavaScript, and Expo automatically generates the necessary native code during a "Prebuild" step. If a library lacks a config plugin, you must run npx expo prebuild to generate native folders and manually link the code.

// Expo: Installing a module with Config Plugin support
// e.g., expo-camera
npx expo install expo-camera

// app.json configuration handles permissions automatically
{
  "expo": {
    "plugins": ["expo-camera"]
  }
}

react-native-cli requires you to manually install CocoaPods (iOS) and update Gradle files (Android) for almost every native dependency. You directly edit native files to link libraries.

# React Native CLI: Manual linking steps often required
npm install react-native-bluetooth-manager
cd ios && pod install && cd ..

// You often must manually edit ios/Podfile or android/build.gradle
// to ensure native libraries are found by the compiler

🚀 Running and Testing the App

expo shines with its development client. You can scan a QR code and run your JavaScript bundle instantly on the "Expo Go" app without compiling native code. For custom native code, you build a custom "Development Client" which acts like a personalized Expo Go.

# Expo: Run on device via QR code (Managed)
npx expo start
# Scan QR code with Expo Go app -> App loads instantly

# Expo: Run with custom native code (Prebuild)
npx expo run:ios
# Builds a custom dev client once, then supports fast refresh

react-native-cli requires you to compile the native app every time you change native code. You must have Xcode (for iOS) or Android Studio (for Android) installed and configured. The initial build time is significantly longer.

# React Native CLI: Must compile native code
npx react-native run-ios
# Launches Simulator, compiles Swift/Obj-C, links binaries, then loads JS

npx react-native run-android
# Launches Emulator, runs Gradle build, installs APK, then loads JS

📦 Updates and Distribution

expo provides Over-The-Air (OTA) updates via EAS Update. You can push JavaScript changes to users instantly without going through the App Store review process (within limits). Binary distribution is handled by EAS Build, which compiles your app in the cloud.

// Expo: Pushing an OTA update
npx eas update --branch production --message "Fix login bug"
// Users receive the fix next time they open the app, no store update needed

react-native-cli typically relies on third-party services like CodePush for OTA updates, which require manual integration. Binary builds are usually managed locally or via custom CI/CD pipelines (GitHub Actions, Bitrise), requiring you to maintain build certificates and provisioning profiles manually.

# React Native CLI: Traditional Store Release
# 1. Archive build in Xcode / Build signed APK in Android Studio
# 2. Upload binary to App Store Connect / Google Play Console
# 3. Wait for review process
# No built-in OTA mechanism without adding libraries like react-native-code-push

🛠️ When to Eject or Use Prebuild

A common misconception is that choosing expo locks you in forever. Modern Expo uses "Continuous Native Generation." You keep your native folders in .gitignore and regenerate them whenever config changes.

# Expo: Regenerating native code based on config
npx expo prebuild --clean
# Deletes ios/android folders and recreates them based on app.json and installed plugins

In react-native-cli, the native folders are the source of truth. You edit them directly. If you break the build configuration, you must debug Gradle or Xcode project files manually.

# React Native CLI: Native folders are permanent
# You manually edit ios/MyApp.xcworkspace or android/app/build.gradle
# No automated regeneration tool resets these files to a default state easily

🤝 Similarities: Shared Core

Despite the workflow differences, both approaches share the same rendering engine and core capabilities.

1. ⚛️ Same React Native Runtime

Both render actual native views (UIView on iOS, View on Android). The performance characteristics of the UI thread are identical.

// Works identically in both Expo and CLI
import { View, Text, StyleSheet } from 'react-native';

export default function App() {
  return (
    <View style={styles.container}>
      <Text>Hello World</Text>
    </View>
  );
}

2. 🔥 Fast Refresh

Both support Fast Refresh, preserving local state while editing code.

// Both environments support this workflow
// Edit component -> Save -> UI updates instantly without losing state
const [count, setCount] = useState(0);

3. 📜 JavaScript Logic

Business logic, state management (Redux, Zustand), and routing (React Navigation) work exactly the same way.

// React Navigation works in both
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';

// Stack navigator configuration is identical
const Stack = createNativeStackNavigator();

📊 Summary: Key Differences

Featureexporeact-native-cli
Native FoldersHidden by default (generated via Prebuild)Visible and mandatory from start
Setup TimeMinutes (QR code ready)Hours (Xcode/Android Studio config)
Native ModulesAuto-linked via Config PluginsManual linking (CocoaPods/Gradle)
Build SystemCloud-based (EAS Build) or Local PrebuildLocal (Xcode/Gradle) or Custom CI
OTA UpdatesBuilt-in (EAS Update)Requires 3rd party (e.g., CodePush)
App SizeSlightly larger (includes Expo modules)Minimal (only what you link)
FlexibilityHigh (via Prebuild), but opinionatedUnlimited (full native access)

💡 The Big Picture

expo is the modern standard for most React Native development. It removes the friction of native tooling, allowing teams to ship faster. With the introduction of Prebuild and Config Plugins, the gap between "managed" and "bare" has nearly closed. You only lose out if you need to modify native code manually on a daily basis without using the plugin system.

react-native-cli remains relevant for highly specialized cases: apps that rely heavily on legacy native code, require specific non-standard build configurations, or teams that strictly forbid cloud-based build services. However, for greenfield projects, the maintenance burden of managing native folders manually often outweighs the benefits unless you have dedicated native engineers on staff.

Final Thought: Start with expo. If you hit a wall where a Config Plugin doesn't exist for a critical native library, you can run npx expo prebuild and effectively transition to a CLI-like workflow while keeping the Expo tooling benefits. Starting with react-native-cli gives you no such easy path to simplify later.

How to Choose: expo vs react-native-cli

  • expo:

    Choose expo if you want to accelerate development with a managed workflow that handles native configuration, provides over-the-air updates, and offers a rich set of pre-built modules. It is ideal for startups, MVPs, and teams that prefer to focus on JavaScript logic rather than maintaining native build configurations. With Expo's 'Prebuild' feature, you can still eject to custom native code later if specific library requirements arise, making it a flexible starting point for most projects.

  • react-native-cli:

    Choose react-native-cli if your project requires immediate, deep customization of native code (iOS/Android) from day one, or if you must integrate legacy native libraries that are not compatible with Expo's managed environment. This approach is suitable for large enterprises with dedicated mobile engineering teams who need full control over the native build pipeline, third-party native dependencies, and specific platform-level optimizations without the abstraction layer of the Expo SDK.