Expo can make mobile development feel almost suspiciously easy.

You create the project, open it on a device, and start working in JavaScript. The build systems, certificates, native project files, and much of the platform-specific setup stay out of sight. For a web team trying to deliver on iOS and Android, that is a real advantage.

I helped move a field application from a native iOS codebase to React Native and Expo. The move made sense. We wanted to support more than one platform without maintaining two completely separate products, and much of the application was forms, data, navigation, and communication with a backend.

Expo gave us a much better starting point for that work.

Then the product reached the part that was not generic: barcode scanning and hardware integration.

The scanner did not care that we had chosen a convenient JavaScript workflow. Its manufacturer supplied a native SDK. The operating systems had their own lifecycle and permission rules. Data still had to cross the bridge between native code and the React Native application.

Expo had not failed. We had arrived at the edge of the abstraction.

An abstraction moves complexity; it does not abolish it

The useful promise of Expo is not that native mobile development no longer exists. It is that a large class of applications can avoid owning most of it most of the time.

That distinction matters.

If an app is built from common interface controls, API calls, authentication, notifications, standard camera access, and other well-supported capabilities, Expo can remove a remarkable amount of work. The team benefits from a coherent toolchain and can spend more time on the product.

But a mobile application still runs on iOS and Android. The moment a requirement depends on a proprietary SDK, a platform service, a specialized background mode, or hardware behavior the abstraction does not cover, the native layer becomes part of the product again.

Teams sometimes describe this as “ejecting from Expo,” as if crossing that line means the original choice was wrong. I do not see it that way. Expo’s prebuild and configuration tools can let a project keep much of its existing workflow while adding native code where required.

The tradeoff is that the team now owns both sides. It has the JavaScript application and the native projects generated around it. Somebody needs to understand how they build, how the bridge behaves, and which changes can be delivered without a new store release.

The complexity was always present. It had simply been rented from the framework until the product needed something custom.

Hardware should be discussed before framework preference

The most important Expo question is not whether the team likes React.

It is whether the application’s defining requirements live inside or outside the supported path.

Hardware is an obvious place to look. Barcode scanners, Bluetooth devices, payment terminals, printers, and vendor SDKs often arrive with native assumptions. A package may exist, but that does not settle the question. It still needs to support the correct devices, operating-system versions, and workflow.

Background work deserves the same attention. So do authentication SDKs, enterprise device management, offline storage, and inter-application communication. These requirements can change the shape of the project more than the screen designs do.

I would rather prove one risky integration in a small technical spike than discover six months later that the central feature depends on a native capability nobody on the team knows how to maintain.

That is not an argument for starting every project in native Swift and Kotlin. It is an argument for testing the product’s hardest boundary before optimizing the pleasant part of development.

Expo was still useful after the boundary

There is a temptation to turn framework decisions into verdicts. Expo either saved the project or became a mistake. The actual experience was less dramatic.

It accelerated a large amount of ordinary mobile work. When a specialized requirement appeared, we accepted more native responsibility. Both things can be true.

The value of the original decision depends on what happens after that point. If the application has one contained native integration and the rest fits Expo well, keeping the Expo toolchain may still be the simplest option. If custom native work is becoming the centre of the product, the abstraction may now add more indirection than it removes.

That boundary is not fixed. Expo’s capabilities and ecosystem continue to change, while product requirements change in the other direction. A package that closes one gap does not mean every future gap is closed.

The decision needs revisiting, not defending.

What I would decide now

I would happily choose Expo again for the right application. I would also put native capability on the requirements checklist before calling the architecture settled.

I want to know:

  • Which vendor SDKs are non-negotiable?
  • Which hardware has to work, on which platforms?
  • What must happen while the app is backgrounded or offline?
  • Does the team have anyone who can diagnose a native build or bridge problem?
  • If native code grows, are we comfortable owning the generated projects?

If those answers point toward mostly standard mobile behaviour, Expo remains a strong default. If the product is fundamentally an interface to specialized hardware or deep platform services, I would start closer to the native layer.

Expo did not teach me to avoid abstractions. Good abstractions are how small teams deliver ambitious software.

It taught me to be honest about where they end.