Skip to content
OTFotf
All posts

The Hidden Journey from AI-Generated MVP to Production-Ready App

D
DaveAuthor
8 min read
The Hidden Journey from AI-Generated MVP to Production-Ready App

AI just gave us the fastest MVP in history

Last week you didn't have a product. Tonight you have a login screen, a dashboard, a Stripe button, and a working API — written by Cursor or Claude Code while you were making dinner. That is a real win, the first time in software history that "validate the idea" and "have something clickable" happen in the same evening.

This post is not about why that is bad. It is about what comes after.

The MVP compiles. The demo works on your laptop. You can sign up, hit the dashboard, and the Stripe button toggles a state. Then you try to put it in front of a real user — your first paying customer, your first investor, your first App Store reviewer — and the second weekend starts. The hidden work is not in the idea. It is in the eighty things the model did not write because you did not ask for them. This is the gap between "MVP done" and "production." Here is what is actually in it.

from-scratch AI MVP vs kit-extended MVP — hours spent on each hidden-work category

The hidden-work taxonomy

I have watched engineers hit this wall six times in the last two years. The work is not exotic. It is the same handful of jobs, in the same order, every time:

  1. Auth flows — signup, login, password reset, email verification, session refresh, RBAC, and a 2am panic when your first user cannot get back in.
  2. Billing — Stripe Checkout, webhook handling, customer portal, dunning, tax, receipts.
  3. Mobile parity — your web MVP is fine, but the user wants iOS. Now you have two component trees, two design systems, and a second build pipeline.
  4. Design coherence — every new feature is a fresh AI generation in a slightly different style. By feature ten, your app looks like five apps.
  5. Shipping — custom domain, DNS, TLS, env management across staging and prod, app store submission, code signing.
  6. Observability — error reporting, request logs, the silence that follows when a user reports "it is broken" with no other detail.

You can buy any of these as a SaaS. You can hand-roll any of them. The cost is the same in both cases: time you did not budget, in the second weekend, when you wanted to be talking to users.

11 production screens. Login, database, payments — all wired.

The SaaS Dashboard Kit ships everything already connected. Nothing to set up. Live demo at saas.otf-kit.dev.

See the live demo

Auth: the unsexy first mile

"Add login" is a one-line ask to a coding agent. The model writes a /api/auth/login route, a <LoginForm> component, and a session cookie. Great. Now the real list:

  • Password hashing (the model picked bcrypt with cost 10 — fine, until you need to migrate).
  • Email verification — which means a transactional email provider, which means an API key in your env, which means env management.
  • "Forgot password" — token, expiry, single-use, email template.
  • OAuth — Google, Apple for iOS, GitHub for the dev tool, each with its own client id and callback URL.
  • RBAC — the AI gave you a users table with no roles. Now every endpoint needs a check.

The list is finite. None of it is fun. Every SaaS founder I have talked to has spent two to five days on exactly this list, and almost all of them said "I should have just used a kit" afterward.

# What this looks like when the auth is already wired
AUTH_SECRET=$(openssl rand -base64 32)
AUTH_GOOGLE_ID=...
AUTH_GOOGLE_SECRET=...
AUTH_APPLE_ID=...
AUTH_APPLE_SECRET=...
DATABASE_URL=postgres://...
RESEND_API_KEY=re_...

Eight env vars. You did not write a single line of code. The same kit that ships the dashboard ships the auth that protects it.

Billing: the second Saturday

Stripe is famously good at checkout and famously treacherous at webhooks. The first time your subscription.updated event fires during a downgrade, you will discover:

  • Webhook signatures must be verified (STRIPE_WEBHOOK_SECRET).
  • The handler must be idempotent — Stripe retries, and your code must not double-credit.
  • Customer state must be mirrored to your DB; you cannot query Stripe on every page load.
  • Tax handling is not automatic in most regions.
  • The customer portal link expires.
// What the handler looks like when it is wired correctly
export async function POST(req: Request) {
  const sig = req.headers.get('stripe-signature')!
  const body = await req.text()
  const event = stripe.webhooks.constructEvent(body, sig, process.env.STRIPE_WEBHOOK_SECRET!)

  switch (event.type) {
    case 'checkout.session.completed':
      await db.update(users)
        .set({ planId: 'pro', stripeCustomerId: event.data.object.customer })
        .where(eq(users.id, event.data.object.client_reference_id))
      break
    case 'customer.subscription.updated':
      // mirror status — never trust the cached plan
      await syncSubscription(event.data.object)
      break
    case 'customer.subscription.deleted':
      await db.update(users).set({ planId: 'free' }).where(...)
      break
  }
  return new Response('ok')
}

