React Native’s New Architecture: A Two-App Migration Playbook

Shopify has moved both Shopify Mobile and Shopify Point of Sale (POS) onto React Native’s New Architecture — without pausing the weekly release cadence that serves millions of merchants. The codebase in question spans hundreds of screens and native modules, extensive custom components, and deep integration with first-party libraries such as FlashList.

The team reports three key outcomes from the effort: development velocity was maintained throughout the migration, feature development saw zero disruption, and a set of common migration issues at scale were identified and solved. Below is a breakdown of the approach, the decisions that shaped it, and the lessons learned for other teams facing the same transition.

Guiding Principles for a Large-Scale Move

The migration strategy was anchored on three principles, each designed to keep the codebase shippable while the underlying architecture shifted.

  1. Minimize code changes first, refactor later. Only the minimum necessary code changes were made to enable the New Architecture. Optimization and refactoring were deferred until after the switch — the priority was to stop introducing new code that would break the build on the new architecture.
  2. Maintain dual architecture compatibility during development. Both old and new architectures were supported throughout the process, allowing continuous testing and preventing regressions. Old architecture support was removed only after the new one shipped.
  3. Maintain performance and stability parity. Before going to production, the New Architecture had to match or exceed the old on TTI (time to interactive) metrics and crash-free sessions.

Keeping the Ship Moving

Shopify ships app updates weekly, so pausing feature development during migration was never an option — technical difficulties could delay essential features or bug fixes. Instead, the team balanced migration progress with merchant needs using two techniques.

Dual architecture testing. Using TopHat, the team generated builds for both architectures on every pull request. This enabled easy testing without local rebuilds and prevented changes that broke the new architecture from ever merging.

Conditional functionality. For third-party dependencies that didn’t support both architectures on a single version, feature flags were used to conditionally disable functionality in development mode on the new architecture.

Key insight for library maintainers: provide a version that supports both architectures. Managing conditional versioning isn't trivial — dual support significantly eases the migration burden. This is exactly why both architectures were maintained in the FlashList v2 alpha versions.

Migration Mechanics: Modules, Dependencies, and Code

Shopify's migration strategy deliberately avoided converting any of its 40+ native modules to TurboModules. Instead, the team forked implementations behind feature flags for modules that failed on the new architecture—primarily those touching UIManager—to keep both architectures working in parallel. The decision to defer TurboModule adoption was pragmatic: the team wanted to reassess which modules remained useful and how their APIs could be improved once the dust settled.

Dependency management was an exercise in triage. Incompatible libraries were updated to dual-architecture versions, while unmaintained ones were patched or removed outright—a side benefit being a leaner dependency footprint. The team also made upgrading to the latest React Native version a prerequisite, not an afterthought. Because the new architecture receives continuous bug fixes, staying current meant most issues were resolved by version bumps rather than custom patches. App code changes were kept minimal, limited to ensuring feature code ran on both architectures, with deprecation warnings added to temporary code paths for later cleanup.

Common Pitfalls and Their Fixes

The new architecture's synchronous rendering and revamped native module interfaces exposed several recurring failure modes. Some were expected growing pains; others revealed latent tech debt.

State Batching Breaks Assumptions

Batched state updates, a hallmark of the new architecture, caused components that relied on intermediate state values to fail. The fix was straightforward: refactor such components to remove timing dependencies. In large codebases, this kind of tech debt accrues naturally, and migration provides a forced cleanup opportunity.

Blank Screens Point to TurboModules

Blank screens—dubbed "Blank Screen of Doom"—almost always traced back to a TurboModule using hacky implementations or outdated architecture-specific APIs, particularly those manipulating UI. Debugging involved commenting out components and providers in the main App component until the culprit surfaced. The UIManager migration discussion proved especially useful for these cases.

Shadow Tree Manipulation

Manipulating React Native views from native UIManagers caused severe UI desync, breaking tap gestures and sometimes crashing apps. Mobile Bridge's TransportableView—which swapped WebView components declared in React Native—failed at best and crashed at worst. The team's solution was to remove the WebView from React Native entirely and manage its lifecycle natively, preserving compatibility with both architectures. Alternative approaches include migrating to pure React Native, full native management, or custom shadow nodes.

View Flattening Side Effects

View flattening, which optimizes out unnecessary components, occasionally removed views carrying refs, leaving ref perpetually null. This broke downstream components, most notably with ActionSheetIOS. Appium-based end-to-end tests also failed to locate flattened views. The team addressed this with a Babel plugin that automatically sets collapsable={false} on components with explicit test identifiers. The general fix is adding collapsable={false} to any view with a ref. Notably, Android already had view flattening pre-migration, so these bugs skew toward iOS.

Main-Thread Deadlocks

