Jacob’s Tech Tavern

Jacob’s Tech Tavern

Backend-driven SwiftUI

Spec ultra-flexible UI that updates in seconds, not hours

Jacob Bartlett's avatar
Jacob Bartlett
Oct 20, 2025

Backend-driven UI is older than most of our careers.

Perhaps older than most of us, full stop.

In the early web, the server told the browser exactly what to render, returning static HTML for a given URL. Layout and data flow sat server-side dictated by the PHP, Django, or Rails application. The client just rendered what it was told.

Gradually, in the 2000s, Ajax and jQuery introduced some client-side dynamism, but the front-end framework explosion of the 2010s placed nearly all the logic and rendering browser-side, with APIs and JSON replacing HTML.

Along came mobile apps in 2008.

These are one of the thickest clients imaginable, designed to run as a standalone computer program. Backend-driven UI can feel… alien. But this tool can be incredibly powerful when used judiciously.

Backend-driven UI, a.k.a. server-driven UI, is a little fuzzy to define. If your app connects to the internet, and displays some nicely-formatted JSON data, your UI is being driven by the backend.

(I’ll try to avoid being a pedantic jerk now). Your UI is backend-driven when the backend sends information about the structure of your screen as well as the content.

Backend-driven SwiftUI has 2 parts: a small, tightly-scoped DSL (domain-specific language), and a flexible parser that renders the content as views.

Confused? Good. It’ll become easy once you see the first example. The trick is balancing power, flexibility, and simplicity: nobody wants you to re-implement HTML from scratch.

Today we will:

  • Learn why you might want to introduce backend-driven UI

  • Define a minimal JSON-encoded DSL to render a static blog post.

  • Introduce layout composition and navigation to render a Netflix-style video feed (from the exact same base screen).

  • Build a fully-interactive form that posts multimedia data to an API.

  • Close with some sanity checks around when backend-driven UI is actually worthwhile.

I love a good “squad on point” App Store listing image

Subscribe to Jacob’s Tech Tavern for free to get ludicrously in-depth articles on iOS, Swift, tech, & indie projects in your inbox every week.

There’s a lot of code here, so I’ll ruthlessly redact to keep things readable. Grab the repo if you want to remix my work for yourself: https://github.com/jacobsapps/BackendUI/


Why would you want Backend-driven UI?

You certainly don’t want your entire app to be backend-driven (except in a couple of edge cases I’ll explain soon. If you find yourself doing this, you’re probably making life harder for yourself, when you could just be building a webapp. It’s the AI age, just ask clanker to write it for you.

But there are a few very compelling reasons you might want some of your app to be backend-driven:

  • Ship changes without app review. This is the big one. 2.5.2 forbids the download of executable code, but a JSON DSL is simply data interpreted by your local rendering engine, and so is entirely kosher. Updating the JSON files on the backend means your changes appear in seconds, instead of waiting hours (or days!) for app review.

  • Decouple changes from engineers. There’s a ton of promotions, paywalls, A/B tests, or content which your product and marketing folks want changed. And they’ll want to make it your problem. Teach them the DSL and tell them to leave you alone. Switch off the lights on the way out, buddy.

  • Rapidly iterate and test variants. This can turbocharge your A/B testing to more quickly try out variants and optimise your app. Yes, this reason is sort of a cop-out amalgamation of the above 2 points.

  • Multi-tenant white labelling. The logical conclusion of backend-driven UI is shipping a single app binary for many different brands, with feature sets driven entirely by remote config files.

  • Smaller binaries. If you have a ton of content, and you keep the DSL and rendering engine tightly scoped, you can ship complex working apps with a single screen of code. This only works for relatively simple apps, naturally.

It’s likely you already use some backend-driven functionality in your app today. Any kind of complex dynamic social media feed, personalised storefront, or multimedia chat will include an infinitely-scrolling menagerie of different types of view. You probably won’t be calling this backend-driven UI, but it is.

I’ve built backend-driven UI’s a couple of times. In my first startup, Carbn, the users insisted in testing that they would become a DAU if we included educational content*. We diligently introduced a flexible set of up-to-date personalised educational content.

*(they were lying)

The sections labelled “importance” and “guide” in this screen were fully backend-driven UI, with sections for each of our 10 Green Habits.

Previously, when I worked in consulting, we also built an internal app for industry news. This one was cool because admin users could update the app content by directly adding DSL components to screens. But I am pretty sure that, like 99.9% of internal apps, it was never used. We should have spoken to users. Or, I guess, maybe not?

Okay, primer done. Let’s start with the least surprising, most useful, thing first.


Building backend-driven UI

Phase #1: Static Content

This tab renders a static content, with a number of types of media: rich text, code blocks, images, and titles.

How to use SwiftData outside SwiftUI

We begin our small DSL with ContentItem, which stores an ID (for view diffing) and a ContentType. This is an enum that defines every kind of view we might want to render, for example text, code blocks, or images.

ContentType.swift

This is where Swift really shines. We add associated values to the enum to define the ‘contents’ of the view, plus customisation options.

For example, ContentType.text has the text itself, plus dynamic type style and a line limit for multiline text. This provides ergonomics that allow many combinations of types without exploding the API surface of the enum.

The actual screen renderer is intentionally quite mundane:

We pass a page with a title and an array of ContentItems. The renderer displays them as a feed, with the classic ScrollView, LazyVStack, and ForEach.

ContentRendererView.swift

The heart of the renderer is ContentItemView, that renders the view based on the ContentType. One big bloody switch statement. It is simple, fast, and explicit.

ContentItemView.swift

Looking at a couple of the views, text rendering supports dynamic type, line limits, and uses AttributedString to inline markdown, allowing links to be inlined easily. We might later want to include alignment or colour into our DSL.

TextContentView.swift

Code blocks are deliberately simple, plain, and scrollable.

CodeBlockContentView.swift

Let’s now check out the JSON that powers the blog.

Our custom Codable logic takes the type and parses out the relevant fields for each associated value using CodingKeys. It’s pretty human-readable, but very easy to muck up. You will want to validate changes in staging.

swift_data_blog.json

Okay.

DSL, ContentType, associated values, LazyVStack, switch statement. Various views.

We have achieved everything we need for 95% of most server-driven-UI use cases. But we can push a little bit further without getting too silly.

Subscribe for free to continue reading

Enter your email and get every post from Jacob’s Tech Tavern.
Already have an account? Sign in
© 2026 Jacob Bartlett · Privacy ∙ Terms ∙ Collection notice
Start your SubstackGet the app
Substack is the home for writing, podcasting, and media founders.