In 2024, the fintech company Found rebuilt the native layer of its hybrid mobile app, and the results were hard to ignore. iOS cold-start times fell from somewhere between twenty and forty seconds to around ten. On Android, the rate of freezes users actually noticed dropped from roughly 0.60% to 0.20% within a few months, as the two engineers behind the project detailed in June 2025. Nobody rewrote the app from scratch, and the web frontend didn’t change at all. What changed was the native layer underneath it, and, more importantly, the team’s thinking about what really belonged there.
Most teams end up facing that same question sooner or later. A new feature exposes a gap, a plugin falls behind on OS updates, or a performance complaint just won’t go away. It’s tempting to frame it as hybrid versus native, as if the only options are sticking with what you have or rewriting everything in Swift and Kotlin. In practice, the better question is much narrower: does this particular gap really need native code? And if it does, how much, who owns it, and for how long?
When does a hybrid app actually need a native module?
Before you write a single line of native code, rule out the simpler explanations.
The framework genuinely can’t do it. React Native’s own documentation is pretty direct about this. Sometimes your app code needs to talk to platform APIs that neither the framework nor an existing library provides, and that’s exactly why Turbo Native Modules exist. If that’s really the case for your feature, you’re looking at a real gap, not a hunch.
The plugin you have doesn’t actually fit. Capacitor’s plugin architecture exists for this very reason: web apps need a consistent, cross-platform way to reach native APIs, and plugins handle the platform differences behind the scenes. When a plugin can’t do that cleanly, because the API doesn’t match what you need, support has gone stale, or the security story is unclear, it’s fair to look elsewhere.
You’ve actually measured a performance problem. A gut feeling that native code “should” be faster doesn’t count. Profile the operation first and let the numbers make the case.
The problem isn’t native at all. A misconfigured permission, a framework version mismatch, or a plain old plugin bug can look exactly like a missing capability from the outside.
React Native’s Turbo Modules, Capacitor plugins, and Cordova plugins each handle this differently, and the implementation details don’t carry over cleanly from one framework to another. So do the diagnosis inside whichever framework you’re actually using.
Plugin, custom module, or a bigger rebuild
Once you’ve confirmed there’s a real gap, the next question is scope.
Option
Fits when
Main trade-off
Existing supported plugin
It meets functional, security, and compatibility needs.
Lower effort now, dependency on someone else’s release schedule
Extend or replace a plugin
Current integration is close but has one clear limitation.
Less work than a full build, still needs a compatibility check
Custom native module
A real gap remains, and the team can own it.
Full control, full responsibility for upgrades and testing
Broader architecture change
Repeated, measurable pain across releases
Significant migration cost, justified only by sustained evidence
What a plugin audit should actually check
Whether a plugin works today is only part of the picture. Think in terms of the next two years, not the next sprint. Which framework and OS versions does it support? How actively is it maintained? What do its security posture and license look like? And could your team take it over if it breaks? GitHub stars aren’t a safety signal, and a quiet repo isn’t automatically a dead one.
Be just as honest about cost. Look at the total cost of ownership, not just the build: initial development, integration, testing, compatibility updates, incident response, documentation, and eventual replacement. Teams that only price the first item are usually the ones surprised by the fifth.
Where to draw the line between shared and native code
Android’s own architecture guidance sums up the principle well. Separation of concerns means splitting an app into methods, classes, files, packages, modules, and layers, each with clear responsibilities and boundaries. In a hybrid app, that means keeping business rules, validation, and orchestration shared wherever it makes sense. Platform API calls, native SDK work, and permission and lifecycle handling should sit behind a defined interface instead of being scattered across the codebase.
Picture it like this: shared app logic calls a defined interface, the interface hands off to a platform-specific adapter, and the adapter talks to the native API. iOS and Android both sit behind that same interface, but don’t assume they’ll behave the same way. They usually don’t.
This boundary matters for security as well as tidiness. OWASP’s mobile security standard lists sensitive native functionality exposed through a WebView bridge as a risk in its own right, and its best-practice guidance recommends limiting what that bridge can reach instead of leaving it wide open. It’s a narrow rule, but it’s especially relevant to hybrid apps, because that bridge is a seam a fully native app never has.
Build the module to survive its second year
A native module proves its worth over years, not in its first sprint. The interface contract should spell out input and output types, how errors are mapped, async behavior, timeouts, permission requirements, and lifecycle expectations. React Native’s typed specs and Codegen are one concrete way to enforce this. React Native’s documentation describes a Turbo Native Module as three things together: the spec, the native code, and the generated Codegen interfaces.
Give failure cases real attention instead of leaving them for later: denied permissions, unsupported devices, missing hardware, interrupted operations, and the app being sent to the background mid-call. This is where iOS and Android will differ, however clean the JavaScript-facing API looks.
Write down ownership while everyone still remembers the details. Who maintains the module? Which OS and framework versions does it support? Which third-party SDKs does it depend on? How are upgrades evaluated? A module without a named owner tends to become everyone’s problem the moment it breaks.
The real cost shows up after launch
Building the module is only the first line on the bill. A realistic lifecycle checklist covers integration with the shared layer, native build and release setup, testing on every supported platform, compatibility work as OS and framework versions move on, dependency updates, incident investigation, documentation, and eventual migration. Compare that total with your realistic alternative, not with an imaginary plugin that never needs maintenance.
Test on both platforms, not just the one open in the editor
Unit and integration tests cover the contract. Testing on real devices covers what they can’t: permissions granted and denied, unsupported devices, lifecycle transitions, interrupted operations, and release-build behavior that debug builds sometimes hide. For anything high-impact, a staged rollout with crash and error monitoring lets you catch problems before they reach every user. Whether you can actually roll back depends on your release setup, so don’t take it for granted.
When a growing native layer means it’s time to rethink the architecture
A growing native layer isn’t, on its own, a reason to rewrite anything. Found’s migration shows what the alternative looks like. The team found that Cordova’s plugin ecosystem kept lagging behind new OS releases, which caused crashes on beta versions, and that building custom plugins meant a fragile, manual copy-paste workflow. Even so, they didn’t rewrite the app. They replaced the native layer with Capacitor, kept the same web frontend, migrated plugins one at a time, and staged the rollout with health monitoring before releasing it to everyone. In their own retrospective, the one thing they said they’d do differently was build in a remote switch between the old and new native layers, so reverting wouldn’t require shipping a new app version. That’s a lesson from one team’s situation, not a universal rule. But it shows the broader point: decide whether to rethink the architecture based on evidence and risk, not on how many lines of native code you have.
Keep the boundary intentional
Diagnose the actual limitation. Check what already exists before building something new. Pick the smallest fix that solves the problem. Keep shared logic shared, and keep native code behind a boundary that can survive years of upgrades, not just the next release. Teams that treat this as a one-off task usually pay for it later in compatibility headaches and incident reports. Teams that keep revisiting the decision usually don’t.
If a feature request is stuck in that gray area right now, somewhere between “the plugin should handle this” and “we probably need custom code,” settle it before anyone writes a line.
FAQs
Does adding a native module mean maintaining two apps?
Not really. It adds platform-specific code, plus the testing and build work that comes with it, but your shared application layer stays intact as long as the boundary is designed well.
Will native code make the app faster?
Only if it solves a bottleneck you’ve measured or fills a gap the framework really can’t cover. Native code on its own doesn’t guarantee better end-to-end performance.
When does a hybrid app actually need a full rewrite?
Only when there’s real evidence, not just a growing file count, that the current architecture can’t reasonably meet the product’s needs.
How much testing does a native module really need?
Enough to cover permission flows, lifecycle transitions, and failure paths on both platforms. A green build isn’t enough.
Figuring out where your hybrid app genuinely needs native code, versus where a better plugin or a cleaner shared/native boundary would do the trick, is exactly the kind of call that benefits from a second set of eyes. Techosquare builds native, hybrid, and cross-platform mobile apps, including React Native-based hybrid builds, and works with teams facing these same trade-offs around shared code, platform-specific integrations, and long-term maintenance. If you’re weighing whether your next feature needs a custom native module, an extended plugin, or a bigger architectural rethink, take a look at Techosquare’s mobile app development services to see how their team approaches native and hybrid builds.
About the Author
Aarnik Mishra writes about mobile app development, custom software, and emerging technology trends. He’s mostly interested in the practical calls that come with building and maintaining digital products, picking the right tech stack, scoping requirements properly, and planning for scale and long-term upkeep, which is really the same thinking that goes into deciding when a hybrid app needs native code of its own.

Top comments (0)