INTRODUCTION
While working on a major architectural upgrade for a high-traffic FinTech mobile application, we set out to transition the platform to Expo SDK 54 and React Native 0.81.5. The goal was to fully leverage the React Native New Architecture and the latest Hermes engine optimizations to handle complex, real-time financial charting.
Everything functioned seamlessly in our local development environments. However, when we pushed the update to our CI/CD pipeline using EAS Build, the iOS builds abruptly halted. We were greeted with a wall of obscure C++ linker errors explicitly complaining about missing symbols for the arm64 architecture.
When you are building applications that handle secure financial transactions, unpredictable deployment failures are unacceptable. This issue surfaced a deep architectural friction point between prebuilt React Native frameworks and locally compiled native modules. We realized that resolving this required diving into the C++ ABI (Application Binary Interface) layer of the JavaScript Interface (JSI). This challenge inspired this article so other engineering teams can avoid similar deployment blockers when modernizing their mobile stacks.
PROBLEM CONTEXT
Modern React Native relies heavily on JSI to enable synchronous communication between JavaScript and native C++ code. This is particularly relevant when using advanced libraries like react-native-reanimated or Expo’s native modules, which bypass the old asynchronous bridge for performance.
In our FinTech application, the architecture relies on Expo Application Services (EAS) to orchestrate cloud builds. To speed up CI/CD execution, EAS frequently utilizes prebuilt binaries for core React Native dependencies (like React-Core-prebuilt). The application integrates heavily with native modules that must be compiled on the fly during the build process.
The issue appears at the boundary where the locally compiled C++ code (from Expo modules and Reanimated) attempts to link against the precompiled C++ code inside the React Native and Hermes frameworks. If the C++ ABI versions do not perfectly align, the iOS linker cannot resolve the symbols, leading to a hard build failure.
WHAT WENT WRONG
The EAS build logs revealed a fatal linking error during the final stages of the iOS compilation phase. The symptoms were isolated to arm64 architecture targets:
Undefined symbols for architecture arm64:
"vtable for hermes::vm::NopCrashManager", referenced from:
hermes::vm::RuntimeConfig::RuntimeConfig() in libExpoModulesCore.a[7](EXJavaScriptRuntime.o)
"typeinfo for facebook::jsi::HostObject", referenced from:
typeinfo for expo::ExpoModulesHostObject in libExpoModulesCore.a[26]
typeinfo for facebook::react::TurboModule in libRNReanimated.a[49]
"typeinfo for facebook::jsi::NativeState", referenced from:
typeinfo for expo::EventEmitter::NativeState in libExpoModulesCore.a[3]
ld: symbol(s) not found for architecture arm64
clang: error: linker command failed with exit code 1Analyzing the logs, we noticed that libExpoModulesCore.a and libRNReanimated.a were failing to locate basic virtual tables (vtable) and type information (typeinfo) for core Hermes and JSI classes. We also observed references containing specific ABI tags like [abi:ne180100] in the raw symbol outputs.
This confirmed an architectural oversight in the dependency chain: the locally compiled static libraries were being built with headers expecting one specific C++ standard and ABI structure, while the prebuilt React Native frameworks pulled in by EAS exported a different ABI version. The missing vtable usually indicates that a virtual member function lacks a definition within the expected ABI namespace.
HOW WE APPROACHED THE SOLUTION
Our initial diagnostic steps focused on isolating the variables in the EAS environment:
- Toggling the New Architecture: We explicitly enabled and disabled
newArchEnabled. The error persisted, indicating the issue was tied to the underlying JSI/Hermes engine integration, not just the TurboModules architecture. - Switching Build Environments: We migrated the EAS build image between different macOS and Xcode versions (from
xcode-16.2to newer environments). The linker failure remained constant, ruling out an Xcode-specific bug. - Forcing Source Compilation: We set
buildReactNativeFromSource: truewithin theexpo-build-propertiesplugin to force React Native to compile from source rather than using prebuilts. Surprisingly, this still failed with the same linker error, suggesting the injected compilation flags were universally misaligned. - Isolating Dependencies: We temporarily uninstalled
react-native-reanimatedand cleared theios/directory to force a pristineexpo prebuild. The error shifted entirely tolibExpoModulesCore.a, proving the issue was systemic to how C++ headers were being resolved across all native modules.
We deduced that the C++ language standard applied during the local compilation phase of the CocoaPods was diverging from the standard used to build the Hermes engine. React Native 0.81.5 introduces stricter C++ requirements (often migrating toward C++20). If the local Pods default to C++17 or lack the correct compiler flags, the resulting ABI tags will mismatch, breaking the linker.
When organizations hire software developer teams to handle complex migrations, they expect this level of root-cause analysis rather than blindly applying temporary patches.
FINAL IMPLEMENTATION
To resolve the ABI mismatch without permanently ejecting from the managed Expo workflow, we needed to enforce strict C++ standard alignment across every native module compiled during the Pod installation phase.
We achieved this by injecting a robust post_install hook into the iOS Podfile. This script iterates through every target and forcefully overrides the C++ language standard to match the one expected by the React Native 0.81.5 prebuilts.
Here is the sanitized implementation deployed to our configuration:
post_install do |installer|
react_native_post_install(installer)
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
# Force alignment of C++ language standard for ABI compatibility
config.build_settings['CLANG_CXX_LANGUAGE_STANDARD'] = 'c++20'
config.build_settings['CLANG_CXX_LIBRARY'] = 'libc++'
# Ensure existing C++ flags are preserved while adding Folly/JSI specific overrides
existing_flags = config.build_settings['OTHER_CPLUSPLUSFLAGS'] || ['$(inherited)']
if existing_flags.is_a?(String)
existing_flags = existing_flags.split(' ')
end
unless existing_flags.include?('-DFOLLY_CFG_NO_COROUTINES=1')
existing_flags << '-DFOLLY_CFG_NO_COROUTINES=1'
end
config.build_settings['OTHER_CPLUSPLUSFLAGS'] = existing_flags.join(' ')
end
end
end
Validation Steps:
- We triggered a fresh
expo prebuild --cleanlocally to generate the updated Podfile. - We cleared the EAS build cache to ensure no tainted intermediate object files (
.o) were reused. - We executed the cloud build. The C++ compilers across all native modules correctly aligned their ABI tags with the Hermes headers, and the
arm64linker completed successfully.
By programmatically aligning the C++ standards via the CocoaPods lifecycle, we maintained the convenience of EAS prebuilds while satisfying the stringent linking requirements of modern React Native architectures.
LESSONS FOR ENGINEERING TEAMS
This scenario underscores several critical insights for teams managing large-scale mobile platforms:
- ABI Boundaries are Unforgiving: When dealing with JSI and Hermes, treating JavaScript as merely a scripting layer is a mistake. Your mobile application is a C++ application, and C++ ABI rules apply strictly.
- Understand Your CI/CD Caching: EAS and other cloud build providers cache precompiled artifacts aggressively. When debugging linker errors, always clear the remote cache to validate your configuration changes.
- Prebuilts Require Flag Parity: If you consume prebuilt binary frameworks, your local compilation flags (like
CLANG_CXX_LANGUAGE_STANDARD) must be in perfect parity with the flags used to generate those frameworks. - Automate Dependency Fixes: Avoid manual patching of Xcode projects. Use
post_installhooks or Expo Config Plugins to programmatically apply build settings, ensuring reproducibility across environments. - Version Alignment is Critical: Moving to cutting-edge versions like Expo SDK 54 requires ensuring that third-party modules (like Reanimated) have explicitly published support for the new JSI headers.
- Strategic Talent Allocation: Transitioning to the New Architecture is not a trivial task. When you hire react native developers for enterprise modernization, ensure they have proven experience traversing the bridge between JavaScript and native C++ build systems.
- Continuous Monitoring: Linker errors are often the first symptom of deeper architectural incompatibilities. Set up your pipelines to fail fast and retain build artifacts for rapid debugging.
WRAP UP
Upgrading to Expo SDK 54 and React Native 0.81.5 brings incredible performance benefits, but it also introduces strict C++ ABI alignment requirements. By understanding how JSI interfaces with the Hermes engine at the compiler level, we successfully unblocked our CI/CD pipeline and delivered a highly performant, resilient FinTech application. If you need engineering maturity to navigate complex architectural upgrades, contact us to explore how our dedicated teams can drive your next deployment.
Social Hashtags
#ReactNative #ExpoSDK54 #Expo #HermesEngine #JSI #MobileDevelopment #iOSDevelopment #EASBuild #ReactNativeDev #FinTechDevelopment #TurboModules #NewArchitecture #SoftwareEngineering #DevOps #CPlusPlus
Frequently Asked Questions
The New Architecture relies on the JavaScript Interface (JSI) for direct, synchronous communication between JS and native code. This requires compiling native modules directly against Hermes and React Native C++ headers. If the C++ compiler standards (e.g., C++17 vs C++20) do not match exactly, the ABI tags differ, resulting in missing symbols during the linking phase.
For most CI/CD pipelines, prebuilts (like those used by EAS) drastically reduce build times and infrastructure costs. Building from source should be a fallback for debugging or when you require custom core modifications. When you hire ios developers for native module optimization, they should know how to align local compilation with these prebuilts rather than abandoning them.
A vtable (virtual table) is used in C++ for dynamic dispatch. When the linker says it cannot find a vtable for a Hermes class, it means the class definition was found in the headers, but the compiled binary object containing the actual implementation was either compiled for a different architecture or exported under a different ABI namespace.
The issue was not inherently a bug in the Xcode toolchain, but rather a configuration mismatch within the React Native CocoaPods environment. The specific CLANG_CXX_LANGUAGE_STANDARD build settings define the ABI structure, which remains misaligned regardless of the Xcode version unless explicitly corrected in the Podfile.
Ensure your custom modules are strictly adhering to the Expo Modules API and that your library's internal Podspec correctly inherits the C++ standard required by the React Native version you are targeting. For teams looking to scale, it is beneficial to hire mobile developers for scalable app architectures who understand these low-level build configurations.
Success Stories That Inspire
See how our team takes complex business challenges and turns them into powerful, scalable digital solutions. From custom software and web applications to automation, integrations, and cloud-ready systems, each project reflects our commitment to innovation, performance, and long-term value.

California-based SMB Hired Dedicated Developers to Build a Photography SaaS Platform

Swedish Agency Built a Laravel-Based Staffing System by Hiring a Dedicated Remote Team

















