One of our teams rewrote their legacy cloud product in an interesting way. Instead of gradually replacing components of the existing product, they forked it, rewrote most stuff from scratch in a different branch using a different stack (different UI/UX also) and called it version 2.0. The old version was still maintained (bugfixes, tech support etc.) but they stopped selling it. Instead, they started selling version 2.0 (new clients were unaware of the difference). Version 2.0 did not have full feature parity with version 1.0 at the start because it would take years to reimplement everything. What they did was gradually migrate clients from 1.0 and 2.0. Most clients did not need all the features so they were OK with version 2.0. Those clients who refused, stayed on version 1.0 for a few years. Then the team was slowly adding more and more features to 2.0 for the next few years, and more and more clients were willing to migrate, and this year the last client migrated to version 2.0. It took 4 years overall. The new version was designed to be more stable, more scalable and more maintainable, so it's a net win in the end. Also, since it was basically a new product, they were able to experiment with new ideas without the constraints of the old product.
There's usually a subset of any given codebase that gets changed substantially on a regular basis, and the rest mostly stays mostly the same. If you can get the parts in the former category replaced with something better to work with, that often gives you most of the advantages and you can migrate any remaining bits as and when you can be bothered.