React Native Migration at Scale: Lessons from YPMC
How we migrated mobile interfaces to React Native at YPMC, improving app store deployment efficiency by 50% while cutting crash rates and standardizing accessibility patterns.
Why teams migrate to React Native
Maintaining separate native codebases doubles release overhead — especially for healthcare and logistics products shipping weekly fixes across Android and iOS.
At YPMC, I led the migration of mobile interfaces to React Native, improving app store deployment efficiency by ~50% while establishing shared patterns with our React web codebase (TypeScript, testing discipline, component thinking).
This article covers what worked, what hurt, and what I'd repeat on the next migration.
Migration strategy: strangler, not big bang
We did not rewrite the entire app in one release. We used a strangler pattern:
- Spike — prove navigation, auth, and one critical flow in RN
- Module-by-module — appointments, then provider dashboard, then partner portal
- Shared packages — validation schemas, API clients, design tokens in a monorepo workspace
Each release replaced a native screen with an RN screen behind feature flags.
Shared logic, platform-specific UI
We extracted platform-agnostic logic:
// packages/core/src/appointments/schema.ts
export const appointmentSchema = z.object({
patientId: z.string().uuid(),
slot: z.string().datetime(),
type: z.enum(["consultation", "follow_up"]),
});
Web and mobile consumed the same Zod schemas and API types. UI remained platform-native where it mattered (gestures, safe areas, haptics).
Navigation and deep linking
React Navigation's stack + tab structure mirrored our web information architecture. Deep links from push notifications mapped to typed routes:
const linking = {
prefixes: ["ypmc://", "https://app.ypmc.com"],
config: {
screens: {
Appointment: "appointments/:id",
ProviderHome: "provider",
},
},
};
Broken deep links were a top crash source before migration — typed linking reduced that class of bugs.
Performance on real devices
Simulator performance lies. We profiled on mid-range Android devices:
- FlatList with
getItemLayoutwhere heights were fixed - Memoized row components for appointment lists
- Image caching via optimized CDN URLs and explicit dimensions
These changes contributed to ~20% crash rate reduction on mobile rendering hot paths.
Accessibility on mobile
We standardized:
- Minimum touch targets (44pt)
- Screen reader labels on icon-only actions
- Dynamic type support where platform allowed
100% compliance with internal mobile interaction standards was the bar before marking a module production-ready.
Release train and OTA vs store builds
We separated:
- JS bundle updates via CodePush-style workflows for non-native changes
- Store releases for native module upgrades
Clear rules prevented shipping native dependency changes through the wrong channel.
Key takeaways
- Strangler migrations beat big-bang rewrites
- Share schemas and API layers; don't force identical UI
- Profile on mid-tier Android hardware early
- Treat accessibility and deep linking as release blockers, not polish