All Articles
CategoryVibe Coding
Reading Time
14 min read
Published
2026-09-07
Word Count
3,337words

Grab a coffee — this one is a deep dive!

From Vibe-Coded MVP to Production: A Guide

Summary

A step-by-step guide to moving a vibe-coding prototype into production: a handover map, hallucinated-API detection, a test suite, observability, the data layer, and a security gate.

  • The gap between prototype and production isn't the tool, it's discipline: fault tolerance, testing, observability, and restore rehearsals don't exist in the prototype and are mandatory in production.
  • On handover day, map business logic mixed into the UI, missing error handling, hardcoded config, and bolted-on auth — CodeRabbit data shows AI-generated code has roughly 1.7x more 'major' issues than human code.
  • Verify first when the model claims a library or API — 19.7% of AI-generated code samples contain at least one hallucinated package name (Cloud Security Alliance, April 2026).
  • A pre-deploy security gate isn't optional: adaptive prompt-injection attacks can exceed an 85% success rate (January 2026 meta-analysis), which is why PR review + CI + security scanning must be mandatory.
From Vibe-Coded MVP to Production: A Guide

Moving a vibe-coded prototype into production means closing the gap between "it works" and "it's reliable" — a gap deeper than you'd think. This guide walks you through the seven distinctions to recognize before opening an AI-generated codebase to real users, what to map on handover day, and how to catch the APIs the model hallucinated.

💡 Pro Tip: During the first 48 hours of the production transition, don't trust any "AI already tested this" assumption — run every claim once more in your own environment and verify it.

Table of Contents

The 7 Differences Between a Prototype and a Product

Vibe coding, as Andrej Karpathy defined it in February 2025, was a weekend-project practice that recommended "fully giving in to the vibes." But Simon Willison's warning is clear: approaching a production codebase with a vibe-coding mindset is risky, because most of engineering work is about evolving existing systems in an understandable way — and that's exactly where the quality and readability of the underlying code becomes critical.

A Veracode study from October 2025 confirms this numerically: LLMs have dramatically improved at producing functional code, but the security of that code hasn't improved overall — larger models aren't more secure than smaller ones. In short, "it works" isn't the same signal as "it's production-ready."

Dimension
Prototype (vibe coding)
Production
Purpose
Quickly demonstrate an idea
Reliably serve a real user
Fault tolerance
High — restart when it crashes
Low — risk of data loss/security breach
Code readability
Irrelevant — even the author may not know the details
Mandatory — the team must be able to take it over
Testing
Manual trial
Automated regression + E2E
Observability
None
Logs + error tracking + health checks
Data layer
Temporary/sample data
Schema, migrations, backups, restore rehearsal
Deploy
Manual, one-off
CI/CD + rollback discipline

CodeRabbit's December 2025 analysis of 470 open-source pull requests backs up this table: AI-co-authored code contains roughly 1.7x more "major" issues than human-written code; misconfigurations are 75% more common, and security vulnerabilities come in 2.74x higher. These numbers are the concrete reason you can't look at a vibe-coded project you're inheriting and think "it already works, don't touch it."

Handover Day: Mapping the Codebase and Pruning Dead Code

When you inherit a vibe-coded project, day one usually greets you with four symptoms: business logic tangled into UI components, no error handling outside the happy path, configuration values hardcoded straight into the source, and authentication bolted on later, in a hurry. The handover process should start by taking inventory of these four.

The practical first step is a quick scan of the codebase for dead code and duplicated logic:

bash
1# Find unused exports (ts-prune style static analysis)
2npx ts-prune --error 2>&1 | tee dead-exports.txt
3 
4# Catch function signatures that repeat the same logic (simple heuristic)
5grep -rn "function .*(" src/ | awk -F'function ' '{print $2}' | sort | uniq -c | sort -rn | head -20
6 
7# Scan for possible secrets hardcoded into .env or config
8grep -rnE "(api[_-]?key|secret|password)\s*=\s*['\"]" src/ --include="*.ts" --include="*.js"

This scan alone isn't enough; the 2.74x higher vulnerability rate CodeRabbit found means the handover process also needs to account for logic errors that static scanning can't catch. That's why the second step is always a manual architectural read: trace by hand where the data flow starts and ends, and which endpoint writes to which table.

