Rebuild or refactor? Deciding what to do with an ageing app
Short answer: Refactor when the app still does the right job and its problems are concentrated in a few areas; rebuild only when the platform itself is dead or the business has changed so much that the app models the wrong thing. For most ageing business apps the safest answer is neither "patch forever" nor "start again from scratch," but a gradual replacement, piece by piece, while the old system keeps running. Whichever path you choose, start with tests around today's behaviour and a clean copy of your data.
Why "just rewrite it" is so tempting, and so risky
After years of patches, every change feels slow and every release breaks something. Starting fresh sounds clean: we know what the old app does, so build the same thing in modern technology.
Martin Fowler, who popularized the strangler fig metaphor for legacy modernization, describes how often that plan fails: replacing a serious system takes a long time, users can't wait for new features in the meantime, existing behaviour is harder to specify than it looks, and much of that behaviour isn't actually wanted, so rebuilding it is waste. A big-bang rewrite also gives you nothing usable until the very end, which is when the risk is highest.
Signals worth reading
Look at symptoms and their cause before picking a path.
Signals that point to refactoring:
- The app still matches how the business works.
- Problems cluster in a few modules (reports, invoicing, one integration).
- The framework and language are still supported, or one or two upgrades away from it.
- Developers can understand the code, even if they dislike it.
- Data is in reasonable shape.
Signals that point to rebuilding (gradually where possible):
- The framework, language or database is out of support with no upgrade path short of a rewrite.
- The business model has changed and the app's core concepts no longer fit (for example, it assumes one location and you now have six).
- Nobody can safely change the code and there are no tests.
- Security fixes can't be applied.
- The app can't offer something you now need, like mobile access or a customer portal, without fighting its design.
On support, read your framework's own policy. Laravel's, for instance, states that major versions are released every year and each receives bug fixes for 18 months and security fixes for two years (as of November 2026). An app stuck several major versions behind is running without security fixes, which changes the urgency, though often an upgrade (a form of refactoring) is still cheaper than a rebuild.
What each path costs in time and risk
| Refactor in place | Gradual replacement (strangler fig) | Big-bang rebuild | |
|---|---|---|---|
| Time to first visible improvement | Short | Short to medium | Long; nothing until launch |
| Business disruption | Low | Low to medium, spread out | High at cut-over |
| Risk of missing hidden behaviour | Low | Medium, caught piece by piece | High |
| Fixes structural problems | Partly | Yes, over time | Yes, in theory |
| Needs tests first | Yes | Yes | Yes, for comparison |
| Can be paused or stopped | Yes | Yes, value delivered so far stays | Hard; half a rewrite has little value |
The strangler fig approach, explained simply
The name comes from strangler fig vines, which grow around a host tree until they can stand on their own. In software terms:
- Put a "front door" in front of the old app so that requests can be routed to old or new code.
- Build one piece of new functionality, or one replacement module, as a separate, modern component.
- Route that piece to the new code. Everything else still goes to the old app.
- Repeat, piece by piece, until the old app does little or nothing and can be switched off.
Fowler's article highlights the hard part: finding the "seams," the places where the system can be split. Well-designed systems have them already; legacy ones rarely do, so part of the work is creating them. Each small replacement is lower risk and starts paying back earlier, and you learn as you go.
Before either path: tests and data
- Capture today's behaviour. Write automated tests around the most important outcomes (a quote total, an invoice, a permission check) using real examples. These protect a refactor and serve as the specification for a rebuild.
- Inventory integrations. Every import, export, email, webhook and scheduled job.
- Clean and document the data. Duplicates, inconsistent statuses, fields used for three different things. Bad data breaks new systems just as easily as old ones.
- Agree outcomes. Fowler stresses being clear on desired outcomes early and revisiting them, because muddled objectives derail modernization.
Decision questions
Answer these with your team and developer:
- Does the app still model how our business works?
- Is the platform supported, or one realistic upgrade away from supported?
- Where exactly is the pain: everywhere, or in three modules?
- Can we write tests around today's key behaviour?
- Can we split off one area and replace it without touching the rest?
- What new capability do we need in the next two years that the current design blocks?
- How long can we tolerate a period with no new features?
Mostly yes to 1–4: refactor and upgrade. Yes to 5 with no to 1 or 2: gradual replacement. A big-bang rebuild is justified mainly when the app is small enough to replace quickly or the old platform truly can't host any interim step.
A strangler-style phased plan
- Phase 1: tests on key behaviour, data clean-up, monitoring, routing layer in place.
- Phase 2: replace the most painful or highest-value module first.
- Phase 3: move further modules in order of value and risk, retiring old code as you go.
- Phase 4: migrate remaining data, switch off the old app, archive it read-only.
FAQ
Is refactoring just tidying up code?
It's restructuring code without changing what it does, often including framework upgrades. Done steadily, it keeps an app healthy for years.
Can the old and new systems share a database?
Sometimes, temporarily. It's a common interim step, but plan when each piece takes ownership of its data.
How do we know the new parts behave correctly?
Run the same tests against both, and compare outputs on real data before switching each piece.
What if our original developer is gone?
That raises the value of tests and documentation first, whichever path you take.
If your business app has reached the point where every change feels risky, describe what it does and where it hurts.