Sooner or later, most teams with an app built using AI coding tools hit the same wall. The product has users, maybe revenue, but every change takes longer than the last one, bugs reappear in new places, and the AI assistant that wrote the code now struggles to modify it without breaking something. At that point someone usually says it: maybe we should just start again.

Sometimes that is the right call. More often it is an expensive way to rediscover the same problems. A rewrite feels clean because the new code has not met reality yet, while the existing app, with all its flaws, already handles real users, real data and edge cases nobody wrote down.

This article gives founders, CTOs and product owners a practical way to decide between stabilising and refactoring an AI-generated codebase and rewriting it, and a phased plan that keeps the product shipping either way.

Why the question comes up with vibe-coded apps

Code produced quickly with tools like Cursor, Lovable, Bolt or Claude has some recognisable traits. None of them are unique to AI, but they show up together and early:

  • No tests. The app was verified by clicking through it, so nobody knows what a change will affect.
  • Inconsistent patterns. Each prompt solved its problem in its own way, so there may be three approaches to data fetching and two to form validation.
  • Duplication. Similar components and helpers were regenerated instead of reused.
  • Thin security. Keys in client code, permissive database rules, missing rate limits.
  • Lost intent. Nobody can explain why something was built a certain way, because no person made that decision.

These problems make a codebase unpleasant, but unpleasant is not the same as unsalvageable. The decision should rest on evidence, not on frustration.

The default answer: stabilise first

For most AI-built apps, the better starting point is to stabilise and refactor incrementally. The reasons are practical:

  • The existing code encodes behaviour users depend on, including edge cases nobody remembers.
  • A rewrite stops or slows feature work for months while competitors keep moving.
  • The new version must reach parity before it can replace the old one, and parity is always larger than estimated.
  • Many of the worst problems, such as exposed secrets and missing access rules, can be fixed in days without touching the architecture.

This is also how EmphaSoft approaches taking over and modernizing AI-generated codebases: we rarely suggest a rewrite, and when we do, it is because the audit showed that rebuilding would cost less than fixing what is there.

Signals that refactoring is the right path

  • The core data model is broadly sound, even if naming and structure are messy.
  • The framework and hosting choices are mainstream and supported, and fit where the product is going.
  • Problems are concentrated in specific modules rather than spread through everything.
  • The app mostly works for users. The pain is on the development side: slow changes and regressions.
  • Security gaps exist but are of the fixable kind: move secrets, add access rules, add validation.
  • You can get the app running locally and deploy it from the repository.

Signals that a rewrite may be justified

  • The data model is fundamentally wrong for the product, for example a single-user design that now has to support teams and permissions, and fixing it would touch nearly every file.
  • The app is locked into a builder platform or stack that cannot meet a hard requirement, such as a native mobile capability, a compliance constraint or a performance target.
  • The product itself has changed so much that most of the existing features are no longer needed.
  • Security problems are structural, for instance business logic and authorization living entirely in the client with no server layer to enforce them.
  • The codebase is small enough that rebuilding the parts users depend on is genuinely a matter of weeks, and you have a clear, written specification of what those parts are.

Even when several of these apply, a full rewrite in one go is rarely necessary. Usually only one layer is wrong, and it can be replaced while the rest keeps running.

A decision framework

Before deciding, get facts. A short audit should answer four questions, and the answers point to the approach.

QuestionPoints toward refactoringPoints toward rebuilding a part or all
Is the data model right for the next 12 to 24 months?Yes, with clean-upNo, core entities or relationships are wrong
Can the stack meet your hard requirements?YesNo, a platform limit blocks a must-have
Where do the problems live?A few modulesEverywhere, with no stable core
How much behaviour must be preserved?A lot, much of it undocumentedLittle, and it is well specified

Then compare costs honestly. For refactoring, estimate the stabilisation work plus the ongoing drag of the remaining debt. For a rewrite, estimate building to parity, migrating data, running both systems during the switch, and the features you will not ship in the meantime. Rewrite estimates are almost always optimistic, so add a generous buffer.