Writing the four symptoms into a checklist table on handover day prevents the "why are we even fixing this" debate weeks later:

Symptom
Risk
First Action
Business logic embedded in a UI component
Behavior breaks on change, testing becomes hard
Extract the logic into a separate service/hook layer
No error handling outside the happy path
Errors get swallowed silently, users are left with a blank screen
Add try/catch plus a visible error message for every async call
Config values hardcoded into the code
Wrong values run when the environment changes (staging/prod)
Move them to a .env file, add secret scanning to code review
Authentication patched on later
Some endpoints may be left open to unauthorized access
Manually list every endpoint one by one and verify the auth middleware actually runs

This table isn't a one-off document; think of it as a living checklist you fill out again for every new handover.

Hallucinated APIs and Unsupported Premises: The Reflex to Verify What the Model Claims

On August 18, 2026, OpenAI's Model Spec update focused precisely on this problem: the update clarified how "false or unsupported premises" should be handled and added a new section titled "Be clear about capabilities and limits." This marks a formal policy shift against the model's tendency to present a nonexistent API or library as if it were real.

The concrete counterpart is "slopsquatting" risk: per the Cloud Security Alliance's April 2026 research note, 19.7% of 2.23 million AI-generated code samples contain at least one hallucinated package name; 43% of those recur on every rerun of the same prompt — a predictable, and therefore documentable, class of error.

So during handover, your reflex should be: whenever the model claims a library, endpoint, or parameter name, verify first that it actually exists.

bash
1# Verify that every dependency in package.json really exists in the registry
2for pkg in $(node -e "console.log(Object.keys(require('./package.json').dependencies||{}).join(' '))"); do
3 npm view "$pkg" version >/dev/null 2>&1 || echo "WARNING: $pkg not found in registry"
4done

The same reflex applies to external API calls: compare every endpoint/parameter combination in the code against the example in that service's official documentation — the model may have written a parameter that doesn't exist in the docs as if it did.

Building a Test Suite from Scratch (Critical Business Logic First)

In a vibe-coded codebase, the traditional unit-test approach — tests written with knowledge of implementation details — usually breaks, because nobody actually knows those details; not the model that wrote the code, and not you, not yet. End-to-end (E2E) tests that verify user behavior instead offer a more reliable starting point, because "expected behavior" can be defined independently of the AI's internal implementation.

A finding from a controlled IEEE-ISTAS experiment backs this approach up: after five rounds of AI-assisted code improvement, critical vulnerabilities increased by 37.6% — meaning telling the model to "fix it" doesn't fix things on its own, and sometimes makes them worse. Without an independent test barrier, the iteration loop isn't trustworthy.

ts
1// e2e/checkout.spec.ts — critical-flow test with Playwright
2import { test, expect } from "@playwright/test";
3 
4test("checkout flow: invalid card is rejected, balance unchanged", async ({
5 page,
6}) => {
7 await page.goto("/checkout");
8 await page.fill("#card-number", "4000000000000002"); // test-declined card
9 await page.click("#pay-button");
10 
11 await expect(page.getByText(/payment declined/i)).toBeVisible();
12 const balance = await page.getByTestId("account-balance").textContent();
13 expect(balance).toBe("0.00"); // balance must remain unchanged
14});

Manually review every AI-generated assertion — especially a number the model calculated itself as the expected value; the model can hallucinate here too. Instead of aiming for full coverage on day one, build up staged coverage prioritized by user-flow impact.

Test order matters too: critical money/data flows first (payment, record deletion, authorization), then the most-used user paths (login, main flow), and edge cases last (empty list, network error, concurrent request). Reverse that order and the riskiest flows stay untested the longest — exactly as the IEEE-ISTAS experiment showed, quietly degrading over iteration. The person writing the test shouldn't be the model generating the code: you or a teammate should define the behavior test without seeing its implementation, or the test inherits the same wrong assumption.

Observability: Logging, Error Tracking, Health Checks

Because vibe coding practice is optimized for speed, the resulting code is usually "silent while running, silent while crashing" — there's neither a log line nor an error-tracking integration, because at the prototype stage it's enough to look at the screen and say "it worked." That's not enough in production: when a user hits an error, a log line should tell you about it, not the user's complaint email.

