Nobody signs off a rewrite because the code is untidy. They sign it off because the site got slow, the team got scared of it, and every small change takes a week. Those are three separate problems, and none of them requires starting over.
Start with a performance budget, not a refactor
The first thing we do on an inherited codebase is write down what “fast enough” means in numbers, then measure against it on every deploy. A budget makes debt visible: when a new marketing script pushes the largest contentful paint past two and a half seconds, the build tells you, and the conversation happens before launch instead of three months later.
Budgets also stop the refactor from expanding. You are no longer trying to make the code good. You are trying to make specific numbers hold, which is a scope you can finish.
Consolidate components before you improve them
Most debt in a front end is duplication. Four button implementations, three card variants that differ by two pixels, two competing grid systems. Improving all of them costs four times what improving one costs, so the first pass is purely arithmetic: find the duplicates, pick a winner, and point everything at it.
We do this with a component inventory — a boring spreadsheet listing every visual pattern and where it appears. It is the least glamorous artefact of the project and the one that saves the most time.
What the inventory should record
- The pattern, and every template that renders it
- Which version is the most complete implementation
- Whether it has accessibility problems that must be fixed on consolidation
- Who owns the content that flows through it
A rewrite converts known debt into unknown risk. It feels like progress because the ticket count resets.
Prune dependencies you cannot justify out loud
Every package in the bundle needs an owner who can explain what it does and what breaks without it. If nobody can, it goes on the list. In practice a third of the front-end payload on an aging site is a carousel library used once, a date utility used for one format string, and an analytics wrapper that was replaced but never removed.
Removing those does not make the code more elegant. It makes it smaller and easier to reason about, which is what everyone actually wanted when they asked for a rewrite.
Document a deprecation path and hold it
The last step is the one teams skip: writing down what is now deprecated, what replaces it, and when the old thing stops being supported. Without a stated end date, both versions live forever and the debt doubles instead of clearing.
We keep this in the repository next to the code, not in a wiki nobody opens. Three sentences per deprecation is enough: what it was, what to use instead, when it disappears.
What this looks like on a real timeline
- Week one — audit, budget, component inventory.
- Weeks two to four — consolidation of the top ten duplicated patterns.
- Week five — dependency pruning and bundle work against the budget.
- Week six — deprecation notes, handover, and a written list of what we deliberately left alone.
Six weeks, no rewrite, and a codebase your team is willing to touch again. That last part is the actual deliverable.

