Rebuilding a product's interface while people still use it
PRIMUS, a recruitment platform, had its interface split across several separate apps. Bringing them into one took about 18 months while new features kept shipping, and the hard part was keeping the old and new versions working side by side.
In short
As the only frontend developer on PRIMUS, a recruitment platform at Navigos Group, I spent about 18 months merging its separate React apps into one Angular app while features kept shipping, and initial load fell from about seven seconds to under one. I explain how I kept old and new versions working side by side, and what I would change next time, such as a focused six-month rewrite.

Renovating a room is easier when everything can leave it for a while. A product that people are still using, and that still needs new features, never lets you clear the floor like that, and that was the position PRIMUS was in when I joined.
PRIMUS was a premium recruitment platform at Navigos Group, and I came in as its sole frontend developer. Its interface was spread across several React applications written in plain JavaScript, and the Redux state those applications shared had become tangled. For the people using the product, opening it took about seven seconds and moving between features took about three. The frontend needed to move into a better shape, but the product had to keep working and keep gaining features while that happened.
Picking a structure I could keep building in
Before moving anything, I needed to know what I was moving toward. The existing applications lacked TypeScript and clear module boundaries, and their shared state was difficult to follow. Those were choices made in the implementation, so changing the framework would not repair them by itself.
For the replacement I chose Angular, a framework for building web applications that gives an app a fixed structure. Its modules split the application into parts with clear edges, its services hold behavior that several parts share, and dependency injection hands each part the services it needs, which gave me the boundaries and the single home for shared behavior that the old applications and their tangled shared state were missing. As the only frontend developer, I also wanted fewer separate library decisions to make, and Angular brought routing, HTTP, forms and RxJS as part of the approach. It was a practical choice for the work in front of me: a consistent structure gave each new piece somewhere to belong while the older applications were still in use.

Moving one room at a time
A complete rewrite would have meant months without shipping, so I moved the product over in pieces while feature work carried on. Over roughly 18 months, that brought the separate React applications into one Angular app.

When a new feature or a big change came in
What moved next was mostly decided by the product work. New features went into Angular from the start, so every feature I shipped also grew the replacement application. When I reached behavior that several parts of the product shared, I moved it into services. An existing React screen moved over when it needed substantial changes anyway, and I replaced the Redux stores one at a time, starting with the ones causing the most trouble.
Making ordinary changes with two frameworks in place
Working this way made progress possible alongside product work, but it also meant living with two frameworks for much of the transition. Every boundary between old and new added something to keep in mind, even when the change itself was an ordinary feature. That ordinary feature work continued alongside the migration: across the project, the PRIMUS record includes 89 assigned Jira tickets marked Done or Resolved, covering both frontend features and fixes.
When product managers chose what to build next
While the move was under way, product managers were deciding the order of feature work from customer complaints and their own judgment. I built an event-driven tracking system so they could also see usage data.
The idea was that different parts of a page could add context to an event before it was sent. For a page view, a module could add its candidate pipeline stage, a search component could add the Boolean query, and another handler could supply the permission level. The tracking system could collect those contributions without knowing the internal details of every component. It gave product managers usage data for the first time, which was another way of improving the product while the migration continued around it.
Where the move ended, and what I would change
By the end, initial load had fallen to under a second, and moving between features took under half a second. Angular worked well for this project, but if I started it again I would consider SvelteKit or Next.js, because for solo work I now find overhead in Angular's decorators, modules and dependency injection configuration.
I would also push for a focused six-month rewrite with a freeze on new features in the legacy application. Continuing feature work is what made the gradual approach possible, and it is also what stretched the period of maintaining both systems to about 18 months, so I would want the next migration plan to reserve more of its time for finishing that transition.
PRIMUS was eventually discontinued for business strategy reasons, and the application was working well when that decision was made. I came out of it with a more qualified view of gradual migrations than I had at the beginning, and the experience still shapes how I think about them: the plan needs to account for the work of keeping the old and new parts usable together.
