How NetEase Cloud Music Supercharged RN with Hermes, Fabric, and TurboModule
This case study details NetEase Cloud Music's migration from React Native 0.60 to 0.70, evaluating performance, bundle size, memory usage, and stability while describing the practical integration of Hermes, TurboModule, and Fabric and the automation that reduced upgrade effort across 100+ RN modules.
Background
NetEase Cloud Music adopted React Native starting with version 0.33 and expanded to over 100 RN modules by the end of 2022. Although RN accelerated cross‑platform development, its JavaScript execution speed and multi‑thread communication caused noticeable gaps in startup time and interaction quality compared with pure native implementations.
Research and Performance Comparison
Before upgrading, the team built a demo to compare RN 0.60 (JavaScriptCore) with RN 0.70 (Hermes + bytecode pre‑compilation) on iOS iPhone 6, iPhone 12, Android Xiaomi 8 SE, and Redmi Note 9 Pro. The results showed substantial improvements:
First‑frame time: Android Xiaomi 8 SE improved by 71.5%, Redmi Note 9 Pro by 77.3%; iPhone 6 by 63%, iPhone 12 by 42%.
LCP (Largest Contentful Paint): Android improvements of 40.1%–41.9%; iPhone 6 by 48.5%, iPhone 12 by 18.3%.
Sample data (RN 0.60 vs RN 0.70):
First‑frame (ms) – iPhone 6: 1563.66 → 578.66
First‑frame (ms) – iPhone 12: 189.66 → 110
First‑frame (ms) – Xiaomi 8 SE: 987.2 → 281
First‑frame (ms) – Redmi Note 9 Pro: 743.4 → 168.4
LCP (ms) – iPhone 6: 2482.2 → 1276.6
LCP (ms) – iPhone 12: 886.5 → 724.25
LCP (ms) – Xiaomi 8 SE: 1720 → 1030.2
LCP (ms) – Redmi Note 9 Pro: 1358.6 → 788.4Impact on Bundle and App Size
Hermes bytecode pre‑compilation increased the raw bundle size by about 18% (uncompressed) and up to 57.6% after ZIP compression. In practice, ZIP‑compressed bytecode bundles grew 40%‑100%, which can affect download success on weak networks. To mitigate this, the team used the -base-bytecode flag when generating bytecode, reducing diff‑package size.
iOS IPA size grew from 1.1 MB to 3.1 MB when Hermes was added; Android binary size changed minimally (6.12 MB vs 6.14 MB).
Memory Consumption
Memory usage dropped dramatically with RN 0.70:
iOS: ~50% reduction (e.g., iPhone 6 from 42.2 MB to 21.4 MB).
Android: Xiaomi 8 SE memory fell from 208.4 MB to 139 MB; Redmi Note 9 Pro from 223.1 MB to 153.9 MB.
Other Performance Gains
TurboModule and Fabric together improved communication latency by over 50%. Long‑list scrolling frame rates remained stable, and overall UI interaction matched native performance.
New Architecture Adaptation
Hermes Integration
Hermes became the default JS engine in RN 0.70, offering ahead‑of‑time bytecode compilation. The team added the following command to generate bytecode and reduce diff‑package size:
hermes -emit-binary -out bundle.hbc -base-bytecode bundle.hbcThey also stripped HBC source maps from the final package and stored them in cloud storage, cutting package size by another 10%‑20%.
XZ compression was introduced, achieving >10% better compression than gzip at the cost of longer compression/decompression times, which were acceptable on mid‑range devices.
TurboModule Migration
TurboModule enables synchronous JS‑to‑native calls via C++ bridges (JSI/JNI). Code generation (Codegen) produces paired Java and C++ files. Developers must change void‑return methods to non‑void for synchronous behavior. The team refactored high‑frequency NativeModules to TurboModules while keeping existing NativeModules functional.
case VoidKind: {
TMPL::asyncMethodCallArgConversionEnd(moduleName, methodName);
TMPL::asyncMethodCallDispatch(moduleName, methodName);
nativeInvoker_->invokeAsync(/* implementation */);
TMPL::asyncMethodCallEnd(moduleName, methodName);
return jsi::Value::undefined();
}Fabric Migration
Fabric restructures the rendering pipeline into render, layout (Yoga), and mount phases, moving shadow‑tree creation and layout calculation to C++. This eliminates Java‑side overhead and improves data transfer efficiency. All NativeComponents had to be converted to FabricComponents; the team used Codegen to generate necessary C++ descriptors and modified ComponentDescriptorRegistry.cpp and SurfaceMountingManager.java to support both legacy and Fabric components during transition.
Frontend Code Adaptation
Beyond native changes, the upgrade required extensive JavaScript refactoring: updating APIs deprecated in RN 0.70 (e.g., findNodeHandle, setNativeProps), privatizing third‑party libraries, and using aliasing to prevent dependency bloat. The team automated many of these tasks with scripts, reducing per‑module manual effort from half a day to 1–2 hours.
Upgrade Practice and Cost Reduction
Automation Scripts : Scripts handled most dependency upgrades; only a few API changes required manual work.
Source Code Adjustments : Modified core RN source to make FabricComponent optional, enabling a mixed‑mode deployment.
Dual‑Package Build : Implemented a build pipeline that produces a single bundle compatible with both RN 0.60 and RN 0.70, allowing gradual rollout via Android split‑APK and iOS AB testing.
Stability Assurance
Added runtime downgrade switches for Hermes, FabricComponent, and TurboModule, controllable via remote configuration.
Implemented iOS dual‑dynamic‑library strategy to enable safe AB testing and phased rollout.
Upgrade Benefits
Performance : First‑frame and LCP times dropped dramatically, achieving sub‑second page loads even on low‑end devices.
Stability : Crash rates related to JavaScriptCore vanished; memory‑related crashes decreased thanks to lower memory footprints.
Related links: https://reactnative.dev/docs/next/new-architecture-intro https://reactnative.cn/architecture/fabric-renderer
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
