Skip to content

Cross-platform architecture

Sharing a Product UI Across Web and Native Hosts

Sharing React components only helps if the platforms also agree on identity, units, empty states and errors. I made those agreements explicit at the host boundary.

3 min read

Senior AI Engineer · M.S. Computer Science, Tsinghua University · See the products I built

In this article · 5 sections
One server assembler supplies a validated view model to Next server preload and native API fetch. Both hosts render the same shared screen, while ports provide routing, authentication, settings and device capabilities.
How it works. The platforms have different delivery mechanisms. They do not need separate definitions of the customer’s profile, location or practice history.Open full size ↗

The engineering decisions

  • Keep navigation, screens and responsive rules in one shared client.
  • Use host ports for platform capabilities and one server assembler for each view model.
  • Test serialized contracts and rejected states, not just matching screenshots.

Duplicate screens create duplicate product rules

A web page and a native screen can look similar while disagreeing on a unit, an empty state or which venues are visible. I moved player-facing screens, navigation and responsive behavior into one shared client package. The goal was to share product meaning as well as visual components.

The web host retained server rendering, routing, session handling and analytics integration. The Capacitor host retained startup, deep links, system permissions, secure capabilities and device connections. Ports exposed those differences without importing either platform’s runtime into the shared screens.

One assembler, two delivery paths

Each view model had one server-side assembly function. Next could preload it, while the mobile API serialized the same result for an asynchronous fetch. Profile, locations and later practice-history screens could therefore agree on fields and semantics before layout entered the picture.

The validators rejected combinations that should never be displayed: a guest profile carrying personal identity, duplicate location identities or closed locations in the public directory. A coming-soon venue did not become Open because it had an address. Mock identities and history remained visibly labeled in development previews.

Adapt the shell without forking the screen

The shared web shell switched its navigation from bottom bar to sidebar at the desktop breakpoint. It did not maintain a second desktop business DOM. The native host kept bottom navigation and presented some shared content in sheets through its own navigation adapter.

Settings such as display units remained device preferences rather than fields injected into a server view model. Practice measurements used established transport units and converted in the shared display boundary. That avoided a server-preloaded screen and an API-loaded screen applying different conversions.

GolfJoy native profile screen showing account state, server selection and unit preferences
In the product. Official Google Play sample interface. The host owns platform capabilities, while shared product screens define the visible states.Open full size ↗

An unavailable resource is not an empty account

The shared resource state machine distinguished loading, ready and error, with a working retry action. Server-preloaded web views could enter ready directly. A disconnected native client needed an error state rather than fabricated locations or a zero-valued practice history.

Session detail also enforced ownership and completed-state requirements in the query. Database exceptions were not converted into an innocent-looking empty result. A missing record, an unauthorized identifier and an unavailable backend need deliberate handling, even when the UI does not expose sensitive distinctions.

Check both the contract and where it can ship

Golden fixtures and serialized round trips checked that the hosts consumed the same contract. The migration also removed old native screen copies and used boundary checks to prevent them from returning. Browser captures covered both hosts at narrow widths and included a real network-error state.

The reviewed migration slices were merged with local end-to-end and build-matrix evidence. Their records explicitly say they had not yet been deployed at that stage. Production-semantic builds still excluded gated routes, and marketing and China builds excluded the mobile surface. A local shared-screen success did not authorize enabling it everywhere.

Project context and evidence

Written from my August to September 2026 shared-client architecture and migration records. Public links below support the product and attribution context. Internal project records are summarized, with customer records, credentials, and employer source code omitted.

All articles ↗