This is not interesting code. It is necessary code. It is the kind of code the AI did not write because you did not say "handle subscription churn gracefully" — you said "add a Stripe button."

Mobile parity: the same screen, twice

If your MVP is web-only and you want iOS, the hidden work is not "build the iOS app." It is "make the same component render in two places without two design systems."

The naive answer is a PWA. The PWA looks fine until you want push notifications, an App Store listing, or a native gesture. Then you have a real native build, and a real native design system, and the same dashboard has to be built twice — once for the web runtime, once for the native runtime — and every new feature is two features.

// What this looks like when one component renders everywhere
import { Button } from '@otfdashkit/ui'         // web
import { Button } from '@otfdashkit/ui-native' // iOS / Android

// same name, same props, same look — different runtime underneath
<Button variant="primary" size="md" onPress={handleCheckout}>
  Start free trial
</Button>

The point is not the import path. The point is that the design tokens — colors, spacing, radius, typography — flip one theme across both runtimes, so the component is visually identical on web and native without a per-platform stylesheet.

Design coherence: the tax that compounds every feature

This is the one nobody warns you about. Every new feature, you ask the model to generate. Every generation is slightly different:

  • The first dashboard uses one shade of gray for muted text. The second uses another.
  • The first card has 16px radius. The fifth has 12px. The ninth has 20px.
  • The first button is 40px tall. The third is 44px (because iOS, fine). The seventh is 38px and you do not know why.

By feature ten, your app looks like five apps stitched together. By feature twenty, it looks like an intern built it, because that is functionally what happened.

The fix is not "be more careful." The fix is a design system the AI extends instead of regenerates. Twenty tested prompts that say "use the existing <Card> and <Button>; do not invent new variants" — committed to the repo as CLAUDE.md, .cursorrules, and a folder of ai/prompts/ — so every generation goes through the same front door.

# CLAUDE.md
## Components
- Use `<Button>` and `<Card>` from the kit. Never invent a new variant.
- Use tokens (`color.muted`, `space.4`) — never raw hex or pixel values.
- Every new screen must pass `pnpm check:design` before merge.

That file is thirty lines. The savings are cumulative, and they compound with every feature you ship.

Shipping: the part nobody budgets

Localhost is not a product. The gap between "works on my machine" and "works at app.example.com with a custom domain, valid TLS, a mobile build submitted to the App Store, and a separate staging environment" is where many AI-built MVPs die quietly.

  • DNS records (CNAME, A, the apex redirect).
  • TLS provisioning (Let's Encrypt, automatic renewal).
  • Env management (the eight env vars from earlier, but per environment, encrypted at rest).
  • Mobile build (Xcode archive, code signing, TestFlight, App Store Connect).
  • Play Console (the equivalent, with its own signing key ceremony).
# What "ship" looks like when the infrastructure is wired
pnpm ship --domain app.example.com --env production
# → provisions TLS, sets DNS, builds web, builds iOS, builds Android,
#   uploads to TestFlight and internal Play track

One command. The work it represents is dozens of steps, three different vendor dashboards, and at least one Stack Overflow pilgrimage about Apple code signing.

What this gets us

The hidden work is not a moral failing of AI coding tools. They wrote exactly what you asked for. The problem is that "production" is a destination the prompt does not know about. You asked for an MVP; the tool delivered an MVP. The eighty other things were always going to be between you and the user, in every era of software, with every tool.

The win is not "stop using AI to build." The win is to make the hidden work a one-command affair, so the second weekend is the weekend you ship, not the weekend you wire up Stripe webhooks.

A full-stack kit you own the code to closes exactly this gap: auth wired, billing wired, mobile parity in one codebase, design coherence enforced by a 24-item checklist, ship-to-production as one script. The AI tool then extends the kit instead of regenerating it, which is the part that does not change when the model does. Roughly 200 components, the same name and props on web and native, free on npm, paid kits at $99 with an everything bundle at $149 — pick the boring infrastructure once, put it in the repo, then let the AI ship features on top of it.

The fastest MVP in history is great. The fastest production app is better. The two are not the same job, and pretending they are is where the second weekend goes. Pick the boring infrastructure once. Then talk to users instead of config files.

ai-toolsarchitecturebackend
OTF SaaS Dashboard Kit

Ship the product, not the setup.

  • 11 production screens — auth, billing, team, analytics, settings
  • Real database, payments, and login — all wired on day 1
  • AI configs pre-tuned so your agent extends instead of regenerates