Building Server-Driven UIs

Building Server-Driven UIs

14 min read

Server-Driven UIs known as SDUI is an architectural pattern that aims to reduce client-side complexity by building user-interfaces from the server over APIs. This is transforming the way apps are developed, fostering dynamic and flexible UIs.

This is not just theory. Some of the biggest names in the tech industry adopted Server-Driven UIs, such as: Airbnb, Instagram and Shopifyβ€”just to name a few. Spoiler: we also use this at N26.

In this post we will explore what are Server-Driven UIs, their benefits and how to implement them πŸ™

Table of contents

  1. Understanding Server-Driven UIs
    1. Traditional
    2. Server-Driven UI
  2. Building Server-Driven UIs
    1. Define the component tree
    2. Implement the components
    3. Build the rendering engine
  3. Benefits of Server-Driven UIs
  4. Challenges of Server-Driven UIs
  5. Conclusion

Understanding Server-Driven UIs

The best way to understand Server-Driven UIs is by comparing it with the traditional approach.

In the traditional world, the data comes from the server πŸ’», which acts as the source of truth and the UI is driven by each of those clients (Web, iOS, Android). Each client is responsible for fetching the data and transforming it into a user-interface.

With SDUI, the definition of the user-interface shifts from the client to the server, unifying this logic for all of them. The clients will fetch the UI from the server and render it.

Let's illustrate this with an example, using the Apple Podcasts webapp:

Apple Podcasts – Top Technology Podcasts

Now, let's represent the user-interface of the Top Technology Podcasts page using a JSON to simulate the server response:

Traditional

A contract is defined between the client and the server, adhering to a specific structure and shape.

The client will fetch the data from the server and will transform it into a user-interface.

{
  "title": "Top Technology Podcasts",
  "shows": {
    "title": "Top shows",
    "items": [
      {
        "description": "Ben Gilbert and David Rosenthal",
        "image": "https://placehold.it/200x200",
        "rank": 1,
        "title": "Acquired",
        "url": "/podcast/acquired/id105"
      }
    ],
    "episodes": {
      "title": "Top episodes",
      "items": [
        {
          "duration": "1h 36m",
          "image": "https://placehold.it/200x200",
          "publishedAt": "09-28-2024",
          "rank": 1,
          "title": "OpenAI's $150B conversion, Meta's AR [...]",
          "url": "/episode/acquired/id105/episode/1"
        }
      ]
    }
  }
}

Server-Driven UI

With SDUI the server will send a tree-like structure of components to the client. Each component represents a part of the UI and contains information about what to render and the properties the component needs.

The client will traverse this tree 🌳, rendering each component as specified by the server.

[
  {
    "type": "Title",
    "props": { "text": "Top Technology Podcasts" }
  },
  {
    "type": "SectionTitle",
    "props": { "text": "Top shows" }
  },
  {
    "type": "PodcastList",
    "children": [
      {
        "type": "Podcast",
        "props": {
          "description": "Ben Gilbert and David Rosenthal",
          "image": "https://placehold.it/200x200",
          "rank": 1,
          "title": "Acquired",
          "url": "/podcast/acquired/id105"
        }
      }
    ]
  },
  {
    "type": "SectionTitle",
    "props": { "text": "Top Episodes" }
  },
  {
    "type": "EpisodeList",
    "children": [
      {
        "type": "Episode",
        "props": {
          "duration": "1h 36m",
          "image": "https://placehold.it/200x200",
          "publishedAt": "09-28-2024",
          "rank": 1,
          "title": "OpenAI's $150B conversion [...]",
          "url": "/episode/acquired/id105/episode/1"
        }
      }
    ]
  }
]

Building Server-Driven UIs

Now that we understand the concept behind Server-Driven UIs, let's explore how to build them in a practical scenario.

We will break down the process into the following steps:

  1. Define the component tree 🌳: Define the components and the tree that represents the user-interface.
  2. Implement the components 🧩: Create a component for each definition.
  3. Build the rendering engine βš™οΈ: Traverse the JSON tree and render each component as specified.

Define the component tree

Based on the JSON response we defined previously, here's how we're going to break down the user-interface into components:

  • Title: The main title of the page.
  • SectionTitle: The title of a section.
  • PodcastList: A list of podcasts.
  • Podcast: The podcast item.
  • EpisodeList: A list of episodes.
  • Episode: The episode item.

SDUI Component Tree

Implement the components

After defining the tree, it's time to build the components. I will be doing it with React and Tailwind but you can use any other library.

