---
title: Server-driven UI
description: The background of server-driven UI, how it works in Backlit, and where it fits.
---

Server-driven UI (SDUI) is an architecture in which the server decides what a screen
contains and the client decides how to draw it. The server response is not raw data
that the client must interpret. It is a description of the screen: a tree of typed
nodes, with the data for each node inside it. The client has one component for each
node type and renders the tree.

## Background

The web browser is the oldest SDUI client. A server sends HTML, the browser renders
it, and the links and forms in the response tell the browser which requests it can
make next. The REST architecture calls this constraint hypermedia as the engine of
application state (HATEOAS). Single-page applications moved away from it. The server
became a JSON API, and the knowledge of each screen moved into client code.

Mobile teams brought the idea back, because a native app cannot change a screen
without a release through an app store. Airbnb built the Ghost Platform, where one
GraphQL schema of sections, screens, and actions drives the web, iOS, and Android
clients. Most of its high-traffic features, such as search, the listing page, and
checkout, run on it. Lyft moved its bikes and scooters product to SDUI to increase
release speed and to keep business logic out of three client codebases.

These systems return components that have a meaning in the product, and not HTML or
layout primitives. The client owns the visual result, so each platform keeps its
native look and the design stays consistent.

## How it works in Backlit

The Backlit SDK builds a tree of protocol nodes for each view. Each node has a
`kind`. The example below declares a table. The protocol output shows the node that
the server sends.

<BacklitExample path="table/basic" view="list" />

The web client has a registry that maps each node kind to a React component. It
walks the tree, finds the component for each kind, and renders it. A kind that the
registry does not know renders a fallback component and does not stop the page.

Interaction follows the same model. The server declares what a user can do, as
targets and actions inside the nodes: open a view, submit a form, run an action on a
row. The client does the request. The server answers with one of four responses,
which are success, redirect, refresh, or error, and the client applies it. The
client has no knowledge of your domain. It knows the protocol only.

The protocol contains only data that a client cannot infer. It has no pixel values,
no colors, and no layout coordinates. Thus a client for a different platform can
render the same response with its own components.

## Why SDUI fits internal apps

Internal apps are built from a known set of parts: tables, forms, filters, detail
pages, charts, and actions. The value of an internal app is in the data and the
operations, and not in a new interface.

An SDUI protocol works when a fixed catalogue of parts can express the screens of an
application. Airbnb and Lyft each designed a catalogue for one product. The screens
of internal apps are similar enough between companies that one catalogue can serve
all of them, and Backlit ships that catalogue, designed for the back office.

The catalogue is where you start, and it does not limit what you can build. The next
two sections cover the work that goes beyond it.

## Extend Backlit

Backlit is built to be extended from day one. An extension is a package with a
server module and a client module, and it can add these things:

- New field types, with their data conversion and their rendering.
- New blocks, widgets, and charts.
- Complete resources, with their views and actions.

The last item means that an extension can ship finished screens, and not only parts
for your screens. A queue monitor or an audit log can be an extension that you
install, and the pages appear in your app. You can write extensions for your own
apps, share them between the apps of your company, or publish them.

The [Extensions](/extensions) page shows an extension that adds an operations
dashboard for BullMQ queues, with the code that registers it.

## Write a custom page

Some screens are specific to one company and one use case, and no catalogue fits
them. For these, you write a custom page. You add a JSX file to the `pages`
directory of the frontend app, and the file becomes a route inside the Backlit
shell. The page is normal React, so the interface has no constraints.

A custom page still uses the platform. The components of the design system are
available, so the page looks the same as the other pages of the app. The React Query
integration is available, so the page gets the same data fetching and caching as a
Backlit view.

```tsx title="src/pages/revenue_forecast.tsx"
import { useQuery } from '@tanstack/react-query'
import { Card, CardContent, CardHeader, CardTitle } from '@backlit/shadcn-design-system'

import { ForecastCanvas } from '../components/forecast_canvas.tsx'

export default function RevenueForecast() {
  const { data } = useQuery({
    queryKey: ['revenue-forecast'],
    queryFn: () => fetch('/api/forecast').then((response) => response.json()),
  })

  return (
    <Card>
      <CardHeader>
        <CardTitle>Revenue forecast</CardTitle>
      </CardHeader>
      <CardContent>
        {/* Your own component. Backlit puts no limit on what it renders. */}
        {data && <ForecastCanvas series={data.series} />}
      </CardContent>
    </Card>
  )
}
```
