In 2022, our fintech startup needed to deliver iOS, Android, and web applications for a digital banking product. With a team of seven engineers and a nine-month runway to launch, we chose React Native with Expo as our cross-platform foundation. Eighteen months later, the platform serves 340,000 users across all three platforms with a 4.7-star average app store rating. This case study covers the architecture, the trade-offs, and the lessons we would carry into the next build.
Starting Context
The product requirements were substantial for a startup: biometric authentication, real-time push notifications for transactions, offline access to account balances, document scanning for KYC, and PCI-compliant card management. The regulatory environment required separate security reviews for each platform deployment.
Team composition: three senior React engineers (no native mobile experience), two mid-level fullstack engineers, one designer, and one engineering manager. Budget for native iOS and Android development: zero.
Architecture Decisions
Framework: React Native with Expo
We chose React Native over Flutter for one reason: our entire team knew TypeScript and React. The learning curve for React Native was two weeks versus an estimated eight weeks for Flutter with Dart. At a startup, those six weeks matter.
Expo provided managed native modules for camera, biometrics, secure storage, and push notifications — capabilities that would have required native development experience without it.
Monorepo Structure
We used Turborepo for orchestration. The packages/domain directory contained all business rules — transaction categorization, spending limit calculations, account balance aggregation — running identically across platforms.
UI Framework: Tamagui
Tamagui gave us a single component library that rendered native views on mobile and optimized HTML on web. This was the highest-leverage decision we made — it enabled genuine UI code sharing (72% of UI components) without the uncanny valley effect.
Data Layer: tRPC + React Query
tRPC provided end-to-end type safety from the backend API to the mobile client. React Query handled caching, background refresh, and offline support.
Code Sharing Results
After 18 months of development:
| Layer | Code Sharing % |
|---|---|
| Business Logic | 98% |
| API Client | 100% |
| State Management | 95% |
| UI Components | 72% |
| Navigation | 0% (platform-specific) |
| Native Modules | 15% (wrappers shared) |
| Overall | 68% |
The 68% overall sharing translated to roughly 2.5x developer productivity compared to building three separate applications.
Need a second opinion on your mobile/frontend architecture?
I run free 30-minute strategy calls for engineering teams tackling this exact problem.
Book a Free CallMeasurable Results
| Metric | Target | Actual |
|---|---|---|
| Time to launch (all platforms) | 9 months | 11 months |
| App store rating (average) | 4.5 | 4.7 |
| Cold start time (iOS) | < 2s | 1.4s |
| Cold start time (Android) | < 3s | 2.1s |
| Crash-free sessions | > 99.5% | 99.7% |
| Monthly active users (month 6) | 100K | 142K |
| Engineers required | 7 | 7 (no additional hires) |
What Failed
Expo Managed Workflow Limitations
At month four, we needed custom native module integration for a third-party card SDK that Expo's managed workflow did not support. The ejection to a bare workflow cost us two weeks of engineering time and introduced native build complexity that the team was not prepared for. We should have started with the bare workflow from the beginning.
Web Performance
React Native Web (via Tamagui) produced larger JavaScript bundles than a native Next.js application would have. Initial web load time was 4.2 seconds — unacceptable for SEO and user acquisition. We ended up rebuilding the marketing pages and onboarding flow as pure Next.js pages, reserving the React Native Web runtime for the authenticated dashboard. This split architecture added complexity but reduced web load time to 1.8 seconds.
Platform-Specific Gesture Handling
Our custom swipe-to-pay feature required different gesture implementations on iOS (UIKit gesture recognizer bridged) and Android (native gesture handler). The shared gesture abstraction we built was fragile and broke across React Native versions. We should have accepted platform-specific implementations for gesture-heavy features from the start.
Honest Retrospective
Would we choose React Native again? Yes, for this team composition. The TypeScript/React expertise of the team was the deciding factor, and it proved correct.
What would we change?
- Start with Expo bare workflow, not managed
- Build the web marketing funnel as pure Next.js from day one
- Invest in E2E testing (Detox for mobile, Playwright for web) in month one, not month six
- Hire one engineer with native iOS/Android experience by month three
- Use React Navigation's native stack everywhere — we initially used JS-based stack for "consistency" and the performance difference was noticeable
What surprised us?
Android performance was significantly better than expected. React Native's Hermes engine on Android often outperformed iOS's JavaScriptCore for our computation-heavy transaction categorization logic. We had allocated extra Android optimization time that we did not need.
Conclusion
Cross-platform development with React Native enabled a seven-person team to ship a production banking application to iOS, Android, and web in 11 months. The 68% code sharing rate translated directly into reduced engineering headcount requirements — our estimate for building three native applications was 12-15 engineers. The trade-offs were real: web performance required architectural splits, gesture-heavy features needed platform-specific implementations, and Expo's managed workflow hit limitations sooner than expected.
For startups with React/TypeScript teams building content-heavy or form-heavy applications, React Native remains the highest-ROI cross-platform choice. For gesture-intensive or graphics-heavy applications, evaluate Flutter's rendering engine or native development more seriously.