Jurij Tokarski

Jurij Tokarski

Should I Rebuild My App From Scratch?

An old app, its developer gone, and a new one who wants to rewrite it all. Rarely: refactor while the stack carries what's next, rebuild beside the live app.

Our app is years old and the developer who built it left. Every new feature takes weeks, and the new developer wants to rewrite it all. Should we rebuild, or is there another way?

Rarely from scratch. Refactor while the stack can carry what you plan next, and the code gets better as it's touched.

When it can't, I'd build the new version beside the live one and move users across when it's ready.

Rewrites fail at the feature freeze, not in the code. That's the cost to ask the new developer about.

Debt Matters Where It Blocks the Roadmap

Technical debt is the extra cost every change pays for an earlier shortcut. Rewrite only when the next feature has nowhere to live.

The shortcut is a rushed data model, a library pinned years back, a function nobody dares to touch. Features that take weeks are the interest, paid on every change that passes through it.

Most debt gets paid down by the scout rule. Leave each file better than you found it, while you're already in it.

When debt blocks one feature, fixing it is that feature's first step. When it blocks every feature, the team ships slower each month.

So the test is the roadmap, not the code's age. Ugly code that can host next year's features gets better as it's touched. Clean code on a platform that can't host them is still a dead end.

Rewrites Fail at the Freeze, Refactors Don't

Refactoring wins most of the time because it never stops shipping. A rewrite freezes features until launch, and that's where it costs most.

Joel Spolsky made the case against rewrites from scratch in 2000, with Netscape as his example.

A rebuild is honest when the next thing the business needs can't live in the old app. The data model may be wrong at the root, as in a booking prototype that treated all staff as interchangeable. Even an AI-built V1 gets fixed in place unless its data model is the problem.

A slow app is the other thing a rewrite promises to fix. Measure first, on the pages users open, then fix the biggest cost and measure again. Each fix gets before and after numbers, observed, not assumed.

For this app, the question to put to the new developer is which planned feature the old code can't host. Answering it takes reading the old app first.

The Old App Is the Spec

Access comes first: the repository, the cloud accounts, the keys and the domain move into your name before any change. With the developer gone, nobody else may hold them. Who owns the code my developer writes? covers that handover.

The missing developer matters less than it seems. While the old app runs, it is the spec. When nobody remembers how something should behave, the answer is in how it behaves now.

A doc describes the product on the day someone wrote it. Code shows what happens, not what should. The live app answers both.

Whatever comes next ships documented, so the next developer doesn't depend on anyone's memory either. And because the old app is the spec, a rebuild keeps it running.

Build V2 Beside V1

Legacy app modernization moves a working app onto a stack you can keep building on. For a founder, that's often a V1 from two years ago, or an AI-built app from six months ago.

The safe way runs V2 beside V1. V1 stays live for the whole move, with no freeze and no maintenance window. V2 grows one feature at a time, starting with what V1 can't host, and users move across in batches.

A rebuild we ran for eight months worked this way. V1 was static pages that couldn't host the streaming chat panel its new AI assistant needed. The first piece of work was the bridge, not a screen: shared sign-in across both apps.

Two more things kept it safe. Every screen V2 didn't handle yet had a one-click fallback to V1. Feature flags kept unfinished V2 work hidden in production.

The cost is that every change has to work in both apps at once, and in the backend they share. A feature shipped in V1 mid-project was ported to V2, then re-ported as V1 kept changing. Shipping small kept that drift recoverable, and the cutover ended up as one flag.

Stop halfway and you keep a working app. Each step is in production before the next starts, and every commit is in your repo.

An upgrade inside one app follows the same rule. It runs beside the roadmap in small pull requests: the libraries that block it, the version bump, then the cleanup.

If this is a React app of that age, the upgrade may start with its build tool.

Leaving Create React App

Move off it, to Vite or Next.js. The React team sunset Create React App in February 2025. It points existing apps to Vite or a framework.

Vite fits an app behind a login. Next.js fits when public pages need server rendering for search.

The Vite move is mostly mechanical. Environment variables go from REACT_APP_ to VITE_, index.html moves to the project root, and Vitest replaces the built-in Jest setup.

The service worker is the trap. CRA's registration can survive the move and point at a file Vite never builds.

One of the two apps I moved lost offline that way. It didn't need it, so I left it off.

Whichever way the move goes, the app needs care after it.

A Live App Needs Monthly Care

Application maintenance keeps a live app working, safe and useful after launch: bugs, updates, security patches, API changes and small changes. For a web app, it also keeps up with browsers and hosting.

It comes in four types:

  • Corrective: fixing bugs.
  • Adaptive: keeping up with new runtimes, browsers and APIs.
  • Perfective: small improvements and speed-ups.
  • Preventive: updates, patches and cleanup before anything breaks.

The preventive kind gets skipped, because nothing is broken yet. Skip it long enough and the other three get expensive. An app whose developer left may have gone without it since.

One app of mine went three years without a deploy. When work on it resumed, it started with version updates, not features.

How I Take Over an Old App

Mine starts with a week of reading, before anything changes. I write down what only the original developer knew, map what depends on what, and list the risks.

The reading ends in a verdict: maintain, refactor or rebuild, with the migration order. When it says rebuild, I say so rather than keep patching. Keep going, or stop and take the map, the risk list and the plan to anyone, the new developer included.

A rebuild runs as app modernization, V2 beside V1. Its length follows how much the old app does. The eight-month rebuild spent two of those months on stability, between matching V1 and moving users.

Care runs as app maintenance, with all four types on one board. Dependencies get current within a couple of weeks, then take about 30 minutes and a deploy each week. Reported bugs go first.

The same week of reading works when an agency hands me a client's old code. That way, nobody promises the client anything before the reading is done. Can my agency sell white label development? covers that setup.

All of it runs at one weekly price, on the homepage. You can pause or cancel any week.

What the Founder Should Do Now

Move the access into your name first: the repo, the cloud accounts, the keys and the domain.

Then ask the new developer which planned feature the old code can't host. A clear answer is a reason to rebuild beside the old app. No answer is a reason to refactor as you go.

Then put the plan to one test: could you stop next week and still have a working app? A refactor passes it every week, and so does a rebuild beside the old app. A rewrite from scratch fails it until launch day.

Got thoughts on this? Reply by email.

Jurij Tokarski

Jurij Tokarski

I run a solo business and produce software. Usually I'm shipping for clients or building for myself. Sometimes I pause to write about it.

x.comlinkedin.comjurij@varstatt.comRSS