Back to blog
ProductProduct EngineeringSaaS

La Minute Builder #12 — Here are the 5 mistakes that stop an MVP from becoming a real product

8 min read
5 MVP mistakes — Design System, Atomic Design, modular monolith and architecture debt for scaling a SaaS product

Why do some MVPs keep evolving for ten years… while others have to be completely rewritten just months after launch?

Building an MVP quickly has never been easier.

With AI, no-code tools and vibe coding, you can ship a product in a few days.

But building fast doesn't guarantee you can evolve fast.

The problem isn't development speed.

It's the architecture chosen at the start.

« It's just an MVP. We'll clean it up later. »

That sentence sounds reasonable.

Yet that's often when a product's biggest debt is born.

Not technical debt.

Architecture debt.

Unlike a few bugs or imperfect code, bad architecture gradually slows down the whole team.

Every new feature becomes more expensive.

Every change becomes riskier.

Every new developer takes longer to understand the project.

Here are the five mistakes I see most often.

1. Coding everything directly in screens

When you're building an MVP fast, it's tempting to wire buttons, icons and forms directly into each screen.

The trap: you're not really reusing a CSS class. You're copying the full markup — icons included — into every view.

For example:

<!-- Login.vue — everything lives in the screen -->

<button class="btn-primary flex items-center gap-2">
  <svg width="16" height="16"><!-- lock icon copied --></svg>
  Sign in
</button>

Then a few days later:

<!-- Checkout.vue — same pattern, copied by hand -->

<button class="btn-primary flex items-center gap-2 px-5">
  <svg width="16" height="16"><!-- card icon copied --></svg>
  Pay
</button>

Then:

<!-- Profile.vue — yet another slightly different variant -->

<button class="btn-primary flex gap-2">
  <svg width="14" height="14"><!-- same icon, different size --></svg>
  Edit
</button>

At first, it works perfectly.

Then a designer changes the button color, spacing, or swaps an icon.

You have to update every screen individually — button by button, icon by icon.

Even an icon should be a component, not an SVG pasted into twelve files.

Weeks later, some buttons already behave differently.

The product becomes inconsistent.

A better approach: the Design System

A Design System extracts these patterns into generic components — buttons, icons, inputs — used everywhere.

For example:

<Button variant="primary" icon="lock">
  Sign in
</Button>

Then:

<Button variant="primary" icon="credit-card">
  Pay
</Button>

Under the hood, `<Icon />` centralizes size, color and style. `<Button />` handles variants and states.

The component stays unique.

The design stays consistent.

And when it evolves, the whole application evolves with it.

2. Copying components instead of thinking Atomic Design

A very common mistake is duplicating an existing component.

You quickly end up with:

Button.vue

ButtonPrimary.vue

ButtonFinal.vue

ButtonNew.vue

ButtonFinalV2.vue

Each component then gets its own fixes.

Its own bugs.

Its own styles.

Maintenance quickly becomes impossible.

The Atomic Design approach

Atomic Design, created by Brad Frost, suggests building an interface like Lego bricks.

Atoms

The smallest components.

  • Button
  • Input
  • Avatar
  • Label
  • Icon

Molecules

Several Atoms assembled together.

  • SearchBar
  • LoginForm
  • CardHeader

Organisms

Complete interface blocks.

  • Navbar
  • Sidebar
  • CheckoutForm

Templates

Overall page layout.

Pages

The application's final pages.

This approach has a huge advantage.

Each component is created once.

Then reused dozens, even hundreds of times.

Development gets faster.

So does maintenance.

3. Designing a data model only for today

When building an MVP, we often think about the immediate need.

Imagine a product for organizing events.

You might simply create:

User

Event

Everything works.

Then new needs appear:

  • multiple organizers
  • multiple teams
  • multiple roles
  • multiple subscriptions
  • multiple companies

The initial model is no longer enough.

You add columns.

Then exceptions.

Then workarounds.

Months later, nobody really understands the database anymore.

A better approach

« What will this product probably need to support in two years? »

Before creating a table or collection, I always ask myself this question.

Without building everything today, you can still design an architecture that can evolve.

For example:

Organization

Membership

User

Role

Subscription

Even if some tables stay nearly empty at first.

The product can grow naturally.

Without having to rebuild everything.

4. Organizing the backend by business domains

This is probably the point that changes a project's maintainability the most.

Many MVPs start like this:

src/

controllers/

services/

models/

utils/

helpers/

middlewares/

This architecture looks simple.

Then the project grows.

Services start depending on each other.

Imports become hard to follow.

Nobody really knows where to add a new feature.

The modular monolith

Today, many teams use a modular monolith.

The idea is simple.

The project stays a single application.

But each business domain is fully isolated — with its own internal structure.

src/
  Auth/
  Users/
  Payments/
  Subscriptions/
  Events/
  Notifications/

Each domain contains its own logic, from controller to tests:

Auth/
  controllers/       → auth.controller.ts
  services/          → auth.service.ts
  repositories/      → auth.repository.ts
  dto/
  entities/
  tests/

Users/, Payments/, Subscriptions/… follow the same layout. You no longer hunt for a `payments.service.ts` lost inside a global `services/` folder mixing everything together.

This approach is used in many NestJS projects.

It enables:

  • clearer separation of responsibilities
  • fewer dependencies between domains
  • easier testing
  • preparation for a possible split into microservices if the product grows

The modular monolith often offers an excellent balance between simplicity and scalability for an MVP.

5. Treating the MVP as throwaway code

This is probably the most dangerous idea.

« If the product works, we'll rewrite it. »

In reality, that rarely happens.

Why?

Because when the MVP works… customers arrive.

Then come:

  • requests for new features
  • bug fixes
  • user support
  • investors
  • hiring

Nobody wants to pause development for months to start from scratch.

The MVP naturally becomes V1.

Then V2.

Then V3.

The architecture chosen at the start often stays with the team for years.

Technical debt or architecture debt?

These two concepts are often confused.

Technical debt mainly concerns code quality.

It can usually be repaid gradually:

  • improve a component
  • remove dead code
  • add tests
  • simplify a function

Architecture debt is different.

It concerns the product's foundations.

Poor separation of concerns, a poorly designed data model or an overly coupled architecture become extremely costly to fix.

This is often the debt that forces companies to rewrite their entire software.

What to remember

An MVP doesn't need to be perfect.

It doesn't need every Design Pattern in existence.

It doesn't need to anticipate every future need.

But it must be designed to evolve.

Building fast doesn't mean building temporarily.

An MVP isn't a prototype.

It's the first version of a product meant to grow.

Technical debt can often be repaid.

Architecture debt sometimes stays with a product for years.

The biggest risk of an MVP isn't that it fails.

It's that it works.

The best products started as MVPs

Instagram was an MVP.

Airbnb was an MVP.

Spotify was an MVP.

They weren't perfect.

But their teams laid foundations solid enough to evolve their products over the years, without systematically starting from zero.

An MVP's quality isn't measured only by how fast it was built.

It's also measured by its ability to support the product's next versions.

La Minute Builder — follow the blog on Instagram, Substack and LinkedIn