Building a production-grade mobile app is a different discipline than shipping a prototype. At VoiceAct Solutions we have shipped dozens of cross-platform applications for startups and enterprises, and the difference between an app that stalls in review and one that scales to millions of sessions comes down to architecture decisions made in the first two weeks. This guide distills that experience into a practical engineering playbook for React Native and Expo.
Why Cross-Platform Still Wins in 2026
The economics are hard to argue with: one TypeScript codebase, one design system, and one CI pipeline deliver native iOS and Android binaries from the same commit. With the New Architecture stable and Expo Go replaced by development builds in production workflows, the old trade-offs — "React Native is slow", "you need a Mac to ship iOS" — have largely collapsed.
Native modules, however, remain unavoidable at the edges. The win is not avoiding native code, it is isolating it: build proprietary bridges only for the 5% of features consumers care about, and keep the rest on the JS clock.
Architecting for Maintainability From Day One
The most expensive mistake a studio sees is a single, tangled app directory that cannot split. Our default structure is feature-sliced:
textapps/mobile ├── src/ │ ├── app/ # Expo Router file-based routes │ ├── features/ # auth, checkout, feed — each self-contained │ ├── shared/ # UI kit, hooks, api client, i18n │ ├── native/ # iOS/Android native modules and specs │ └── services/ # analytics, crash reporting, feature flags
In practice, the Expo Router file-based routing is the strongest convention you get for free: screens become routes, deep links become route strings, and the navigation tree stays visible in the file tree. Your UX team approves it, your junior engineers understand it, and your tests can import a route and render it in isolation.
State, Data, and Server Synchronization
Server state is not global state — and teams that conflate the two pay for it in bug reports. The rule we enforce in every codebase is simple:
- Server state belongs in TanStack Query. Cache keys follow the URL, invalidations follow mutations, and optimistically updated checkouts revert automatically when a payment fails.
- Client state is limited to navigation parameters, forms, and transient modals.
- Ephemeral state stays in a tiny, typed store — never hang re-render-prone data on context boundaries.
Every network call goes through one typed module: a single fetch wrapper, one interceptor for token refresh, one typed error hierarchy. When the designer asks for a loading skeleton, that module is the only place you touch.
The New Architecture and the Bridge
Since React Native 0.76, the New Architecture is the default: JSI hosts the JS runtime on the native thread, the old bridge's serialization bottleneck is gone, and synchronous native calls are possible. If you are still on 0.7x, the migration is the highest-ROI refactor on your roadmap — legacy bridge code is why "it's slow on Android" incidents keep resurfacing.
When you genuinely must write a native module, follow the TurboModule spec path:
- Define the spec in TypeScript — it is the single source of truth for both platforms.
- Implement the spec in Kotlin and Swift with typed interfaces.
- Call it like any promise; never marshal large payloads synchronously across the boundary.
Also worth measuring: your start-up. Profile with Hermes (now the default engine), track time-to-first-interaction on a mid-range Android device, and keep cold-start traces in your performance monitor. VoiceAct routinely moves launch screens to the native side when a startup page still blocks on the JS host.
Performance Engineering: The Second Refactor
A senior engineer's performance checklist for React Native is unglamorous, but it is the checkpoint:
- FlashList over FlatList for anything that scrolls more than a screen height — virtualization done right.
- Image pipeline discipline: resize on the server or a CDN image service, set a
cacheKeyon every image, and avoidresizeModehacks across large image sets. - JS thread profiling — jank on the JS thread is the classic sign of heavy layout animations; move them to the UI thread and re-measure in release mode.
- Bundle splitting: tune the Metro bundle per platform and strip unused i18n and native modules from the default export.
- Prefetch and cache: warm the API cache on launch, and use local persistence (MMKV) to skip network round-trips for the 80% of users who have opened the app before.
CI/CD, Testing, and Over-the-Air Updates
Expo tooling made CI boring in a good way. eas build produces signed binaries, eas update ships over-the-air updates to production channels within minutes, and expo-doctor catches dependency rot before the build breaks.
Keep the test pyramid honest: unit tests for the API client and domain code, component tests for the UI kit and features, and a smoke suite of end-to-end flows with Detox or Maestro running on a real device farm ahead of every release. A release should not depend on "it worked on my simulator" anymore.
Results in Production
On a recent VoiceAct commerce engagement, applying this playbook to a React Native + Expo application with a native payment module delivered:
- Cold start under 1.6s on mid-range Android devices (down from 4.2s).
- Ninety-three of ninety-five app-store screenshots passing a web.dev-style audit on real devices.
- A crash-free session rate above 99.5% across both stores post-release.
- One shared React tree for the full checkout funnel — one codebase, two stores, zero duplicated screens.
The lesson is that React Native is not a shortcut; it is a lever, when the architecture is designed for it. Set up the folder boundaries, drive server state through one client, keep native code at the edges, and measure start-up in CI from week one. That is the difference between an app that ships and an app that scales.
Get in touch with the VoiceAct mobile engineering team to apply this playbook to your product — we build, measure, and ship cross-platform apps with the discipline of senior studio engineers.