Toggle components code πŸ‘ˆ
app/components/Title/index.tsx
const Title = (props: { text: string }) => (
  <h1 className="text-3xl font-extrabold">{props.text}</h1>
)
app/components/SubTitle/index.tsx
const SubTitle = (props: { text: string }) => (
  <div className="flex items-center gap-x-1">
    <h2 className="text-lg font-bold">{props.text}</h2>
    <ChevronRight className="opacity-60" size={20} />
  </div>
)
app/components/PodcastList/index.tsx
const PodcastList = (props: Props) => (
  <ul className="grid grid-flow-col grid-cols-4 gap-x-4">
    {props.children}
  </ul>
)
app/components/Podcast/index.tsx
const Podcast = (props: Props) => (
  <div className="text-sm">
    <Image
      alt={props.title + ' ' + props.description}
      width={200}
      height={200}
      className="rounded-lg mb-2"
      src={props.image}
    />
    <p className="font-bold">{props.rank}</p>
    <p>{props.title}</p>
    <p className="opacity-60">{props.description}</p>
  </div>
)
app/components/EpisodeList/index.tsx
const EpisodeList = (props: Props) => (
  <ul className="grid gap-2">{props.children}</ul>
)
app/components/Episode/index.tsx
const Episode = (props: Props) => (
  <div className="flex flex-row gap-x-4 items-start text-sm">
    <Image
      alt={props.title}
      width={90}
      height={90}
      className="rounded-lg"
      src={props.image}
    />
    <p className="font-bold">{props.rank}</p>
    <div className="flex-1 pb-4 border-b-[1px]">
      <p className="text-xs font-semibold opacity-60">{props.publishedAt}</p>
      <p className="font-semibold py-1">{props.title}</p>
      <p className="text-xs opacity-60">{props.duration}</p>
    </div>
  </div>
)

Build the rendering engine

This is where the magic happens πŸͺ„. We are going to implement our rendering engine by traversing the component tree and matching each node with the corresponding component.

To do that, first we define a map of components that we will use to match the type of each node with every component.

app/page.tsx
import Title from './components/Title'
import SectionTitle from './components/SectionTitle'
import PodcastList from './components/PodcastList'
import Podcast from './components/Podcast'
import Episode from './components/Episode'
import EpisodeList from './components/EpisodeList'
 
const SDUI_COMPONENTS = {
  EpisodeList,
  PodcastList,
  Podcast,
  Episode,
  SectionTitle,
  Title,
} as const

Then, we create a component that using recursion will traverse the tree and render it. In case you're not familiar with recursion here's a video from Sam Selikoff that explains it very well.

app/page.tsx
const SduiRenderer = ({ component }) => {
  const Component = SDUI_COMPONENTS[component.type]
 
  if (!Component) return null
 
  return (
    <Component {...component.props}>
      {component.children?.map((component, idx) => (
        <SduiRenderer key={`c-${idx}`} component={component} />
      ))}
    </Component>
  )
}
 
const Page = () => {
  return data.map((component, idx) => (
    <SduiRenderer key={`b-${idx}`} component={component} />
  ))
}

Here's how all the pieces come together πŸ•ΉοΈ, feel free to play around with the response to see how the user-interface reacts 😜

Benefits of Server-Driven UIs

Immediate changes and faster iterations

Server-Driven UIs allow you to push immediate bug fixes ⚑️ and updates without having to release an update to the clients.

This is especially useful in environments like mobile apps, where updates typically require going through a review process and waiting for users to install the new update.

Reduce client-side complexity

By shifting the UI logic 🧠 to the server, Server-Driven UIs make the client ligther and simpler 🍰. The client only needs to focus on rendering the components provided by the server.

Dynamic user-interfaces

Defining the UI on the server enables highly adaptable and dynamic interfaces that can be tailored on-the-fly 🎯, such as:

  • Personalization: Customize layouts and content for different user segments, devices, or use cases.

  • A/B Testing: Experiment with different designs and features by serving different variants to user groups.

  • Release flags: Gradually roll out new features by enabling them for a subset of users.

Challenges of Server-Driven UIs

  1. State management 🀹: Synchronizing state between the client and the server can be challenging, specially when dealing with complex interactions as the server needs to keep up with the client's state and update the UI accordingly.
  2. Offline support πŸ›œ: Server-Driven UIs rely on the server, managing the user experience when offline can be challenging. Caching and pre-fetching stategies can help mitigate this.
  3. Performance considerations ⚑️: Efficient network requests, payload optimization, low latency along with a good client-side rendering performance are crucial to ensure a smooth user experience.

Conclusion

Server-Driven UIs represent a paradigm shift in how we approach user interface development, offering a powerful tool for building dynamic and flexible apps ❀️.

However it's key to asses whether SDUI aligns with your project's needs and constraints. It's not a one-size-fits-all, but for the right use casesβ€”such as content-heavy user-interfaces and apps requiring frequent updates it can significantly enhance delivery speed.


Enjoyed the article? 😍

Subscribe πŸ‘¨β€πŸ’»