Refresh or Rebuild? Signs Your App Needs a Refresh
Executive TL;DR
- A rewrite is justified by a wrong domain model, a dead platform, or a changed business — not by ugly code, an unfashionable framework, or a team that would rather start fresh.
- Run the diagnostic before the decision: where do commits cluster, how long from merge to production, what share of deploys need a fix, and which dependencies are outside their support window. If the pain concentrates in 10–20% of the codebase, refresh it.
- Default to a strangler migration over a big-bang rebuild. Route traffic at the edge, replace one bounded slice at a time, and keep shipping features while you do it — a rewrite that ships nothing for nine months is the highest-risk option on the table.
The discussion usually begins in the same way. Shipping is slow, an upgrade broke staging, and someone senior is saying the quiet part loud: we should just rebuild it. It feels like the right thing to do. A rewrite offers a codebase without the compromises, a stack the team wants to work in, and a story leadership can understand.
The actual promise of the rewrite is that you will spend six to eighteen months relearning requirements that are currently encoded (poorly, but correctly) in the code you are deleting. Every weird conditional in a 5-year-old payment flow is a bug report someone filed, a tax rule in a state, or a contract of a big customer. “The old app remembers things that no one on the team can recall.
Meanwhile, the symptoms that spark the discussion are often four specific, fixable problems: dependencies that have drifted outside their support windows, a feedback loop so slow that nobody refactors anything, one hot spot in the data layer that makes every page feel broken, and a frontend written against a framework generation that has since moved on. Each of these has a finite fix. None of this requires a new repository.
So the right question is, “How is the code good?” It is whether the cost of change is concentrated or dispersed. Pain is a refreshment when concentrated. The real indication that the structure is wrong, not the code, is evenly distributed pain. Every feature touches everything, and no module can be reasoned about alone.
Refresh, incremental replacement, or full rebuild — which does this codebase actually need?
| Targeted refresh | Strangler-fig replacement | Full rebuild | Maintain and defer | |
|---|---|---|---|---|
| Cost | Lowest — weeks of focused work on a known surface | Moderate, spread over quarters and absorbed into normal delivery | Highest, and the estimate is almost always the optimistic one | Cheap now, compounding later as workarounds accumulate |
| Time to first value | Days to weeks — upgrades, caching and pipeline fixes land immediately | Weeks — the first replaced slice goes live early | Months, sometimes years, with nothing shippable in between | None; you are buying time, not improvement |
| Risk to live revenue | Low, with tests and staged rollout | Low per slice — traffic shifts gradually and rolls back at the edge | High, concentrated at one cutover date on a system nobody has operated before | Low today, rising with each unpatched dependency |
| Lock-in created | Minimal — you keep your options | Low; each slice can use a different stack if justified | High — you are married to today's stack choice for the next five years | None new, but existing lock-in hardens |
| Ops burden after | Unchanged, often better once CI and observability are fixed | Temporarily doubled — two systems, one router, until the last slice moves | Eventually simpler, if the rebuild actually finishes | Grows; more manual steps, more tribal knowledge |
| Team and hiring fit | Good if the stack is still mainstream; poor if nobody will work on it | Good — the team learns the new stack on real, small scope | Popular with the team, which is exactly why it needs cold scrutiny | Worst — attrition risk rises when engineers see no path out |
| Fails when | The domain model itself is wrong, or the platform is genuinely end-of-life | Boundaries cannot be drawn; everything reads and writes the same tables | Requirements are undocumented and the business cannot pause feature work | A compliance deadline, a security advisory or an acquisition forces your hand |
When to pick each option
If the diagnostics are showing a small area, go for a targeted refresh. Only a few files have many commits. Two or three dependencies are out of their support window. Build takes 40 minutes; no one runs the full suite locally. One endpoint or one N+1 query is responsible for most of the latency complaints, and the frontend is a framework generation behind. Those are separate, estimable projects—dependency and runtime upgrades, pipeline and test-time work, a query and caching pass, and an incremental frontend modernization. The typical shape is 4 to 10 weeks, and the product is still shipping.
When the structure is wrong but the business can’t stop, choose a strangler-fig substitute. You put a router or gateway in front of the existing app, choose the slice with the clearest boundary and the most pain (reporting, notifications, search, or a public API), and serve it from new code, everything else remaining in place. Each slice pays for the next one; rollback is a routing change, and traffic moves by percentage. This is also the right answer when you need to change the stack for hiring reasons. The team learns the new stack on a real but survivable cliff.
Only do a full rebuild when one of three things is true. The domain model is fundamentally wrong—the app was built on a concept the business no longer uses, and now all the features are fighting it. An unsupported runtime or framework that has no upgrade path (or a vendor that has gone out of business) The platform is basically dead. Or the product that is there is no longer the product you are selling, so you are not rebuilding; you are building something new that just so happens to replace it. If the case for the rebuild is "the code is a mess," then you are proposing to pay for a rewrite to solve a readability problem.
Pick, keep, and defer with purpose, never by accident. It’s fine for a product to be sunset, a seasonal system with a hard freeze window, or a team that’s one hire away from being able to do the work right. If you don’t write down what you’re deferring, what would force the decision, and when you’ll revisit it, then deferring becomes decay.
Rendering diagram…
// middleware.js — Next.js edge middleware in front of the legacy app.
// One file is usually all the "strangler" infrastructure you need to start.
import { NextResponse } from 'next/server';
const SLICE = '/reports';
const LEGACY_ORIGIN = process.env.LEGACY_ORIGIN; // existing app
const ROLLOUT = Number(process.env.REPORTS_ROLLOUT ?? 0); // 0–100, changed at runtime
function bucket(request) {
// Sticky: a user who saw the new reports page keeps seeing it.
const existing = request.cookies.get('slice_reports')?.value;
if (existing) return existing;
return Math.random() * 100 < ROLLOUT ? 'new' : 'legacy';
}
export function middleware(request) {
const { pathname } = request.nextUrl;
if (!pathname.startsWith(SLICE)) {
// Everything else still goes to the app you are refreshing in place.
return NextResponse.rewrite(new URL(pathname, LEGACY_ORIGIN));
}
const assignment = bucket(request);
const response =
assignment === 'new'
? NextResponse.next() // new service
: NextResponse.rewrite(new URL(pathname, LEGACY_ORIGIN)); // old path
response.cookies.set('slice_reports', assignment, { maxAge: 60 * 60 * 24 * 30 });
response.headers.set('x-slice-reports', assignment); // tag every metric
return response;
}
export const config = { matcher: '/((?!_next|favicon.ico).*)' };
// Why this matters: rollback is an env var, not a release. And because every
// response is tagged, your dashboards can compare error rate and latency for
// old versus new on the same traffic — which is the evidence that funds slice two.
MVP development planner
Scope, budget, and launch plan
Choose the product, platforms, and modules. The range, phases, and team update as you go. It is a planning figure, not a quote.
Newsletter
One architecture teardown or evaluation, every other week
Practical write-ups on framework choices, migrations and scaling decisions – written for people who have to justify the call in a meeting.
Newsletter
Get the 2026 Tech Stack Guide
Join the KarmaKoders newsletter for architecture notes, stack evaluations, and build playbooks.