A 300-Screen Native App Embraces React Native
Back in 2020, Shopify declared React Native the future of its mobile development. Since then, the company has been methodically moving its native iOS and Android apps to the cross-platform framework. The migration strategy, however, is far from uniform. Each app presents unique challenges, and the chosen path must reflect that.
Shopify Point of Sale, for example, was originally built for small merchants. As it grew to process billions of dollars in transactions for some of the company's largest clients, its codebase and UX began to buckle under the weight of feature creep and tech debt. A full rewrite was deemed the only viable solution.
Shopify Mobile, the company's flagship app, is a different beast entirely. It is a stable, mature product that met merchant needs, and rewriting it from scratch was a non-starter. At 300 screens per platform and over six years in the making, a rebuild would take years, halt all new feature development, and result in the exact same app. A rewrite was clearly out of the question.
From Organic Growth to a Dedicated Strategy
Instead, the initial strategy was to begin adopting React Native gradually. The Mobile Enablement team laid the groundwork, and feature teams started building new features in React Native while occasionally migrating existing parts of the codebase. This organic approach allowed the team to learn and experiment without the pressure of a rigid timeline.
However, this method soon revealed significant drawbacks:
- The migration was painfully slow, with an estimated completion timeline of four to five years.
- Any change to the design system required updates across Android, iOS, and React Native.
- Three distinct architectures (Android, iOS, RN) had to be maintained simultaneously.
- Interoperability between new React Native code and legacy native code became a major time sink.
A few months in, a different path became necessary. The team had built a solid React Native foundation, had full leadership support, and had developers on board. This led to the creation of an approach called "Iterative Porting": building all new features in React Native while migrating existing ones in parallel.
Training the Team for a New Stack
Before any code could be ported, the engineers had to know how to write it. Many developers were experts in Kotlin and Swift but new to React Native. To bridge this gap, the team collaborated with Shopify's Learning & Development department to create an internal training program called RN Accelerator.
This program was structured into five milestones:
- Javascript and Typescript
- React
- React Native
- React Native for Shopify Mobile
- Advanced tools and techniques
This modular design allowed engineers to skip what they already knew and focus on the rest. Progress was tracked, and completion was rewarded, quickly getting the entire team up to speed and shipping React Native code to production.
The Inventory: Planning and Prioritizing the Port
To manage a project of this scale, the team needed a complete inventory of what had to be done. A custom script was used to scan the iOS and Android codebases and identify every source file needing migration. These files were then cataloged in a spreadsheet, organized by feature and sub-team, with columns for complexity and status.
With a long list in hand, a clear prioritization framework was required. The team implemented a scoring model similar to RICE to ensure consistency across all sub-teams. Each item was assessed on:
- Reach: The number of monthly active users.
- Impact: The qualitative or quantitative effect, rated from 1 to 5.
- Confidence: A percentage expressing certainty in the reach and impact estimates.
- Effort: The estimated workload in people over weeks.
The RICE Score was calculated as:
(Reach x Impact x Confidence) / Effort = RICE Score
Features and components were stack-ranked using this score. Migrations were executed behind feature flags, monitored for two weeks post-release for stability, and then declared complete once the native code was deleted and the flag removed.
Improvements as a Side Effect
The porting process also created opportunities for UX improvements. For instance, the Discounts List screen was modernized to match the new design language used in lists and filters across the app, making the transition to React Native a chance to ship an enhanced experience.
To manage these opportunities, the team established a decision flow to determine which improvements to prioritize and who would own them, based on the type of change and the team's context. When a clear solution or owner wasn't evident, a project proposal was required to explore the idea further.
Starting at the Root
Measuring progress required a clear map. The team created a dashboard to track the migration of all screens. Rather than starting small, the team chose to prioritize high-impact surfaces: the root screens of the app. Most of the time spent porting these screens went into preparing the necessary infrastructure, an investment that would benefit all future ports.
Porting the main tab-based navigation and the root overview screens ensured React Native was active earlier in the app lifecycle, right after login. This set the stage for Shopify Mobile to become fully React Native from start to finish and enabled global configuration on the React Native side at launch, rather than waiting for a single feature to initialize.
Unexpected Wins
The project yielded benefits beyond the primary migration goals.
Aligning iOS and Android Logic
While porting screens, the team uncovered far more differences in business logic between iOS and Android than anticipated. Discrepancies were found in rendering product subtitles, permission checks for components, and even the fields used in GraphQL queries. Aligning these differences simplifies future feature development across both platforms.
A Unified Mobile Team
Bringing iOS and Android developers together into one mobile team doubled the manpower available for feature work, PR reviews, and more. This has accelerated new feature development, and the long-term goal is to open up the app to web developers as well, further expanding the contributor pool.
Simplicity Through Declarative UI
Transitioning from Imperative UI in native code to a Declarative UI in React Native produced a much simpler codebase. This fresh start also allowed engineers to clean up old experiments, remove unneeded GraphQL query variables, and retire technical debt with more intention.
Current Status and Lessons Learned
As of the project's latest update, all four root screens of the Shopify Mobile app are now running React Native in production. The visual difference is nearly imperceptible to users, which was a primary goal. Any noticeable changes typically reflect UX improvements introduced during the port.
The root screen port took about four months, mostly spent on infrastructure that will be reused. Shopify's investment in open-source React Native frameworks was also leveraged, including:
- FlashList - A performant variant of FlatList
- React Native Performance - A performance monitoring framework
- React Native Skia - High-performance 2D graphics
Even with planning, unexpected challenges surfaced—from identifying missing dependencies to managing interop with existing native code. The team's iterative approach, coupled with a strong training foundation and clear prioritization, proved essential in navigating the surprises and keeping the migration moving forward.
Bridging to Native Screens
Porting the root screens created a new problem: the inner screens still lived in native code and were normally opened directly from a View Controller or Fragment. To let React Native reach them, the team built a custom mechanism called Native Routes. It registers each native screen under a custom name on both Android and iOS, and adds a navigator in React Native that can target those names. With that in place, the old backing View Controller and Fragment could be removed entirely for each ported screen, cutting down the remaining native surface area.
Deeplinks: Two Platforms, One Pattern
Android had a straightforward deeplink story because it opened the destination screen as a modal. iOS was harder: the app constructed the whole navigation stack to reach an inner screen. Since the root screens were also the entry points for deeplinks, the initial plan was to recreate that stack behavior in React Native. But with multiple screens being ported at the same time, some flows produced complex mixed chains like RN→Native→Native→RN. That approach wasn't sustainable.
The iOS team ended up adopting the Android pattern instead: open the deeplink target as a modal. Because Native Routes were already being built for each inner screen, the change was straightforward and shipped quickly.
Safe Area Handling
Safe Area support took some experimentation. The first attempt was wrapping outer components in SafeAreaView, but that didn’t work for every case. Horizontal safe areas in particular needed the content of each list cell to be inset while the cell’s background stretched edge to edge. Wrapping the cell itself would have made the background respect the Safe Area incorrectly.
The resolution was to push Safe Area handling down into the shared UI components. Once the team realized the fix belonged on the content inside each component rather than on the component wrapper, the pattern became much easier to apply consistently.
What’s Next
With the root screens done and the infrastructure in place, porting speed has increased. Most developers on the team had no prior React Native experience, so their growing familiarity with the framework is compounding and accelerating the remaining work. The project does require switching between native code and React Native constantly, which is demanding, but the team views it as a chance to grow. The path to a fully React Native Shopify app is getting shorter, though there is still meaningful work ahead.



