I build mobile products in TypeScript. The constraint is always the same: one team, two stores, a web surface that should not be a third rewrite, and a release process that does not stall on a laptop's Xcode install.
Expo is still the default I reach for. Not because it is fashionable. Because OTA, cloud builds, and a shared React Native codebase are the parts of mobile I do not want to own by hand.
What I have actually shipped
LegalAgent is the current Expo production case: voice and chat in React Native, RAG over case material, an admin surface for prompts and documents. Expo is how that client ships to phones without a native team in the loop for every JS change.
Before that I spent real time in React Native without treating Expo as the whole story. Make Sense Labs was Sense.chat: messaging, an EOS wallet, privacy features. Tractor Supply was a retail app with browsing, accounts, and AR product previews on ViroAR. Those codebases taught me the native edge cases (AR, store review, performance) that Expo does not erase. They also taught me why I do not want to rebuild certificates and build graphs for the next product if EAS will hold them.
What Expo is for
Expo sits on React Native. React Native is the bridge from React to iOS and Android. Expo is the toolchain around that bridge: a module set, a build service, and a publish path that is not "open Xcode."
Over-the-air updates. JS and assets go out without waiting on App Store or Play review. Store review still exists for native binary changes. For copy, flags, and a large class of bugfixes, OTA is the difference between a same-day patch and a week of sitting in review. I use that on products where the JS layer is the product.
Cloud builds. EAS compiles on their machines. I do not maintain a blessed macOS image so Android and iOS both build. Certificates and profiles are part of that service, not a wiki page.
Native APIs through modules. Camera, notifications, biometrics, media: I reach for the Expo module first. Custom native code is the exception, and Expo's bare / continuous native path is there when the exception is real.
React Native Web and NativeWind. Shared TypeScript types, shared business logic, styling that is not a second design system. Authentication that is one flow, not three. Same domain model on a phone and a browser, not a third rewrite.
React Native in the wild
I do not need a logo wall to justify the engine. Coinbase published their move to React Native and called out OTA as part of how they ship. Meta built the framework. Discord, Shopify, and a long list of wallets (MetaMask Mobile, Rainbow, Ledger Live) run on it. That is enough evidence that RN is not a prototype stack. Expo is how I operationalize it on a small team.
When I do not use it
Heavy 3D, custom AR/VR, or a graphics pipeline that Expo's modules do not cover: Tractor Supply's ViroAR work is the reminder. Very specific native modules with no Expo equivalent. Work that is only a thin shell over a large existing native app. Millisecond-sensitive loops (HFT-style clients, competitive realtime games).
Then I look at a development build or a fully native app. Those cases are real. They are not most of the product work I am hired for: chat, accounts, media, AI surfaces, commerce.
The default
If the team already writes TypeScript and React, Expo is the cheapest honest path onto phones. One codebase, EAS for binaries, OTA for the JS layer, native modules until they are not enough. LegalAgent is why I still pick it. Sense.chat and Tractor Supply are why I know where it stops.