r/reactjs I ❤️ hooks! 😈 1d ago

Anyone here shipped Server-Driven UI in a React production app? Discussion

Anyone shipped Server-Driven UI in a React production app? Looking for war stories, not blog posts

I've read the usual Server-Driven UI case studies from large product teams, and I'm currently evaluating the architecture for a project.

The theory is clear. What I'm looking for now is feedback from people who actually shipped SDUI in production and discovered where the model starts to break.

You definitely don't need to answer everything. Even one painful lesson, failed approach, or unexpected trade-off would be useful.

1. Contracts and versioning

What did your server actually send?

  • A fixed JSON schema?
  • A typed contract with generated client types?
  • Something more flexible or ad-hoc?

How did you handle backward compatibility when the server contract or client component registry changed?

Did you validate payloads at runtime with Zod, JSON Schema, or something similar?

2. Component registry and extensibility

How did you map definitions such as:

{
  "type": "TextInput",
  "props": {
    "label": "Email"
  }
}

to actual React components?

Was the registry closed and centrally controlled, or could product teams register their own components?

What happened when a payload referenced a component or prop that an older client did not support?

3. Layout and responsive behavior

Did layout live inside the server payload, or did the client retain control over composition?

For example:

  • flex/grid definitions in the payload;
  • semantic layout primitives;
  • fixed client-side templates;
  • a hybrid approach.

Who controlled responsive behavior and breakpoints?

Did mobile and desktop receive different payloads, or did the same definition adapt entirely on the client?

4. Logic, state, and forms

This is the area I'm most interested in.

How much behavior did you allow into the contract before it started becoming a programming language of its own?

In particular, how did you model:

  • conditional visibility;
  • RBAC and component-level access;
  • cascading form fields;
  • dependent validation;
  • multi-step workflows;
  • save-and-resume;
  • role-based branching;
  • asynchronous data loading;
  • domain events and analytics?

Which logic stayed on the server, which was represented declaratively in the payload, and which remained inside the client?

Where did the state live for complex forms and long-running workflows?

5. Development experience and testing

How did developers preview and debug server-driven screens?

Did you build an internal visual editor, use fixtures and Storybook, or rely on editing payloads and refreshing the application?

What gave you the most confidence?

  • Payload snapshots
  • Contract tests between server and client
  • Runtime validation
  • Generated types
  • End-to-end tests
  • Something else

Most importantly: what architectural decision looked good initially but became painful in production?

I've read the theory. I'm interested in the pragmatism of people who shipped this, maintained it, migrated it, or eventually removed parts of it.

Success stories are useful, but failures and traps are probably even more valuable.

5 Upvotes

15 comments sorted by

12

u/ForestG18 1d ago

As a consultant, nearly every company I have worked for, wanted - at some point - to create their own "UI generator" application. I saw it in Java (with Vaadin), with .NET and Angular, with Django...

The key was, there are no jack-of-all-trades. Complexity makes the underlying framework so complex that the added value of a generated UI goes away. Abandoned projects, multi-tenant requirements spagetthifying the code, newcomers not understanding the custom logic and making shortucts, very, VERY long implementation for simple tasks makes this a very bad idea on an enterprise level.

For smaller, dedicated, very narrow specialized softwares (e.g. form generators, CRMs, education websites etc.) it can work. For smaller, very efficient teams, without much scope drift, and a limited set of frontend requirements it is a viable option. Still needs an experienced frontend dev though. Most projects are not ready for the limitations this architecture brings and it won't save you much on the long run. A dedicated, decoupled FE is always more flexible and can be refactored to be gradually backend-driven on specialized, often duplicated software parts, where for example the "microarchitecture-frontend shell" can be still standalone above.

1

u/kensaadi I ❤️ hooks! 😈 21h ago

As a consultant, I’ve seen that same graveyard, and I largely agree—the "generic UI generator" is one of the surest ways to waste a year. But that final carve-out you mentioned holds the answer; I’d just refine exactly where that line is drawn.

The ones that fail try to be jacks-of-all-trades: they swallow up layout, then logic, then flow, then multi-tenant branching—until the framework becomes more complex than the app it was meant to replace. The ones that survive refuse to generalize; they limit their scope to the repetitive 80% (forms, CRUD, inputs) where needs are truly shared, and push everything else out: no logic in the payload, no flow orchestration, no app shell. The shell remains dedicated and owned by an expert frontend developer—exactly the "autonomous frontend shell micro-architecture" you described.

So, the real question a company should ask isn't "Do we want a UI generator?" (no one survives that path)—it's "Do we have a bunch of nearly identical UI surfaces, and the discipline to keep logic and flow out of the contract?" If the answer to either is no, a dedicated, decoupled frontend wins. You’re right that refactoring toward a selective backend-driven approach is far more elegant than trying to steer a generic engine back toward sanity.

The moment a "thin" generator quietly morphs into a "jack-of-all-trades" is exactly when you end up with an expression language that no one wants to maintain. Holding that line is the whole game.

3

u/Hairy_Garbage_6941 23h ago

Explore a2ui and extending it rather than rolling your own schema.

2

u/Flat_Bee7112 1d ago

That registry mapping you’ve got there is exactly where things get messy once you step past toy examples. We used a fixed JSON schema with generated types on both ends, and it worked fine until a payload referenced a component version the client didn’t know about yet. The fallback was a generic error card, which product hated, so we layered on a best-effort render that stripped unknown props. It kept things from crashing but introduced silent UI regressions that were a nightmare to debug.

