Server-Driven UI and GraphQL
Server-Driven UI (SDUI) reduces client-side logic and provides consistency across client platforms (web/iOS/Android/etc) by returning UI data from an API instead of domain data. This approach is geared toward use cases where collaboration between front-end and back-end teams is possible and there is a strong desire to build a cohesive experience across multiple teams and organizations.
When building a graph with server-driven UI, it’s often desirable to converge the schema with the design language being used by client teams. In practice, this requires a definitive design system and a well-defined collection of components to use on the front end to render views.
The primary need for SDUI comes from mobile native-app development. These platforms require shipping versions of your client-side app to a store that usually has a lengthy review process before a new version gets published. Then, even after a new version of the app is released, users have to update their versions from the store to start using or experiencing the new features.
Instead, if your API returned exactly what was displayed on the screen you could control when different UI elements or text are displayed. Changes can also be made to the inputs sent to the API without requiring any app updates.
SDUI is not a framework to be implemented but a design pattern that can be implemented in many different ways while still following the same core principles. You could have just a few parts of the app controlled from the server, like text or if certain elements should be hidden. It could then advance to more parts like changing theming or component features from the API, all the way to having a generic layout that could render any UI component in any order.
Take for example this mock ecommerce schema which allows us to display a list of featured products:
type Product @key(fields: "id") {id: ID!name: 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 now render multiple specific experiences depending on the query result. We can use the returned __typename
value to determine if a collection of products should be rendered, or show the user an error message.
fragment productFields on Product {idnameprice}query FeaturedProductsCollection {featuredProducts {__typename... on ProductsList {products {...productFields}}... on ProductsCarousel {products {...productFields}}... on ProductsError {message}}}
Clients can then have a collection of types mapped to which UI component they should use for their specific platform and render it with the correct data.
const { data } = useQuery(FeaturedProductsCollection);// Maps GraphQL __typename to UI componentconst TYPE_COMPONENTS = {ProductList: ListComponent,ProductCarousel: CarouselComponent,ProductError: ErrorComponent};const component = TYPE_COMPONENTS[data.featuredProducts.__typename];if (!component) { throw new Error('Unknown Type Returned!'); }renderComponet(component, data);
This means that from the API response you can dynamically choose when to render different components or start displaying different components based on any of our API inputs, including headers or user information.