Skip to content

Server-driven UI: where it helps and where it hurts

Server-driven UIOpinion5 min readMiniwall team

Part of the series: Server-driven UI: a field guide for app teams

Server-driven UI is worth it for a screen that changes often, runs on more than one platform, and gets edited by someone who doesn't ship the build. For almost everything else it's a cost you pay every week to avoid a cost you'd pay twice a year. That's our opinion, and the rest of this post is the reasoning, so you can disagree with the right step.

If you want the definition first, we wrote it up in what server-driven UI is. Short version: the server sends a description of a screen, and a renderer inside the app draws it.

What you actually buy

The benefit is easy to state. A change to a screen stops waiting for a build, a store review and users updating. If that wait is what slows your team down, this removes it for the screens you hand over to the server.

There's a second benefit that gets less attention. When the screen is data, the person who wants the change doesn't have to be the person who writes Swift or Kotlin. A marketer fixing a headline or a PM trying a new order of blocks can do it without opening a pull request. Whether that's good depends on how much you trust your validation, which comes up below.

And a third, if you're on several platforms: one description, drawn by one renderer per platform, means the web, iOS and Android versions of a screen can't quietly drift apart in wording.

What you pay for it

Nobody sells you this part, so here it is in plain terms.

You now own a schema. Every component type you add is a promise to every app version still in the wild. A phone can be running a build from months ago, and it has to do something sensible with a component it has never heard of. Deciding what "sensible" means, and testing it, is most of the work. We cover the fresh-versus-stale side of this in caching remote screens.

Mistakes move from compile time to run time. In native code a typo in a property name fails the build. In a JSON screen it's a bad edit that goes live to real people unless something checks it first. You'll end up writing validation, a preview, and probably a way to roll back. None of that is hard, but it's real work, and I'd expect it to cost more than the first estimate says. That's a guess, not a measurement.

Accessibility lives in the renderer. Android's own guidance says users "must be able to understand the content and purpose of each interactive and meaningful UI element within your app"[2]. In a server-driven screen, that sentence turns into a job for the renderer: every button it draws needs a proper label, role and focus order, and text has to scale. If the renderer gets this right once, every screen benefits. If it gets it wrong, every screen is wrong at the same time, and the person editing the JSON can't fix it.

Debugging has two places to look. "Why does this look off?" might be the data, the renderer on that platform, or the app version. You need to be able to see which screen version a given device actually drew.

Where it breaks down

The Flutter team's package for remote widgets is a useful place to see an honest limit, because it states its own. Its README says the package is "best suited for interfaces made out of prebuilt components"[1]. As we read that page, it says it can't create new widgets, names things like page transitions, drag and drop and custom painters as poor targets, and warns against moving a whole application onto it[1]. That's one team's package, not a law of nature, but the shape of the limit shows up in any server-driven system: the server can only arrange what the app already knows how to draw.

So the cases where we'd say no are mostly cases where the interesting part of the screen is the part you can't describe as data:

  • Dense interaction. Drag and drop, custom gestures, animation tied to touch. By the time your format can express it, you've built a second programming language.
  • A screen that rarely changes. If it moves twice a year, a release is cheaper than a renderer.
  • One platform, fast releases. If you can ship daily, the wait you'd remove isn't there.
  • No owner for the schema. This one kills more projects than technique does, in our view. Someone has to say no to the fortieth component type.

A quick way to decide

Helps whenHurts whenSkip it if
SpeedThe wait for a release is the bottleneckReleases already ship in a day or twoYou deploy daily on one platform
Who editsNon-engineers need to change content or layoutOnly engineers ever touch the screenNobody will use the editing power
PlatformsWeb, iOS and Android should look the sameYou have one platformOne codebase already covers it
InteractionMostly text, images, buttons, listsGestures, animation, custom drawingThe screen is the interaction
UpkeepSomeone owns the schema and its testsNobody doesYou can't staff that owner

Swipe sideways to see every column.

If you only need to change a value or switch something on and off, you probably don't need a renderer at all. A flag or a config value is far less machinery. Feature flags vs remote config vs CMS walks through that choice, and ways to change a live app without shipping a release lists the rest.

Where we land

Start narrow. Pick the one or two screens where waiting for a release hurts most, keep everything else native, and see whether anyone actually uses the freedom. The screens that benefit tend to be the ones the business keeps wanting to change, such as promotions, onboarding and upgrade prompts. The screens that don't benefit are the core of the product.

We haven't run this comparison across a large sample of teams, so treat the table as a starting point and not a result. If your situation is an exception, that's fine. The point is to know which cost you're choosing.

Sources

  1. Android Developers, Accessibility principles (opens in a new tab)developer.android.com · accessed