← Back to blogReact Native

React Native's new architecture: what changed in our production app

April 9, 2025·7 min read

React Native's new architecture has been discussed for years. JSI replaces the bridge. Fabric replaces the old renderer. TurboModules replace the old native module system. The theoretical benefits are clear: synchronous native calls, concurrent rendering, lazy module loading.

We migrated a production healthcare app with 50,000+ active users to the new architecture in early 2025. This is what actually happened.

What the migration involved

The migration wasn't a single flag change. It touched three layers:

  1. Enabling the new architecture in the build configuration
  2. Updating native modules to the TurboModule spec
  3. Updating UI components that relied on the old renderer's behaviour

Build configuration

For Android:

# android/gradle.properties
newArchEnabled=true

For iOS:

# ios/Podfile
ENV['RCT_NEW_ARCH_ENABLED'] = '1'

This was the easy part. The hard part was everything that broke afterward.

What broke

Native module incompatibilities

We used 14 native modules. Of those, 9 had already been updated for the new architecture. The remaining 5 had not:

  • A custom analytics bridge
  • A Bluetooth module for medical device connectivity
  • A custom camera module
  • A file encryption module
  • A biometric authentication wrapper

For each, we had two options: wait for the maintainer to update, or write a TurboModule wrapper ourselves. We chose to write wrappers for the three critical ones (Bluetooth, encryption, biometrics) and keep the other two on a compatibility layer that React Native provides for old-style native modules.

The compatibility layer works but negates some of the new architecture's performance benefits for those modules. The calls still go through a bridge-like path.

Animation regressions

Our app used Animated.createAnimatedComponent extensively. The Fabric renderer handles animated values differently. Several animations that worked correctly on the old architecture had visual glitches:

  • A slide-in panel animation that showed a single frame of the final position before animating
  • A fade-out animation that jumped to opacity 0 instead of transitioning
  • A layout animation on a list item that caused a flash of the unmounted component

The root cause for all three was the same: Fabric's synchronous rendering meant that state updates and layout calculations happened in a different order than the old asynchronous renderer. The animations assumed the old ordering.

The fix was migrating these animations to react-native-reanimated, which works on the native thread and isn't affected by the renderer change. This was additional work but resulted in smoother animations overall.

Testing the migration

We couldn't deploy the new architecture to all users at once. The risk was too high for a healthcare app where reliability is non-negotiable.

Our approach:

  1. Internal testing for two weeks (the engineering team used the new architecture builds)
  2. Beta release to 500 users for one week
  3. Gradual rollout: 10%, 25%, 50%, 100% over three weeks
  4. Rollback plan: the old architecture build was maintained in parallel for the entire rollout period

We found and fixed 12 issues during internal testing, 3 during beta, and 1 during the gradual rollout. The one production issue was a crash on a specific Samsung device running Android 11, caused by a native module that used a deprecated API removed in the new architecture's native layer.

The measurable impact

Startup time

The most significant improvement. Cold start time (from app launch to interactive home screen):

  • Old architecture: 3.1 seconds (median, mid-range Android)
  • New architecture: 2.0 seconds (median, same device)

A 35% reduction. The improvement comes from two sources: TurboModules load lazily (only when first accessed, rather than all at startup), and JSI eliminates the bridge serialisation overhead during initialization.

JavaScript execution

JSI replaces the asynchronous bridge with synchronous native calls. In practice, this means native function calls that previously took 1-5ms (bridge serialisation + message queue + deserialisation) now take microseconds.

For a single call, this isn't perceptible. For operations that make many native calls in sequence (layout calculations, animation setup, data serialisation), the cumulative effect is significant.

We measured the time to render our most complex screen (a session detail view with a message list, participant info, and real-time status indicators):

  • Old architecture: 180ms
  • New architecture: 95ms

Almost a 2x improvement. The screen makes about 200 native calls during initial render (style calculations, layout, native component creation). Each call was a bridge crossing. With JSI, those 200 calls complete in a fraction of the time.

Memory

No significant change. Memory usage was within 5% of the old architecture, which is within normal measurement variance.

What the migration delivered vs expectations

Expected: dramatically faster animations. Actual: no visible change for most animations. The bottleneck for animation smoothness was already the native thread's rendering capacity, not the bridge. Animations that were already at 60fps stayed at 60fps. Animations that were janky due to JS thread contention improved because the JS thread had less work.

Expected: simpler native module development. Actual: more complex initially. TurboModules require Codegen, which generates C++ interfaces from a JavaScript spec. The setup is more involved than the old NativeModules.MyModule approach. The benefit's type safety between JS and native, which catches errors at build time rather than runtime.

Expected: concurrent rendering (Suspense, transitions). Actual: not yet. Fabric supports concurrent rendering in theory, but React Native's implementation doesn't expose it to user code yet. This is expected in future releases.

Expected: easy migration. Actual: took three weeks of engineering time and two weeks of cautious rollout. For a production app with native modules, the migration is non-trivial. For a new app or one using only JavaScript libraries, it would be much simpler.

Was it worth it

Yes, but not for the reasons I expected. The startup time improvement alone justified the migration. Users commented on the app feeling faster without knowing anything had changed. For a healthcare app where staff open the app in urgent situations, a one-second faster startup is meaningful.

The JSI performance improvement for complex screens was a bonus. The animation regressions were the most time-consuming part to fix, and they resulted in better animations after the migration (because we moved to Reanimated, which we should have been using already).

If you're starting a new React Native project, use the new architecture from day one. If you're migrating an existing production app, budget for 2-4 weeks depending on your native module dependencies, and plan a gradual rollout.

RESPONSES

Leave a response