A useful middle option is the partial rebuild: keep the parts that work, and replace one layer, such as the backend API, the data model or the mobile client, behind a stable interface. This is often the right answer when only one of the four questions above comes out badly.

A phased modernization plan

Whichever direction you choose, the work goes better in phases, each of which leaves the product in a better and shippable state.

Phase 1: Audit and triage

  • Read the code, map the main flows and data model, and check dependencies.
  • Review security against a reference such as the OWASP Top 10.
  • Produce a written list of risks ranked by what they could cost the business, not by how ugly the code is.
  • Make the rewrite or refactor decision here, with evidence.

Phase 2: Stop the bleeding

  • Rotate exposed keys and move secrets out of the code.
  • Enable and test access rules, add server-side validation and rate limits.
  • Set up automated backups and confirm a restore works.
  • Add error monitoring and uptime alerts so you see problems before users report them.

Phase 3: Build a safety net

  • Write tests around the flows that matter most: sign-up, login, the core action, payments, and authorization rules.
  • Set up continuous integration so tests run on every change and failures block the merge.
  • Introduce code review, with a named person approving each merge, including changes written by AI assistants.

Phase 4: Refactor in small steps

  • Pick one pattern for each concern (data fetching, forms, state, styling) and migrate toward it module by module.
  • Remove dead code and duplicated components as you touch each area.
  • Clean up the data model through versioned migrations, keeping old and new structures compatible during the transition.
  • Prioritise the modules you change most often. Code nobody touches can stay imperfect for a while.

Phase 5: Replace what cannot be fixed

If the audit identified a layer that must be rebuilt, replace it incrementally using the strangler fig approach that Martin Fowler describes: put a stable interface in front of the old component, build the new one behind it, move traffic over feature by feature, and remove the old code once nothing depends on it. This turns a risky big-bang switch into a series of small, reversible releases.

How to keep shipping during modernization

The biggest risk in any modernization effort is that the product stands still. Users and investors do not care about the refactor; they care about what ships. A few practices keep both moving:

  • Share capacity, do not freeze features. Reserve a fixed share of each sprint for stabilisation work and keep shipping features with the rest. The exact share depends on how urgent the risks are, and it can shrink as the safety net grows.
  • Refactor where you build. When a feature touches a messy module, clean up that module as part of the feature. This keeps refactoring tied to business value.
  • Use feature flags. Release new or rewritten parts to a small group first, compare behaviour, and switch back instantly if something goes wrong.
  • Keep releases small. Many small deploys are easier to review, test and roll back than one large one.
  • Measure progress. Track a few signals such as regression count, time from change to release, and error rate, so the team can see whether the effort is working.
  • Keep using AI tools, with guard rails. AI assistants are much more useful in a codebase with tests, consistent patterns and clear module boundaries. Modernization makes them productive again rather than replacing them.

Common mistakes to avoid

  • Deciding before auditing. The rewrite decision made in a frustrating week is rarely the right one.
  • Rewriting with the same process. If the first version had no tests and no review, a second version built the same way will end up in the same state, only later.
  • Underestimating data migration. Moving real customer data into a new model is often the hardest part of a rewrite and deserves its own plan and rehearsal.
  • Refactoring without tests. Restructuring code with no safety net simply moves the bugs around. Tests come before large refactors.
  • Treating it as a one-off project. Code health needs an owner after the clean-up, or the debt returns.

Before any of this, it helps to know where you stand. Our production readiness checklist for vibe-coded apps covers security, data, testing, observability, backups and compliance, and makes a good input to the audit.

Conclusion

Rewriting an AI-generated app is occasionally the right decision, but it should be the conclusion of an audit, not a reaction to a bad week. In most cases the faster and safer route is to fix the critical risks first, build a safety net of tests and monitoring, refactor in small steps alongside feature work, and replace only the layers that truly cannot be saved.

If you want an outside view, EmphaSoft can review your repository and help you stabilise and extend your vibe-coded product, or add engineers to your team through staff augmentation while you keep shipping. Tell us about your codebase, and we will give you a straight answer on whether to refactor, rebuild a part, or start over.