Writing
Modernizing a Service Admin UI Without a Rewrite
September 18, 2025
A service admin panel had become hard to change. The fix was not a full rewrite, but an AI-assisted strangler migration from strange React patterns to predictable code.
A software rescue does not always mean rewriting the product.
Sometimes the users are fine. The UI works. The business depends on it every day. The problem is that the code underneath has become so strange that every change takes too long, every estimate feels padded, and the development team is afraid of touching the wrong thing.
That was the situation with a service admin management UI we helped modernize.
The app was an old Create React App build. It used React, but not in the way most React engineers expect to read it. Instead of traditional JSX and recognizable components with hooks, the code leaned on factory-style patterns that made it difficult to look at a file and understand the HTML being produced.
That matters. JSX is one of the reasons React works so well. It gives engineers a visual, readable bridge between component logic and interface structure. When that disappears behind custom factories, the app may still run, but the code stops explaining itself.
The state layer added another problem. The app also used Redux, which meant a non-React specialist had to understand both the unusual rendering pattern and a separate state-management model before safely changing the UI. Two complex concepts were working against the team at the same time.
The first step was not coding. It was archaeology.
We used LLMs and AI-assisted analysis to audit what was happening across the application. Where did these patterns come from? How did data move? Which factories produced which parts of the interface? Where was Redux essential, and where was it just making simple UI work harder?
You know you are on shaky ground when the LLM has not really seen the pattern before. But it could still help pick the system apart. That is one of the better uses of AI in software rescue: not blindly changing code, but helping a senior engineer map a strange codebase faster.
Once we understood the shape of the app, the decision was clear. A full rewrite did not make sense.
The customer service team was already using the panel. They were happy with the general UI. They just had changes they wanted, and those changes were taking weeks or months instead of hours. Breaking the whole product apart to prove an architectural point would have punished the people depending on it.
So we used a strangler pattern.
Section by section, we rewrote the interface into standard React with JSX. We replaced the confusing factories with readable components. We introduced hooks and query patterns where they made the data flow easier to reason about. As we touched each section, we incorporated the actual requests from the customer service team and shipped those improvements along the way.
The goal was boring on purpose: no visual or operational disruption for users, but a much better codebase underneath.
That is a good rescue principle. If the users are already relying on the product, success is not always a big reveal. Sometimes success is that nothing feels broken while the engineering team slowly regains control.
We also modernized the API assumptions. The backend needed to behave more like a traditional REST API, with predictable resource boundaries and request behavior. That gave both humans and LLM-assisted development a clearer contract to work against.
We added JSDoc types instead of forcing a full TypeScript migration all at once. TypeScript may have been the cleaner long-term destination, but it also would have expanded the surface area of the change. JSDoc gave us more type safety and editor support while keeping the migration focused.
The final infrastructure step was getting off Create React App and onto Vite. Create React App is no longer the right foundation for modern React work, and staying on it would only make future maintenance harder.
The result was not a rewrite. It was modernization without operational drama.
That distinction matters. Full rewrites have their place, but this was not one of them. The app had users. The workflows were known. The UI did not need to be reinvented. The real problem was that the code had become hostile to change.
AI helped, but not by magically rebuilding the product. It helped with audit, comprehension, pattern extraction, and mechanical migration. The engineering judgment was deciding how to use that leverage without creating unnecessary risk.
The measure of success was simple: how fast can the team ship the next requested change?
Before, changes could take weeks or months because nobody wanted to touch the code. After the modernization, the same kind of work could move in hours.
That is what a good rescue should do. It should not only make the code prettier. It should give the business its change velocity back.