Writing
Architecture

You could build server-driven UI yourself. Here’s the bill.

The first version of a home-grown server-driven UI is a weekend. Then the Configuration Complexity Clock starts: your JSON grows conditions until it’s a worse programming language. What you actually signed up for — and the rule that avoids it.

The real alternative to a server-driven-UI tool usually isn’t a competing product. It’s a sentence in a planning meeting: “this is simple, we’ll just build it ourselves.” And the first version proves the point — a JSON schema, a small renderer, a screen coming down from the server. A weekend, maybe two. The bill arrives later.

The weekend demo

A home-grown server-driven UI starts honestly enough: you define a shape for a screen, write a component that walks it, and render a few atoms. It works. Someone demos it. The team is sold. What no one prices in that meeting is that you didn’t build a feature — you started a format and a runtime, and both are now yours to own.

Then the clock starts

Mike Hadlow named the trap in 2012: the Configuration Complexity Clock. Configuration that begins as simple data slowly grows a condition, then a loop, then an expression, until you’ve built a programming language inside your JSON — one with no types, no debugger, and no tests. The screen needs to hide a field for some users; the screen needs a computed total; the screen needs a rule. Each is a small, reasonable step, and the sum is a language you now maintain.

the config that became a language
// it never starts like this. it ends like this.
{ "if": "user.tier == 'pro' && cart.total > 100",
  "then": "show(discountBanner)",
  "else": "hide(discountBanner)" }  // who tests this?

The demo took a weekend. The language takes the rest of the roadmap — and the day it needs to evaluate one of those expressions in the browser, it also collides with every strict Content-Security-Policy on a page that matters.

What you actually signed up for

Past the demo, the surface is a platform, not a feature. A schema you design, version, and migrate forever. A safe way to express logic without shipping an interpreter. Forms, validation, RBAC gating, i18n, responsive layout, per-atom prop schemas — and a second renderer the day you support more than one design system. Each is yours to build, test, and keep working across framework upgrades. This is why the companies that do server-driven UI at scale staff whole teams to run it.

The exit: don’t let the JSON become a language

The way off the clock is a rule you enforce, not a feature you add: the contract describes structure, not behavior. A closed catalog of atoms — enough to compose real screens, small enough to stay consistent — and logic that lives in typed, testable code on your side, reached through an escape hatch when the catalog runs out. The wall always has a door, so you never build into a corner, and the JSON never grows conditions to evaluate.

The honest part

Building it yourself can be the right call — full control, no dependency, a format shaped exactly to your domain. The cost isn’t the first version; it’s owning the schema, the safe runtime, the form and RBAC surface, and the second renderer, for as long as the product lives. That’s the decision to make on purpose, not to back into because the demo looked easy.

Keep the control of building it yourself. Skip the interpreter, the language-in-JSON, and the team it takes to maintain them. Blueprint vs building your own