Expo Is Great for App Development—Until It Isn't
When Expo Goes Nuclear: The Highs and Lows of React Native Development
Jay McBride
Software Engineer
Introduction
We shipped our React Native app to production using Expo. It worked perfectly—until we needed to integrate a barcode scanner.
Expo didn’t support the proprietary SDK. The manufacturer’s docs assumed native iOS and Android development. We had three options: abandon the hardware requirement, eject from Expo entirely, or hack together a bridge.
We spent two weeks building native modules to relay scanner data through JavaScript events. The “managed workflow” that saved us months during development became the bottleneck preventing us from shipping critical features.
This article is for developers who’ve chosen Expo for speed and discovered the hard limits when you need actual native functionality. If you think Expo handles everything React Native can do, you haven’t hit the walls yet.
I’m going to tell you where Expo excels, where it breaks down completely, and how to make the call before you’re too deep to change course.
Enjoying this? 👉 Tip a coffee and keep posts coming
Here’s who this is for: Small teams evaluating React Native. Developers building MVPs who need to ship fast. Anyone hearing “just use Expo” without understanding the tradeoffs.
Not for: Teams with native iOS/Android developers already. Large enterprises with custom hardware requirements. This assumes you’re choosing Expo to avoid native development.
The question isn’t “is Expo good?” It’s “when does Expo stop being good enough?”
The Core Judgment: Expo Ships MVPs Faster, But Hardware Integration Breaks It
Here’s my default recommendation: use Expo if you’re building a standard mobile app with API calls and UI. Don’t use Expo if you need custom hardware, specialized native libraries, or anything requiring low-level device access.
Not theoretical. Learned across three production apps.
Expo’s managed workflow is incredible for speed. You write JavaScript, Expo handles native code. You get over-the-air updates without App Store review. You avoid the nightmare of Xcode and Android Studio configuration.
This works beautifully for 80% of mobile apps: authentication, forms, API calls, standard UI components, navigation, basic camera and location.
Where Expo breaks down:
When you need Bluetooth Low Energy with custom protocols. When you need proprietary SDKs from hardware manufacturers. When you need Android Intents for inter-app communication. When you need background processing beyond what Expo’s task manager supports.
At that point, you’re writing native code anyway. Expo’s abstraction becomes a hindrance, not a help.
The mistake teams make is choosing Expo and then discovering their requirements six months later. By then, ejecting from Expo means rewriting significant portions of the app. You’ve built on Expo’s APIs and assumptions. Removing that foundation is painful.
How This Works in the Real World
Our team chose Expo because we’re web developers. We know JavaScript. We don’t know Swift or Kotlin. React Native with Expo let us build iOS and Android apps without learning new languages.
Expo saved us massive time initially:
- Setup:
npx create-expo-appversus configuring React Native, Xcode, Android Studio, build tools - Development: Live reload, fast refresh, testing on real devices with Expo Go
- Deployment: Over-the-air updates for JavaScript changes without App Store review cycles
- Complexity: No need to understand native build systems, just JavaScript and React
We shipped an MVP in three months. Without Expo, it would’ve been six months minimum, possibly longer if we’d hired native developers.
Then we hit the hardware requirement.
We needed to integrate custom barcode scanners. These weren’t standard phone cameras with QR codes. These were industrial Bluetooth scanners with proprietary SDKs.
The manufacturer provided native iOS and Android libraries. Expo didn’t support them. Expo’s Bluetooth API was too generic for the custom protocol.
Our options:
- Stay in managed workflow, use Expo’s prebuild - Generate native projects from Expo config, write custom native modules, continue using Expo’s development workflow
- Fully eject - Leave Expo entirely, manage native projects ourselves, lose over-the-air updates
- Give up on the hardware - Not an option, the scanners were a core requirement
We chose prebuild. This gave us access to native code while keeping some Expo benefits. But it introduced new complexity: we now had to understand native build systems we’d chosen Expo to avoid.
A Real Example: Android Intents and the Wall We Hit
Android Intents let apps communicate with each other. Scanning apps can register custom intent handlers. When another app needs to scan, it fires an intent, your app handles it.
Expo didn’t support this out of the box. We needed it for integration with a client’s existing inventory management app.
The solution: We used Expo prebuild to generate native Android code, wrote a custom module exposing intent handling to React Native, and spent a week debugging the bridge between native and JavaScript.
This worked. But it defeated the purpose of Expo’s managed workflow. We were writing native code, managing build configurations, and debugging platform-specific issues—everything Expo was supposed to eliminate.
The lesson: Expo is fantastic until you need something Expo doesn’t support. At that point, you’re doing native development with React Native anyway. The question becomes: should we have started with bare React Native?
Common Mistakes Teams Make With Expo
Assuming Expo supports everything React Native does. It doesn’t. Expo is a curated subset of React Native with managed native modules. If it’s not in Expo’s SDK, you’re writing native code.
Not validating hardware requirements upfront. If your app needs specialized hardware or third-party SDKs, verify Expo compatibility before committing. Most manufacturers don’t provide Expo-compatible libraries.
Thinking “we’ll deal with native code later.” Later comes faster than you expect. If you know you’ll need native modules, start with bare React Native. Don’t accumulate Expo-specific code you’ll rewrite later.
Over-optimizing for development speed. Expo ships MVPs fast. But if your MVP needs to become a production app with custom requirements, that initial speed advantage disappears when you’re rewriting native bridges.
What Breaks When You Push Expo Too Far
You end up writing native code anyway, but with Expo’s abstractions in the way. Instead of straightforward native development, you’re managing Expo’s build pipeline, config plugins, and bridging layer.
Over-the-air updates stop working. Once you add native modules, you can’t update them over-the-air. You’re back to App Store review cycles. One of Expo’s biggest advantages is gone.
Build complexity increases. Expo’s managed workflow hides build complexity. Prebuild exposes it. Now you’re managing Xcode, Android Studio, and Expo’s config system simultaneously.
The honest tradeoff: If you need native functionality, bare React Native is simpler than Expo with native modules. Expo’s value proposition is avoiding native code. Once you can’t avoid it, Expo becomes overhead.
Best Practices For Choosing Expo vs. Bare React Native
Choose Expo when:
- Building standard apps (social, e-commerce, content, forms)
- Your team is JavaScript-only, no native developers
- You need to ship an MVP in weeks, not months
- Your app uses standard device APIs (camera for photos, location, notifications)
Choose bare React Native when:
- You need custom hardware integration
- You’re using third-party SDKs without Expo support
- You have native developers on the team
- You know you’ll need low-level platform access
Choose Expo with prebuild when:
- Starting with Expo’s speed but expecting native requirements later
- Need access to native modules while keeping some Expo tooling
- Can tolerate increased build complexity as a tradeoff
The decision point: Can you build the entire app with Expo’s SDK? If yes, use Expo. If no, start with bare React Native. Don’t choose Expo and hope you won’t need native code.
When We’d Choose Expo Again vs. When We Wouldn’t
We’d choose Expo again for:
A standard mobile app hitting REST APIs, displaying data, collecting user input, authenticating users. Simple camera use (photos/videos). Push notifications. Standard gestures and navigation.
For this category, Expo ships apps 2-3x faster than bare React Native. The managed workflow is incredible. Over-the-air updates are a game-changer for iteration speed.
We wouldn’t choose Expo for:
Apps requiring custom Bluetooth protocols. Integration with proprietary hardware SDKs. Background processing beyond simple tasks. Complex inter-app communication. Anything where the manufacturer provides only native libraries.
For this category, bare React Native is cleaner. You’re writing native code either way. Expo’s abstractions become obstacles.
The honest answer: We shipped our MVP with Expo and would do it again. The speed was worth the later complexity. But if we’d known about the hardware requirements upfront, we would’ve started with bare React Native.
Conclusion
Expo didn’t fail us. We chose it without understanding its boundaries.
The Expo team is clear about limitations. The docs explain which native features require custom modules. The ecosystem is transparent about what’s supported.
We failed to validate our requirements against Expo’s capabilities before committing. We optimized for initial speed without considering long-term flexibility.
After shipping three React Native apps—two with Expo, one bare—I’ve learned that Expo is the right choice for most apps, and the wrong choice for apps with specialized requirements.
The future of mobile development isn’t Expo versus bare React Native. It’s understanding your requirements well enough to choose the right tool before you’re too committed to change.
Start with a requirements audit. List every third-party integration, every hardware component, every native feature you might need. Verify Expo supports them. If it doesn’t, start bare.
Your users don’t care what framework you chose. They care that features work.
Frequently Asked Questions (FAQs)
Can you switch from Expo to bare React Native later?
Yes, but it’s painful. You’ll rewrite code that uses Expo-specific APIs. You’ll reconfigure build pipelines. Budget 2-4 weeks for the transition, more for complex apps.
What’s the difference between managed workflow, prebuild, and bare workflow?
Managed: Expo handles all native code, you write only JavaScript. Prebuild: Expo generates native projects you can customize, hybrid approach. Bare: Full React Native, you manage everything native. Each step increases control and complexity.
Does Expo work with native libraries from npm?
Only if they’re compatible with Expo or you use prebuild/bare workflow. Many React Native libraries require native code that doesn’t work in managed Expo. Always check compatibility first.
What happens to over-the-air updates after adding native code?
JavaScript changes still update over-the-air. Native code changes require App Store/Play Store updates. You lose the ability to update everything instantly but keep some OTA benefits.
Should beginners start with Expo or bare React Native?
Expo, unless they already know native development. Expo hides complexity beginners don’t need to understand yet. Learn React Native with Expo, then graduate to bare when requirements demand it.
Your turn: What was the first feature requirement that made you realize your framework choice was wrong?
Enjoying this? 👉 Tip a coffee and keep posts coming