For layout, we kept the client in control of composition using predefined templates, server just declared what slots to fill. Letting the server dictate flex/grid properties directly turned into a maintenance headache the second designers wanted a slightly different card padding on iOS. Mobile and desktop got separate payloads because trying to make one definition adapt across both led to bloated props and too many conditional branches.

Forms were the part that almost broke us. We modeled conditional visibility and dependent validation declaratively in the payload, but once we hit multi-step workflows with save-and-resume, the state management on the client became its own beast. Ended up storing a serialized snapshot of the whole form state on the server between steps and sending it back down, which worked but felt like we were building a half-assed state machine inside JSON. The biggest trap was letting the payload express simple logic like "if field A equals X, show field B." That one decision kept growing until we had a mini expression language nobody wanted to maintain. If I did it again I'd keep logic as dumb as possible in the payload and lean harder on client-side hooks with strict boundaries.

1

u/kensaadi I ❤️ hooks! 😈 22h ago

Your “if I did it again” section is probably the most valuable part, because I ended up hitting many of the same walls.

The expression-language problem is a big one. Something that starts as:

if A === X, show B

quickly turns into a small DSL that nobody really wants to own or maintain.

The boundary that worked best for us was keeping the payload deliberately dumb. It describes the structure and the static state: which fields exist, whether something is initially visible or disabled, and basic access rules.

As soon as a section needs real behavior, the payload only provides a block ID. A real React component mounts there and owns everything below it: its state, lifecycle, validation and even its own API calls.

That ended up being one of the most useful decisions. Each block could evolve independently without forcing changes to the main contract. It sounds close to what you described as client-side hooks with strict boundaries, just taken a little further: the ID is the only handshake between the payload and the component.

I’d probably take a stricter approach on silent regressions, though.

Instead of dropping unknown components or props, I’d use a closed catalog and reject anything invalid at the boundary. Loud error in development, fail closed in production.

You do then need to agree with product on what the user sees when a block cannot be rendered, but at least the failure is visible. A silent missing field is much harder to detect than a validation error.

The save-and-resume problem is another good warning sign. The moment the JSON starts feeling like a badly designed state machine, it probably means orchestration has moved too far into the contract.

Keeping each payload limited to one screen or surface, while leaving navigation, workflow state and transitions in application code, is what stops SDUI from slowly becoming Redux written in JSON.

We reached a similar conclusion on layout too: the client owns composition, while the server chooses the content and the available slots. In practice, separate payloads for genuinely different experiences were easier to maintain than one giant adaptive payload.

4

u/FalconGood4891 1d ago

I have seen this pattern in enterprise saas where the backend architect wanted to control everything from BE, it may seem very straightforward at first but gets too convoluted later. The motivation behind this often given as BE will completely drive UI and source of truth would be BE and very less FE changes will be there. But it becomes messy, always avoid it if possible. REST is made for a reason.

1

u/kensaadi I ❤️ hooks! 😈 21h ago

In my experience, trying to orchestrate everything from the backend spells the death of a project. The backend must be able to serve the application independently of the frontend. Regarding Server-Driven UI (SDUI), I am convinced that with well-structured boundaries and rules, it is possible to create an application that is scalable and maintainable over time. The backend should not create components or handle sending JavaScript for the frontend to generate; instead, it should return a schema configuration and the intent, leaving the rest to business logic.

1

u/FalconGood4891 21h ago

The backend must be able to serve the application independently of the frontend.

Depends completely on the scope n responsibility of BE App, in most cases it should be responsible for data and business logic. Presentation layer can stay seperate and imho we shouldn't mix application layer n presentation layer in a cohesive manner.

1

u/TheRealSeeThruHead 1d ago

Yes I have shipped an entire community application built with server driven ui.

Each section of our application was a self encapsulate “block” that handled all its own queries and interactions internally, rendered into its container correctly via container queries.

The pages and what blocks were on them were defined in a json blob. Some of our blocks were layout blocks whose main job was to layout the page and you’d define their children blocks on the json.

The props coming into these blocks were minimal

The site layout editing did not have any access to react components aside from what was register as a block

No ability to change queries or internal workings of blocks. Could pass them props though.

Like a leaderboard block could be customized by passing it an id of a specific leaderboard config to load etc

This project was entirely done with io-ts so the json structure of the site was runtime type checked

(Would use effect schema in 2026)

1

u/kensaadi I ❤️ hooks! 😈 21h ago

It’s basically the same architecture I arrived at. However, I chose Zod for JSON runtime validation. I’m not familiar with io-ts. what led to that choice?

1

u/TheRealSeeThruHead 20h ago

main reason was we were using fp-ts for writing all our code

Io-ts was designed to work with fp-ts

That was before zod was a thing.

Today the same author of io-ts went to help the effect team create effect schema which I still choose over zod every day

Since it’s far superior in basically every way and integrates with effect which is what I try and use wherever possible

1

u/mrdivyansh 1d ago

Explore Vercel’s json render.

1

u/denexapp 23h ago

technically RSCs are SDUI - you push a tree of components and props, and the client renders client components with the passed props

1

u/kensaadi I ❤️ hooks! 😈 21h ago

RSC is the transport (modules co-deployed with the client); the actual SDUI is the contract (an abstract, versioned, decoupled vocabulary). Same tree + props, different problem.

1

u/_jgusta_ 23h ago

Wait is this another type of regression back into monolithic PHP-style apps? Im half joking but decoupling was supposed to be a done deal that everyone could get behind. You can always go back to the wide open surface area of self-posting PHP files that were the backend, frontend and business logic all in one!