Case Study

I took a legacy SaaS from a 4.5 to an 8.0 without a rewrite

A founder's older product still makes money and is scary to touch. Rewrite or keep patching? Neither. Here is how I modernized a 100K-user SaaS in place, from a 4.5 to an 8.0, while it never went dark.

I took a legacy SaaS from a 4.5 to an 8.0 without a rewrite

A founder with an older product asks me the same question almost every time. The codebase is five or ten years old, it still makes money, and it is scary to touch. Do we rewrite it, or do we keep patching it until it falls over?

My answer is neither. You modernize it in place, in small reversible steps, while the business that runs on it never stops. I have run a US referral-marketing SaaS as fractional CTO since its 2016 launch, and this is the approach I used to modernize it for the next decade of growth. The same approach works on almost any legacy product carrying a real business.

The short version

The product serves 100,000+ users and carries the kind of history any successful SaaS accumulates over a decade. When I set out to modernize it, I scored the architecture at 4.5 out of 10 against the things that actually break products as they grow. Over eighteen months I took it to 8.0. One engineer, multi-engineer pace. No rewrite, no big-bang migration, no weekend where the site went dark and everyone held their breath. Every change shipped behind the live product, and the users never felt the floor move.

That is the whole story. The rest of this post is how, and more importantly, what I deliberately chose not to do.

Why a rewrite is usually the wrong answer

The rewrite is the seductive option. A clean slate, a modern stack, no legacy baggage. It is also the option that quietly kills products.

A rewrite means you freeze the system that works, build a parallel one, and pray you can swap them before the market moves or the budget runs out. For any SaaS with a real user base and live activity every day, that freeze is the risk. You stop shipping value to real customers for months to rebuild something that already works. And the new version always discovers, the hard way, the thousand small decisions the old one had baked in for good reasons.

So I do not rewrite. I treat a mature codebase like a building that has to stay open while you renovate it. You do not demolish a working store to rebuild it. You renovate one section at a time, after hours, and open the doors every morning. This is the discipline behind the strangler fig pattern: you grow the new system around the old one and retire the old parts only once the new ones carry the load.

Score it before you touch it

You cannot improve what you have not measured. Before changing a line, I score the architecture across the things that actually break products as they scale: front-end coupling, build and deploy safety, data-layer risk, observability, and how easily one change can take down something unrelated.

A score gives you a number, but the real value is the ranked list that comes with it. You learn which changes buy the most safety for the least risk. That ranking is the whole game. Modernizing a mature codebase is not about doing everything. It is about doing the few things that move the most risk off the table first, in an order where each step makes the next one safer.

The front end: migrate, do not replace

In almost every long-lived product, the front end is the heaviest drag. Years of incremental jQuery, layered on layer, where one change can ripple into three screens you forgot existed. This product was no exception, and that is normal. It is what a decade of shipping looks like.

I did not rip it out. I introduced Preact alongside the existing code and migrated screen by screen, starting with the ones that change most often. As each screen moved to a clean component, I removed the older code it replaced, taking out roughly a third of the legacy jQuery over the project. Each migration was small enough to review, ship, and roll back on its own. At no point was there a “new front end” branch sitting half-finished and rotting. The same incremental discipline is what lets you scale a front end without losing team velocity: you move in pieces small enough that the product never stops shipping.

I moved the build to Vite at the same time, turning a slow step into a fast one. Faster builds are not a vanity metric. They are what makes shipping ten small safe changes a day actually possible instead of theoretically possible.

The data layer: the change I make most carefully

The riskiest part of any mature system is wherever business logic has drifted into places it does not belong. Move that logic into visible, testable, version-controlled application code and the whole system gets easier to reason about.

This is the change I always stage most carefully, because the data layer is where a mistake costs real customer data. So I want to be precise about what I do not do. I do not run destructive migrations. I do not restructure existing tables to make a refactor convenient. I do not put existing user data at risk to win a cleaner diagram. The behavior becomes visible and testable, and the data sits exactly where it was. Safe to change is the goal, not clever.

