Skip to content
← All insights

Cross-platform apps: what the shared layer should actually contain

Chris Bacon4 min read

The cross-platform conversation is almost always framed as a framework choice — one toolkit against another, with a benchmark and a bundle-size table. That framing skips the decision that actually matters.

The real question is not which framework. It is which layer you share. You can share everything, share nothing, or share a specific slice, and those three answers have very different consequences five years out.

This is one of the architectural decisions that custom software development should settle before a framework is chosen, because the framework choice follows from it rather than the other way around.

Three ways to answer it

Share everything. One codebase renders the interface on both platforms. This is fastest to a first release and it is a real answer for a lot of products. The cost arrives when a platform convention changes, or when the framework’s rendering diverges from the platform’s, and you are waiting on someone else’s release cycle to fix something your users can see.

Share nothing. Two native applications, written twice. Every pixel is right. Every rule is implemented twice, which means every rule can be implemented differently twice, and eventually one of them is.

Share the core, not the interface. The deterministic part — the rules, the calculations, the state machine — lives in one place. The interface is native on each platform.

Why the third one has held up

The shared-core case study on this site is exactly that arrangement: two native apps where the shared code is strictly the deterministic engine and every pixel is platform-native.

The reasoning is that the two layers age differently. Interface conventions change constantly and are platform-specific by nature — that is the whole point of a platform convention. Business rules change slowly and are not platform-specific at all. Sharing the layer that does not vary and duplicating the layer that does matches the shape of the problem.

It also puts the correctness-critical code in one place. If a discount is calculated wrong, it is wrong once, it is found once, and it is fixed once. Duplicated rules are where two apps silently disagree, and that class of bug is miserable to find because each app looks correct in isolation.

What it costs

More work up front, and this is not a small caveat.

You need a boundary that is genuinely deterministic — no platform calls, no implicit device state, nothing that behaves differently on one side. Designing that boundary takes real effort, and a leaky one is worse than no boundary at all, because you get the ceremony of a shared core with none of the guarantees.

You also need two people’s worth of interface work, or one person who is competent on both platforms. That is a staffing constraint, and for a small team it can be the deciding factor regardless of the architecture’s merits.

When I would not do this

If the application is mostly forms over an API, there is no meaningful deterministic core to share, and this architecture is ceremony around an empty box. Share everything and move on.

If the product’s survival depends on being in market this quarter, the fastest path is the correct path, and you can migrate the core out later when it exists and has stabilised. Choosing durability for a product that may not have a second year is optimising the wrong risk.

And if the rules genuinely differ by platform — different regulations, different payment handling, different feature sets — then the “shared” core is a fiction with conditionals in it, and two implementations are more honest.

The test I would apply

Write down the rules that must produce identical results on both platforms. If that list is short or empty, share the interface. If it is long, and getting it wrong would be visible to a customer or an auditor, share the core.

That is the whole decision. The framework comparison table is downstream of it and considerably less interesting.

How to tell whether the boundary is real

A shared core that is not genuinely deterministic gives you the maintenance cost of a shared layer with none of the guarantees, so it is worth testing rather than assuming. The test I use is blunt: can the core be exercised with no device present, no network, and no clock, and does it produce identical output from identical input every time.

If any of those three has to be relaxed, the boundary has leaked, and the leak will eventually show up as a bug that reproduces on one platform and not the other. Those are among the most expensive bugs to chase, because the first day is spent establishing that the difference is real rather than imagined.

Related reading: application development covers the other three decisions that set the cost. The development process covers where this decision sits in the sequence and why it comes before a framework is chosen. And product development covers whether you are building something with a five-year horizon at all — which is the assumption this entire argument rests on.

What I have not measured

I do not have a clean comparison of total cost between these three approaches on equivalent products, because nobody builds the same product three ways. What I can say is which failure modes each one produces, and that is a weaker claim than a cost figure would be. Anyone offering you the cost figure has not measured it either.