App hangs on iOS during launch stemmed from legacy native modules loading on the main thread while animations ran concurrently, causing a deadlock. The issue was nearly impossible to reproduce in development but generated numerous production crashes. The fix: set requiresMainQueueSetup to false on legacy native modules unless strictly necessary—which is rarely the case.

Animation Performance at Scale

Shopify's heavy reliance on Reanimated for navigation exposed severe frame rate drops on both platforms—issues only visible at Shopify's scale and complexity. The response from Software Mansion and Meta was exceptional, with early patches addressing most problems. These fixes are being integrated upstream into Reanimated and React Native. For most apps, Reanimated remains the recommended choice; the performance concerns manifest primarily with complex animations at scale.

Rollout Strategy: Android First, Phased, and Monitored

Since the new architecture cannot be gated by a remote feature flag, Shopify relied on a careful rollout exploiting the differences between app stores. Google Play allows fine-grained percentage control and instant installation halts; the App Store's gradual schedule cannot pause new installs (only updates), and approval can take 24 hours. Shopify's schedule leveraged this:

  • Day 1 — Android 8%, iOS 0%: early signals from a platform that can be fully stopped immediately.
  • Day 2 — Android 30%, iOS 1%: substantial Android increase while keeping iOS adoption low for reaction time.
  • Day 3 — Both at 100%: confidence high enough for full rollout to gather scale data.

The emergency response plan was tiered against Shopify's 99.95% crash-free session target:

  1. Stability above 99.80%: fix forward on the next weekly release.
  2. Stability between 99.00% and 99.80%, or a broken critical flow with a known fix: pause rollout and hotfix.
  3. Stability below 99.00% or a broken critical flow without a quick fix: rollback.

Rollback was a last resort—operationally heavy and a significant timeline setback. The team preferred pausing early to address stability, preserving the scale data needed for progress.

Post-Migration Scorecard

The migration succeeded with minimal disruption, maintaining Shopify's weekly shipping cadence throughout. Immediate wins from Fabric adoption included:

  • App launch times improved by ~10% on Android and ~3% on iOS.
  • Screen rendering simplified through measure before paint, enabling smoother, faster loads (detailed in the FlashList v2 post).
  • Batched state reduced unnecessary re-renders, making tab switching snappier.

Not everything went smoothly. Some screens required post-release tuning: load times increased up to 20% on complex components because the new rendering path invalidated design assumptions. Session stability initially dipped below the 99.95% target before recovering after weeks of bug fixes. ANR crashes spiked on both platforms—some caused by custom Reanimated/React Native patches, others from main-thread native module initialization that was benign on the old architecture. None of these performance issues were attributable to the new architecture itself; they exposed pre-existing component design flaws.

Recommendations for Other Teams

Based on migrating a large-scale production app serving millions of users, Shopify's advice:

  1. Audit dependencies early — identify compatibility issues before writing migration code.
  2. Upgrade to the latest RN version first — release the upgrade separately to avoid conflating issues and minimize blast radius.
  3. Enable Fabric in development ASAP — early exposure signals which areas need attention, while maintaining old architecture compatibility.
  4. Search before solving — many issues are already documented in GitHub issues and community discussions.
  5. Minimize changes initially — prioritize bug fixes over optimizations before release.
  6. Be strategic with native modules — migrate only where benefits are clear.
  7. Plan for temporary degradation — leverage store rollout controls to manage risk during phased releases.
  8. Prefer fixing forward — avoid rollbacks to preserve momentum and timeline.

Post-Migration Focus: Optimization Over Compatibility

With the foundation now stable, Shopify's engineering focus shifts from making everything work to making everything faster. The new architecture unlocks approaches that the legacy bridge-based system simply could not support, directly targeting long-standing performance bottlenecks.

The immediate roadmap centers on three key areas:

  • Targeted TurboModule conversions: Moving high-frequency access patterns—such as reading user preferences and feature flags—off the serialization-heavy bridge to trim overhead in performance-critical paths.
  • Synchronous layout features: Leveraging the ability to measure and apply layout synchronously to build UI components that avoid visual jumps and render complex views more efficiently.
  • Startup performance work: Employing lazy TurboModule initialization and improved rendering strategies specifically to reduce time-to-interactive and overall app launch duration.

The Path Forward and Acknowledgments

Shopify is quick to credit its partners in this effort, specifically Meta and Software Mansion, whose collaboration was essential at every stage. The broader React Native community also played a critical role; the shared knowledge found in GitHub discussions, engineering blogs, and open-source projects provided the groundwork for this migration.

While the project demanded considerable engineering time, the consensus is that the payoff justifies the cost. The new architecture offers concrete advantages that directly improve the merchant experience: synchronous layouts promise to eliminate UI jank, and TurboModules deliver faster interop with native code. For Shopify, these aren't just architectural improvements—they are tools for building the fluid, responsive interfaces their users expect. As the new architecture solidifies as React Native's future, Shopify's investment in it signals a long-term commitment to the framework's roadmap.