MVP and architecture · 6 min
Why MVPs get rewritten after a year: decisions that preserve version one
Why MVP rewrites become expensive: unclear boundaries, brittle integrations, data ownership, missing observability and no path for the next release.
Fast does not mean unstructured
A first release can be small while its boundaries stay explicit. When domain rules, HTTP handlers and integrations are mixed, every change becomes a product-wide risk.
A minimal architecture is not a microservice collection. Separate responsibilities, define contracts and leave a path to replace a component later.
Integrations without queues break launch
A synchronous provider call is convenient in a demo, but its latency and limits soon enter the user journey. Important operations need idempotency, a queue and an explicit pending state.
If that is not possible yet, at least persist the input and provider response. Otherwise a lost request cannot be recovered.
Data and permissions are not later work
A temporary table becomes permanent, and a universal admin becomes a leak risk. Define ownership, lifecycle and least-privilege roles before real users arrive.
Migrations must be repeatable and verifiable. Rewrites often start with a schema that cannot be changed safely, not with a bad interface.
Make growth observable
Errors, latency, queues and key business events belong in an MVP. Without them, the team sees complaints but cannot locate the failure.
Before expanding, run a short technical review: what handles current volume, what will become a bottleneck and which contour should be strengthened first.
Give the first release a next boundary
Even when validating one hypothesis, define where a second role, channel or integration would fit. This does not mean building it now; it keeps the decision possible.
Layer contracts and a clear domain model are usually more valuable than early infrastructure complexity.
Make technical debt visible
Mark temporary choices: manual import, simplified permissions, missing cache or local script. Give each a trigger for replacement.
Invisible debt is more dangerous than a long task list because new features are planned as if the temporary layer did not exist.
Test quality with real traffic shapes
Tests need realistic data size, retries and dependency failures. Small staging volumes hide index and queue problems.
Load testing does not replace observability, but it reveals where architecture stops being predictable.
A rewrite can be the right decision
Not every rewrite is failure. It can be justified by changed product boundaries, a blocked requirement or a repair cost higher than migration.
Base the decision on dependencies, measurements and a staged transition plan, not team fatigue with old code.
Separate a hypothesis error from an architecture error
If users do not complete the journey, rewriting code is premature; validate product value first. If demand is proven but changes are expensive because modules are coupled and tests are missing, the problem is architectural.
This diagnosis guides a scope change, staged refactor or a genuinely new contour.
Give migration a measurable success condition
Choose what improves: change lead time, incident rate, infrastructure cost or response speed. Compare old and new behavior on real data while both paths are available.
Without a criterion, a team may replace technology without removing the reason the MVP became expensive.
Do not confuse technical debt with a missing product
Teams sometimes want a rewrite because the original hypothesis no longer matters to the market. Compare current journeys, retention and support requests with what the product was meant to validate.
If demand is missing, a new architecture will not save the budget. If demand is proven, isolate bottlenecks and change one contour at a time.
Preserve working boundaries during refactoring
Put the new module behind an adapter and keep the old contract for the rest of the system. A parallel path or feature flag enables comparison and a fast rollback.
A migration without an observable switch becomes another big bang, even with cleaner code.
Migrate data by rules, not just tables
Define field mapping, transformations, defaults and rerun behavior. Validate history, permissions and object links, not only row counts.
For critical data keep before/after reports and owner spot checks. This costs less than finding a silent error after the legacy path is removed.
Record decisions that should not be repeated
A short decision record explains a boundary, queue, database or timeline trade-off. It saves a new team from rediscovering the same problem next year.
Document deliberate deferrals too. An unexplained deferral quickly becomes forgotten debt.
Audit before deciding to rewrite
List incidents, slow changes, manual workarounds and modules the team avoids. Link each symptom to a cause and cost. A few focused changes often remove most pain without stopping the whole product.
Keep client compatibility during migration
Public APIs, mobile versions and data exports outlive internal modules. Add adapters, versions and a transition window so every client does not need a same-day update. This lowers risk while the new path is verified.
Budget for temporary dual operation
The old and new contours may run in parallel during migration. Price infrastructure, reconciliation and support, then compare it with a one-day switch. A transparent temporary cost is often cheaper than a long outage.