Introduction to Server-Driven UI

I love building apps, especially native apps, where I can integrate with the platform and create fantastic experiences for those who use them. As a consultant, my clients often have different goals. They want the apps to be cross-platform, look the same everywhere, be fast to develop, and often only exist to support other parts of the business.

Can these two worlds be combined? In my recent project, we decided to try. In a series of posts, I will explain our approach.

Introduction

In 2016, John Sundell talked at UMT 2016 about how Spotify used component-driven UI to build their app. I really enjoyed that talk, and since then, I’ve seen more and more companies taking a similar approach. Airbnb has their Ghost Platform, Zalando has AppCraft, Lyft has described their approach on their blog and in multiple podcasts, Yelp is another example, and there are many more.

The approach I’m talking about is Server-Driven UI. A way to move most of the business logic and view composition down to the server, but let the UI still be native for each platform.

Server-Driven UI opens up a whole new world of possibilities, and in these posts, I will try to convince you to explore these yourself.

Caveat

Before we go into the technical parts, I want to clarify that this technique is not for all apps. There’s no silver bullet, as we all know, and different apps have different needs.

  • Games: Are games apps? Maybe, but they are very different from other apps since they are usually built on a game engine and must be blazingly fast and are, therefore, outside the scope of these posts.
  • Utility apps: These are the apps we use daily to write, manage our tasks, work out, and create all kinds of stuff. These apps should be fast, well integrated into the system, and, most importantly, be available offline. Server-driven apps can be fast, but they are hard to use offline so utility apps is also out of scope.
  • Service-based apps: These can be your bank apps, booking apps, news apps, shopping apps, ride-sharing apps, etc. These apps are essentially sidekicks to the actual service, and they may very well be necessary for the service to succeed, but they wouldn’t exist without the service. Service-based apps are perfect candidates for Server-Driven UI.

Building service-based apps

So, let’s focus on service-based apps. These are the apps that clients want us to build, which is great because these apps often have lots of users.

However, these apps come with some requirements:

They must be cross-platform and reflect the brand on every platform. They should also be updateable at any time. Maybe the content needs to change for black Friday, maybe some documents should surface to the top for tax season, or perhaps the marketing department wants to change the images and messaging in the apps.

The apps also need to support the business. That’s not unique for service-based apps, but the bigger the service that the apps support gets, the number of involved teams increases, which slows the release cycle down until it almost stops entirely.

I discussed this with another mobile architect while deciding on a new architecture for our apps. He said:

Why don’t you just hire a bunch of React Native developers and be done with it?

That may be the correct answer for some apps, and it does satisfy the first requirement, but unfortunately, not the others. It also closes the door on many native features we want to include in the future, so we decided to try Server-Driven UI instead.

What is Server-Driven UI?

Ok, enough talking. What is this Server-Driven UI thing? I’ve mentioned it a few times but have yet to explain it. To do so, I will start with a peace of a banking app, the accounts screen, to be precise.

If we look at the code we normally use to build this in React, it could look something like this.

function AccountsScreen({
  totalDebt, totalSavings, creditCardAccounts, savingsAccounts
}: Props) {
  return (
    <List title="Accounts">

      <WidgetListItem>
        <Widget
          icon={Icon.ArrowDown}
          label="Total Debt"
          value={totalDebt}
        />
        <Widget
          icon={Icon.ArrowUp}
          label="Total Savings"
          value={totalSavings}
        />
      </WidgetListItem>

      {creditCardAccounts && (
        <ListSection title="Credit Card Accounts">
          {creditCardAccounts.map((account) => (
            <DataListItem
              icon={Icon.Card}
              label={account.name}
              value={account.debt}
              url={`/accounts/${account.accountNumber}`}
            />
          ))}
        </ListSection>
      )}

      {savingsAccounts && (
        <ListSection title="Savings Accounts">
          {savingsAccounts.map((account) => (
            <DataListItem
              icon={Icon.Money}
              label={account.name}
              value={account.balance}
              url={`/accounts/${account.accountNumber}`}
            />
          ))}
        </ListSection>
      )}
    </List>
  )
}

If you’ve written any code in React, this will probably look normal to you. If you look closer though, you can see that his example only uses our custom components, and we don’t use any HTML. Frankly, nothing tells us that this is a web application at all, and it’s not hard to imagine how this code could render these two UIs.

This is the idea behind Server-Driven UI: Let’s find a common language to define our view, then we can move everything required to create that defintion to the server and let the clients be much thinner.

Because, let’s face it, React components are never as simple as the one above. They have hooks, GraphQL calls, business logic, error handling, authorization checks, localization, A/B testing, logging, analytics, … and the list goes on and on. What makes it worse is that it’s the same for iOS and Android, so if we can write this once on the server we can focus on more interesting things.

Front-of-the-frontend

So, what’s left for the apps?

Frontend development is a huge field, and even if you remove many of the duplicated tasks mentioned above, there is still much to do. Brad Frost has an excellent article about why we should stop talking about frontend development as being one profession and instead think of ourselves as front-of-the-frontend and back-of-the-frontend developers. I like that definition.

In Server-Driven UI, we move most of the back-of-the-frontend responsibilities to the server. This lets the front-of-the-frontend developers focus on the frontend experience and build great native components. These components can look great, be fully accessible, and work like they should on each platform. Hopefully, it also frees up some time to integrate the app better with the system, which is where this post started.

Back-of-the-frontend

Why is this better for the back-of-the-frontend developers?

We will always need to have a small API part and layout engine in our apps, but if we can move most of the responsibilities above to the server, we can be faster to market, and we can update our apps at any time, and it will update for everyone, not only for the people who keep their apps updated.

Finally, it makes it easier to scale. Creating domain teams on the backend side responsible for an entire feature is now possible and the server can be modularized anyway we want. This adresses the the last requirement – support the business.

What’s next?

This was just the introduction part of my three-part series. In the following two parts, I’ll detail how we’ve implemented the server and our apps in detail. Please continue reading.