A cleaner application layer is also what makes a legacy product ready for what comes next. Once business logic lives in visible, testable code instead of hidden in the database, you can add modern capability on top of it, including AI. I have written about why agentic AI in PHP is a production architecture decision, not a prototype, and a well-modernized codebase is the foundation that makes it possible.

Observability: stop flying blind

For most of their life, older products have no real way to answer “why is this slow” or “where did this request actually spend its time.” You guess, or you add a log and wait for it to happen again.

So I stand up tracing early, so a slow request tells you where it was slow instead of leaving you to guess. This is the change that compounds. Once you can see the system, every future decision gets cheaper and safer, because you are deciding from data instead of from a hunch.

What I deliberately did not touch

This is the part founders with a fragile-feeling codebase most need to hear, so I will be blunt about it.

I did not rewrite the custom foundation the product was built on, even though a fashionable rebuild was always an option, because it worked and the rewrite was pure risk for no near-term gain. I did not migrate the database structure for the sake of tidiness. I did not touch the parts of the system that were stable and rarely changed, because stable code that nobody is editing is not where your risk lives. Risk lives in the code you change often, and that is where I spent the effort.

Knowing what to leave alone is most of the skill. A junior engineer, or an AI agent, will happily “improve” a stable module and introduce a bug into something that had not failed in years. The judgment is in not doing that. AI scales the execution of these changes beautifully once you have decided which ones to make, but it does not decide which to make. I have written separately about why a senior running a product on AI needs a peer, not a junior, and modernization is exactly the kind of work where that judgment shows. The agent can run the refactor across two hundred files. It cannot tell you the refactor was the wrong idea.

The result

Eighteen months, a 4.5 to an 8.0, one engineer working at multi-engineer pace. A front end a third lighter and built on modern components. Business logic moved into visible, testable code. A build fast enough to ship many small changes a day. Real observability where there had been none. And through all of it, 100,000+ users kept using a product that never went dark for a migration.

The product did not get replaced. It got safe to change again. That is the difference between a rewrite and a modernization, and for a business that depends on its software every day, it is the whole difference.

Frequently asked questions

Can you modernize a legacy PHP or Laravel app without a full rewrite? Yes, and for a product with real users it is almost always the right call. You score the architecture, rank the changes by risk removed per unit of risk taken, and ship small reversible steps behind the live product. A rewrite freezes value delivery for months and rediscovers every decision the old system made for a reason. In-place modernization keeps the business running the entire time.

How do you upgrade a production codebase without breaking it? You change the code you edit often, not the code that is stable. You make each step small enough to review and roll back on its own. You avoid destructive database migrations. And you add observability early so you are deciding from data, not guesswork. The goal is that no single change can take the product down.

Does this kind of modernization put existing user data at risk? No, not when it is done right. The point is to make behavior visible and testable without restructuring tables or migrating user data for convenience. The data stays where it is. Safe to change is the standard, not clever.

Why not just rewrite a 10-year-old app on a modern stack? Because a rewrite freezes your product while you rebuild something that already works, and for a SaaS with a real paying user base that freeze is the real risk. Modernizing in place delivers most of the benefit with a fraction of the danger, and the business never stops shipping value to customers.

The takeaway

If you have a product that makes money and scares you to touch, you do not have to choose between a risky rewrite and slow decay. There is a third path: measure it, rank the risk, and modernize in place one reversible step at a time, while it stays live the whole way.

That is the difference between a rewrite and a modernization. The product does not get replaced. It gets safe to change again.

Have a product you are scared to touch?

I work with founder-led SaaS teams across the US, Canada, the UK, and Australia to modernize older PHP and Laravel codebases in place, without a risky rewrite and without downtime. If you have a product that makes money but is hard to change, I can make it safe to change again. Reach out and let us look at it together.

To keep quality high, I take on a limited number of clients and prefer a qualified inquiry first.