Server-driven UI basics
Use SDUI to reduce client-side logic and provide consistency across client platforms
Server-driven UI (SDUI) is an architectural pattern that aims to reduce client-side logic and provide consistency across client platforms (web, iOS, Android, etc.) by returning product information from an API instead of domain data. This approach is geared toward use cases where collaboration between frontend and backend teams is possible, and there is a strong desire to build a cohesive experience across multiple teams and organizations.
💡 TIP
For advanced patterns and schema types, see
Architectural goals
The primary need for SDUI comes from mobile native-app development. These platforms require shipping versions of your client-side app to an app store that usually has a lengthy review process before a new version gets published. Then, even after a new app version is released, users must update their versions to start using or experiencing the new features.
If your API returns exactly what to display on the screen, you can control the different UI elements or text without app version updates. You can also change the inputs sent to the API without requiring any app updates.
When building a graph with server-driven UI, converging the schema with the design language used by client teams is often desirable. This requires a definitive design system and a well-defined collection of components to render views on the frontend.
Guiding principles
SDUI is not a framework or library you can install. It's a design pattern you can implement in different ways while still adhering to the same core principles.
Start with UI designs
Start with your UI designs instead of looking at data models or API responses. Take a
If you find yourself describing steps to go from API to views, that's a warning sign that you'll have to duplicate that logic across clients.
Reduce logic in existing client code
Identify and reduce any logic in your existing client code. Some may think of "logic" as only business rules in the backend. However, if you've ever had to update conditionals or switch statements for a new feature, or to update how a feature works, you can consider these frontend portions as "logic," too. Any other places that accept your API response and then transform, concatenate, or use helper functions are additional warning signs for logic.
If you had to change this type of logic in one client, you probably had to change it in others. You can reduce this redundancy by moving the logic to your API.
Return product info, not domain data
Product information includes details about UI components' layout, appearance, and content. Domain data is raw or low-level data representing an application's core information and business logic. Ideally, your server provides specific information related to the presentation and behavior of your UI elements on the client rather than sending domain data.
Instead of enums, booleans, or numbers, you most likely only need your API to return strings. By returning only strings, the server can handle all concerns on the backend and return formatted, localized, and ready-to-display text to the client.
Using nullable strings (String
vs String!
) is also a best practice. With server-controlled nullability, the client code can directly use the API response—if a field is present, it renders that component; if missing, it does nothing.
Use a design system
Once all your clients use your API in the same way, you need a shared language that client apps can use to render their views. This is what
Define your capabilities with GraphQL types
Once you have a design system, the next step is to implement it in code. Your server's GraphQL schema is where you can codify the capabilities of your API. Using GraphQL queries, clients can still opt-in to select only the features or types they support.
Adopt SDUI incrementally
Following all the guiding principles, teams may be tempted to overcomplicate their apps by making everything server-driven all at once. By taking small, manageable steps toward SDUI, your team can balance innovation and stability while enhancing the overall user experience.
For example, you can begin by implementing SDUI for a single UI component or feature within your application. This could be a simple widget or a specific section of your app. This allows you to test the waters and learn how SDUI works in a controlled environment.
You can also use SDUI for new features or sections of your application while keeping the existing frontend intact. That way, you can use SDUI for new functionalities without affecting the legacy parts of your application.
SDUI will speed up feature release time in the future, but the initial adoption requires some overhead.
Schema design
Take, for example, this mock e-commerce schema, which allows clients to display a list of featured products:
type Product @key(fields: "id") {id: ID!name: StringformattedPrice: String# other product fields...}type ProductsCarousel {products: [Product]count: Int}type ProductsList {products: [Product]count: Int}type ProductsError {message: String!}union FeaturedProductsResponse = ProductsList | ProductsCarousel | ProductsErrortype Query {featuredProducts: FeaturedProductsResponse}
While querying Query.featuredProducts
, the client can render multiple specific experiences depending on the query result. You can use the returned __typename
value to determine whether to render a collection of products or show the user an error message.
fragment productFields on Product {idnameformattedPrice}query FeaturedProductsCollection {featuredProducts {__typename... on ProductsList {products {...productFields}}... on ProductsCarousel {products {...productFields}}... on ProductsError {message}}}
Check out