Technology Brief · Mobile Platform

The interface lives on the server

Our app's screens aren't hard-coded into the app. Each screen is described by a JSON document our servers compose on every request — the client reads it and renders fully native UI. We change what users see in minutes, with no app-store release and no waiting on review queues.


One JSON document describes the screen

The server returns an ordered list of sections. Each section says what it is, what it looks like, where its data lives, and what happens on tap. The client maps each entry to a native component — every key below controls the highlighted region on the phone.

// GET /v1/meta/home — composed per user & app version { "sections": [ { "sectionTitle": "Now Playing", // copy, editable live "viewType": "nowPlayingCard", // which native component "layout": "single", // rows, grid, carousel… "dataUrl": "/v1/playback/current", // section's own data feed "sortOrder": 1, // position on screen "artwork": { "width": 148, "height": 100 }, // card art size, dp "cardWidthRatio": 0.8, // card vs screen width "action": { "type": "inApp", "url": "app://player" }, "extraParams": { "showLiveBadge": true } // per-section flags }, { "viewType": "friendsListeningActivity", "sortOrder": 2, }, { "viewType": "topMonthlyAlbums", "sortOrder": 3, }, { "viewType": "trendingRightNow", "sortOrder": 4, } ] }
nowPlayingCard · sort 1 Now Playing
friendsListening… · sort 2 Friends listening
topMonthlyAlbums · sort 3 Top monthly albums
trendingRightNow · sort 4 Trending right now

How a screen gets to the user

Two tiers: a light "blueprint" call describes the screen; each section then loads its own data feed independently, so one slow feed never blocks the page.

Compose

Server assembles the section list per user — platform, app version, and account state (a brand-new user gets a different home than a power user).

Deliver

One small JSON payload. Sections carry their own data URLs, refresh intervals, and tap actions.

Validate

Clients check the payload against a strict schema. Anything unrecognized is skipped safely — old app versions never break.

Render

Each section type maps to a fully native component on iOS, Android, and web. No webviews — real native performance.

The server composes a different screen for every user

The payload isn't one static file — it's assembled per request from three signals. This is where server-driven UI stops being a rendering trick and becomes a product lever.

Account state

A first-time listener with no connected service gets an onboarding-focused home; an active listener gets the full feed — same app binary, different blueprint.

Platform

iOS, Android, and web each receive layouts tuned to their conventions from the same section catalog — one definition, three idiomatic renderings.

App version

Payloads are version-gated: newer builds get newer layouts, older builds keep receiving the layout they understand. Nothing in the wild ever breaks.

What we change without an app release

Everything on the left ships live, to every installed app, the moment we update the server. Honest boundary: brand-new component types still ride a normal release.

Live, in minutes

  • Section order, visibility, and layout (list ↔ grid ↔ carousel)
  • Card and artwork dimensions, card-to-screen width ratios
  • All copy and translations, per language, version-gated
  • Add or remove screens built from existing components
  • Feature flags and per-section options
  • Navigation targets and deep links
  • Bottom-tab set, data feeds, cache and refresh policy
  • Empty states, onboarding content, force-update gates

Needs an app release

  • A genuinely new visual component type
  • New native device capabilities (camera, biometrics…)

Built for the real world

A server-driven interface is only as good as its behavior when things go wrong. Resilience is designed in at every layer.

Offline & flaky networks

Every section caches on-device with its own server-set refresh interval. On network failure, the client serves the last good version — the app opens instantly, even offline.

Independent data feeds

Each section loads from its own endpoint. A slow "trending" feed can't block "now playing" — sections stream in as they arrive, skeletons first.

Strict validation

Every payload is schema-checked on device before render. Malformed or unknown content is dropped, never crashed on — a bad deploy degrades a section, not the app.

Localization, versioned

All copy ships with a version number. Clients re-download strings only when the version bumps — translations update fleet-wide in one server change, near-zero bandwidth cost.

New screens as config

Whole screens — a new feature tab, a seasonal recap, a campaign page — are defined as JSON files on the server, assembled from the existing component catalog. Shipping one is a config change.

Controlled sunsets

A force-update gate lives in the same config: when an app version must retire, the server can require an upgrade — the fleet never fragments beyond what we support.

A proven pattern, executed early

Server-driven UI is the architecture behind some of the most-iterated consumer apps in the world — Airbnb, Lyft, and Spotify each built internal SDUI platforms to escape the app-store release cycle. It's typically a scale-stage investment; we built it into the foundation, so every product decision from day one compounds at server speed.

Why this matters

Product velocity

UI experiments, seasonal campaigns, and fixes go out in hours, not the 1–2 week app-store cycle. Iteration speed compounds.

One brain, three platforms

A single server-side definition drives iOS, Android, and web simultaneously — consistent product, one place to change it.

Personalization built in

Because the server composes each screen per user, segmentation and A/B testing are a payload change, not an engineering project.

Safe by design

Strict client-side validation plus forward compatibility: unknown content is ignored, never crashed on. Every app version in the wild stays stable.

App-store compliant

The server sends data, not code. Every component is compiled, signed, reviewed native code — the JSON just arranges it. Same category as any content API.

A compounding catalog

Each new component ships once through the store, then joins the server-side building-block catalog forever. The platform gets more expressive with every release.