Tech Blog•
September 23, 2026
•
8 min read
•
1 views

Refresh or Rebuild? Signs Your App Needs a Refresh

karmakoders Team
Design & Engineering
Diagram comparing a targeted technical refresh, an incremental strangler migration and a full rebuild of an existing application

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 refreshStrangler-fig replacementFull rebuildMaintain and defer
CostLowest — weeks of focused work on a known surfaceModerate, spread over quarters and absorbed into normal deliveryHighest, and the estimate is almost always the optimistic oneCheap now, compounding later as workarounds accumulate
Time to first valueDays to weeks — upgrades, caching and pipeline fixes land immediatelyWeeks — the first replaced slice goes live earlyMonths, sometimes years, with nothing shippable in betweenNone; you are buying time, not improvement
Risk to live revenueLow, with tests and staged rolloutLow per slice — traffic shifts gradually and rolls back at the edgeHigh, concentrated at one cutover date on a system nobody has operated beforeLow today, rising with each unpatched dependency
Lock-in createdMinimal — you keep your optionsLow; each slice can use a different stack if justifiedHigh — you are married to today's stack choice for the next five yearsNone new, but existing lock-in hardens
Ops burden afterUnchanged, often better once CI and observability are fixedTemporarily doubled — two systems, one router, until the last slice movesEventually simpler, if the rebuild actually finishesGrows; more manual steps, more tribal knowledge
Team and hiring fitGood if the stack is still mainstream; poor if nobody will work on itGood — the team learns the new stack on real, small scopePopular with the team, which is exactly why it needs cold scrutinyWorst — attrition risk rises when engineers see no path out
Fails whenThe domain model itself is wrong, or the platform is genuinely end-of-lifeBoundaries cannot be drawn; everything reads and writes the same tablesRequirements are undocumented and the business cannot pause feature workA 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.

Recommended architecture: strangler routing at the edge, refresh behind it

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.

Product
Platforms
Modules
Design
Pace
Launch scale
Compliance

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.

No spam. Unsubscribe anytime.

Newsletter

Get the 2026 Tech Stack Guide

Join the KarmaKoders newsletter for architecture notes, stack evaluations, and build playbooks.

No spam. Unsubscribe anytime.

Book a call WhatsApp