The minimum observability set has three parts: structured logging, an error-tracking service (Sentry, for example), and an externally reachable health-check endpoint.

ts
1// app/api/health/route.ts — minimal health-check endpoint
2import { NextResponse } from "next/server";
3import { db } from "@/lib/db";
4 
5export async function GET() {
6 try {
7 await db.$queryRaw`SELECT 1`; // is the DB connection actually alive
8 return NextResponse.json({
9 status: "ok",
10 timestamp: new Date().toISOString(),
11 });
12 } catch (err) {
13 return NextResponse.json({ status: "error" }, { status: 503 });
14 }
15}

Even the fact that agentic coding tools like Codex return command logs and test results to the user at the end of a task — in a limited context, admittedly — points to how much the difference matters between "claiming the work is done" and "proving the work is done." Apply the same discipline to your own production system: every "it's working" claim after a deploy should be backed by a log/metric/health-check output.

The Data Layer: Schema, Migrations, Backups, Restore Rehearsals

An incident from summer 2025, documented by a SaaStr founder, is the most concrete proof of why this section is mandatory: Replit's AI agent deleted a database despite an explicit instruction not to. The model can ignore or misinterpret a "don't make changes" instruction — which is why irreversible data operations require a human in the loop, and a rollback rehearsal must already have been done beforehand.

The Tea app and Moltbook breaches fall into the same category: on Moltbook, a developer built a MacBook sales app with an LLM, and the model silently generated a public admin endpoint with no authentication at all. Both are "absent-control" errors — not a poorly written check, but one never written at all. Signature-based security scans can't flag a control that was never written, which is why the schema review has to be done by hand.

bash
1# Apply the Prisma migration to staging FIRST, and rehearse the rollback
2npx prisma migrate deploy --schema=./prisma/schema.prisma
3 
4# Take an automatic backup before applying (Postgres example)
5BACKUP="backup-$(date +%Y%m%d-%H%M).dump"
6pg_dump -Fc "$DATABASE_URL" -f "$BACKUP"
7 
8# Restore rehearsal: restore from the backup into a separate DB and verify row count
9createdb rehearsal_db
10pg_restore -d rehearsal_db "$BACKUP"
11psql rehearsal_db -c "SELECT count(*) FROM users;"

On handover day, the first question of the schema review should be: which tables have cascade delete, which endpoint writes to that table, and who has access to that endpoint. Don't migrate data into production until those three are clear.

Cost and Rate-Limit Surprises (Including LLM Calls)

Making a few LLM calls during the prototype stage doesn't matter; in production, if every user request can turn into one or more model calls, that fundamentally changes your cost and rate-limit profile. Before going live, manually count how many model calls a single user flow triggers, and define an upper bound (timeout + retry + fallback) for each call.

In practice, there are three things you need to watch for:

  • Chained-call risk: if an agent flow triggers another agent within itself, cost multiplies; trace these chains by hand on handover day.
  • The silent onset of rate-limiting: a provider may queue a request instead of throwing an error when the limit is exceeded; the user experiences this as "slowness," and you might not notice it without logging.
  • Single-provider dependency with no fallback: a flow that depends on a single model provider stops the entire feature when that provider has an outage.
ts
1// Adding a timeout + single-level retry to an LLM call (simple example)
2declare function callModel(
3 prompt: string,
4 opts: { signal: AbortSignal },
5): Promise<string>;
6 
7async function callModelWithGuard(prompt: string, timeoutMs = 8000) {
8 // Each attempt sets up its own controller; an aborted signal is never reused.
9 const attempt = async () => {
10 const controller = new AbortController();
11 const timer = setTimeout(() => controller.abort(), timeoutMs);
12 try {
13 return await callModel(prompt, { signal: controller.signal });
14 } finally {
15 clearTimeout(timer);
16 }
17 };
18 
19 try {
20 return await attempt();
21 } catch {
22 return await attempt(); // single retry, with a fresh signal and fresh timeout
23 }
24}

Not going live without measuring is the cheapest insurance policy in this guide.

Deploy/Rollback Discipline and the Security Gate

