A full rewrite looks clean in a slide. It is rarely the cheaper path if 40 people already know the screens.
Is the pain in the interface, or in the data path behind it? If dispatchers, nurses, or warehouse staff can do their job on the current screens and the complaint is “it’s slow” or “deploys scare us,” we almost never start with a new UI. Training cost and muscle memory are real project risks, not soft factors.
On the logistics engagement, leadership wanted a new dashboard. We scoped a backend swap instead: same URLs, new queries, feature flags, two weeks of shadow traffic. Twelve weeks later the morning board was usable. Nobody sat in a training room.
Even then we still extract a read model or an API first so the rewrite can ship in slices. Big-bang cutovers on operational systems fail in the first peak hour, not in the demo.
We put a routing layer in front of the existing app (reverse proxy, API gateway, or a thin adapter in the same process). New endpoints implement one user journey. Traffic is mirrored: old and new both run, we compare status codes and payload hashes, then flip a flag. Rollback is the flag, not a database restore.
The hard part is parity, not code generation. Edge cases in the old app — weekend dispatch, vehicles with two drivers, “status unknown” — live in tribal knowledge. We sit with operators for a morning and write those cases as tests before we claim the new path is done.
We will not run a 9-month rewrite while the old system stays frozen. That is how you get two systems, neither correct. If the budget only covers a rewrite with no dual-run period, we say so up front and suggest a smaller slice: one screen, one report, one integration.
More on the pattern we used in production: fleet load time and legacy migration. If you are choosing between the two, send the constraints — peak hour, team size, whether the UI can stay.