Cross-platform frameworks cover the common surface well. The moment you need a vendor SDK, a background capability or a hardware feature the framework has not wrapped, you write native code for both platforms - and that boundary, if drawn badly, becomes the most expensive part of the codebase.
01Check the ecosystem before writing anything
Most needs are already solved. Before writing a module, check whether a maintained community package exists, and evaluate it on release recency and issue responsiveness rather than star count.
A maintained third-party module you wrap is usually better than one you write, because someone else absorbs the platform churn. An unmaintained one is worse than writing your own, because you inherit the maintenance without the knowledge.
If you do adopt one, wrap it behind your own interface immediately. Replacing it later then touches one file rather than every call site.
02Draw the boundary at capability, not at API
The most common mistake is exposing the native SDK's shape directly to your application code. That couples your app to a vendor API, and swapping providers means rewriting everywhere.
Expose the capability your product needs instead - 'scan a document', 'authenticate biometrically' - and keep the vendor specifics inside the module. The interface should be describable without naming the vendor.
Keep the surface small and coarse. Every method is one you implement twice and test twice, and chatty interfaces across the boundary are both slower and more work.
- One module per capability, not one per SDK.
- Return plain serialisable data; complex objects across the boundary are a source of platform-specific bugs.
- Handle permissions inside the module so callers cannot forget them on one platform.
- Make errors structured and identical across platforms, or your app code will need platform branches.
- Write the same integration test against both implementations to catch behavioural drift.
03The costs teams underestimate
Native modules require native skills on both platforms, permanently. That is a hiring and continuity commitment, not a one-off task, and it is the real reason to minimise how many you have.
They also break on OS updates in ways your framework code does not. Platform APIs deprecate, permission models change, and background execution rules tighten - each of which lands on your module rather than on the framework maintainers.
And they complicate the build. Native dependencies mean platform-specific build configuration, which is where cross-platform build failures most often originate.
04Deciding whether to go native entirely
When the proportion of your feature surface requiring native code passes roughly a third, the cross-platform layer is adding indirection without saving much. At that point two native codebases can genuinely be cheaper than one shared codebase plus two native modules plus a bridge.
That is a real threshold and worth reassessing annually rather than treating the original framework decision as permanent.
Topics
Aarav Patel
Principal Engineer · SyncTrix
Writes about the engineering decisions behind production systems - architecture, delivery and the trade-offs that only show up at scale.
Building something like this?
SyncTrix engineers AI, SaaS, platform and cloud systems for enterprises and high-growth teams. Tell us what you're shipping and we'll scope it with you.
Talk to an engineer