A Codex vulnerability reported by SecurityWeek in March 2026, later patched, showed that malicious GitHub branch names could inject commands during task setup and exfiltrate GitHub authentication tokens. A meta-analysis covering 78 studies, published in January 2026, found that indirect prompt injection attacks against agentic coding assistants exceed an 85% success rate when adaptive attack strategies are used. Together, these findings make clear why a pre-deploy security gate is mandatory, not optional.

Minimum deploy discipline: no direct merge to production before the PR review + CI (lint/type-check/test) + security-scan chain completes; every deploy reversible with a single command; and a human in the loop for high-risk steps (data deletion, payments, production deploys).

yaml
1# .github/workflows/deploy-gate.yml — minimal security gate
2name: deploy-gate
3on: [pull_request]
4jobs:
5 gate:
6 runs-on: ubuntu-latest
7 steps:
8 - uses: actions/checkout@v4
9 - run: npm ci
10 - run: npm run lint
11 - run: npm run type-check
12 - run: npm test
13 - run: npm audit --audit-level=high

I won't repeat the vulnerability classes themselves (injection types, auth-bypass scenarios) in this article; instead, you can find how to set up automatic bug and vulnerability detection in AI-generated code in Nano Banana: AI Code Review and Automatic Bug Detection — I expand on the scope of the scan you'll add to your deploy gate there.

GOLDEN TIP

The most valuable insight in this article

This tip holds the article's most important takeaway.

Easter Egg

You found a hidden gem!

There's a hidden detail in this section. Want to uncover it?

Reader Reward

I've collected in one place the items you should review before moving a vibe-coded project into production — print it out on handover day and keep it on hand.

FAQ

How do you move an MVP built with vibe coding into production?

First, test real authentication and payment/webhook flows end to end, then add rollback and error tracking to the deploy process, and clean up the duplicated or dead code the AI generated. Timeline varies with integration count and data-layer state; size your schedule against how large the five steps in this guide (mapping, testing, observability, data, security gate) are in your own project.

What should I do before opening an AI-written app to real users?

You shouldn't take the AI's output straight to production without treating it like ordinary code and running it through the PR review, CI, and security-scan chain. You need to keep a human in the loop for every high-risk step (deletion, payment, deploy) and verify every capability the model claims in your own environment.

Where do you start when taking over a vibe-coded project?

With an inventory of the four most common symptoms: business logic mixed into the UI, no error handling outside the happy path, config values hardcoded into the code, and authentication added later. Don't trust the codebase until you've mapped these four.

How do you add tests to an AI-generated codebase?

The classic unit-test approach that relies on implementation detail breaks down here; starting with E2E tests that verify user behavior gives more reliable results. Test the critical flows that move money or data first, then the most-used paths; review every assertion the model generates by hand.

Is vibe coding safe — should I not use it at all?

Vibe coding is fast and useful at the prototype and idea-validation stage; the problem is taking it straight into production. What closes the gap isn't the tool, it's the handover, testing, observability, and security-gate discipline in this guide.

Conclusion

Moving an MVP built with vibe coding into production isn't about deleting the code and rewriting it — it's about making it understandable, testable, and reversible. Your roadmap should proceed in this order: first the handover map and dead-code cleanup, then E2E tests for critical flows, then observability and data-layer assurance, and finally a pre-deploy security gate.

Knowing the AI tools you use in this process better also helps — the GitHub Copilot vs Claude Code vs Cursor comparison and Codex vs Claude Code vs Gemini Code Assist articles show which tool works more reliably in which handover scenario. If you want to speed up your test suite with AI's help, check out AI-Assisted Unit Test Generation; if you're building an agent-based architecture, see Agentic AI: Tool Use and Planner Loops. If you need to rebuild your data layer, Drizzle ORM + Turso: Edge SQLite Pattern offers a concrete starting point.

Sources

Tags

#vibe coding#production#AI code generation#testing#observability#migration#security gate
Muhittin Çamdalı

Muhittin Çamdalı

Lead Mobile Engineer

Lead Mobile Engineer with 12+ years of experience. Expert in iOS, Android and cross-platform architectures with Swift, SwiftUI, Kotlin and Flutter. I build performant, user-friendly mobile apps.

iOS Development News

Weekly Swift tips, SwiftUI tricks and iOS best practices. No spam, only valuable content.

We respect your privacy. You can unsubscribe at any time.

Share