The vibe-coding gap
Tools like Lovable, Bolt, Cursor, v0, and Replit have compressed the time to a working prototype from weeks to hours. A non-technical founder can describe a product and have something clickable and deployed by end of day. That is genuinely remarkable — and it has permanently changed what "building an MVP" means.
But a prototype is not a product. The gap between "this works in a demo" and "this handles 500 concurrent users without losing data" is where most AI-generated codebases quietly fall apart — not in a visible crash, but in a slow accumulation of correctness failures, security gaps, and architectural debt that compounds until the whole thing needs to be rebuilt.
This guide covers the specific steps you need to take to move an AI-generated prototype into production safely.
Step 1: Audit the architecture before touching a line of code
Before you make any changes, read the entire codebase at a high level. AI tools generate code that works locally but makes structural choices that are expensive at scale: everything in one file, no separation of concerns, API keys hardcoded in client-side components, business logic in UI layers, no input sanitisation.
Document what you have before you start changing things. The things to look for:
- Where is authentication happening and is it actually enforcing access control?
- Are there any secrets (API keys, database credentials) in the client bundle or committed to git?
- Is state managed consistently or scattered across local component state, context, and external calls?
- Does the database schema have appropriate indexes for the queries being made?
- Are there any N+1 query patterns in the data fetching layer?
- Is user-supplied input being validated and sanitised before use?
Write this down. You will make better prioritisation decisions with a written inventory than by trying to hold everything in your head while coding.
Step 2: Fix secrets and authentication first — always
This is non-negotiable before anything goes to a real production environment. AI tools frequently hardcode API keys in components, leave service role keys in client-side code, or implement authentication that has no actual authorisation layer (the user is "logged in" but nothing actually checks whether they have permission to do what they are doing).
Specifically:
- Move all secrets to server-side environment variables — never in the client bundle.
- If you are using Supabase, check whether your Row Level Security (RLS) policies are actually enabled on every table. Supabase creates tables with RLS disabled by default.
- Check that every API route validates the session before performing any action.
- Rotate any keys that have been exposed in git history, even briefly.
- Implement proper CSRF protection on any form submissions.
Step 3: Add error boundaries and observability
AI-generated code handles the happy path well. It almost never handles failures gracefully. In production, failures happen constantly: network timeouts, third-party API rate limits, malformed user input, race conditions on concurrent updates. Without proper error handling, your users see blank screens, broken states, or silent data corruption.
The minimum you need before launch:
- Error boundaries around every major UI section so a crash in one area does not blank the whole page.
- Structured error logging — Sentry, LogRocket, or Axiom all work well with Next.js.
- Meaningful error messages for users when operations fail, not just empty loading states.
- Retry logic with exponential backoff for external API calls.
- Database transaction handling on any multi-step writes so partial failures cannot leave data in an inconsistent state.
Step 4: Refactor the data layer
The data layer is where most AI-generated apps have the most structural problems. Common patterns that need to be addressed:
N+1 queries. If you fetch a list of items and then fetch related data for each item in a loop, you are making one database query per item. For 10 items that is fine. For 500 it kills your database. Replace with joins or batch queries.
Missing indexes. AI tools create schemas that are correct but not optimised. If you are querying by a field regularly — user ID on a posts table, for example — that field needs an index or your queries will do a full table scan on every request.
No pagination. Fetching all records from a table without limits means a single API call fetches your entire dataset as your product grows. Implement cursor-based or offset pagination on any endpoint that returns lists.
Cascading deletes and referential integrity. AI tools often skip foreign key constraints and cascade rules in database schemas. Check that deleting a user does not leave orphaned records that reference them.
Step 5: Harden the API surface
Every API endpoint is a surface for abuse. Before you have real users, you also have attackers probing for weaknesses. At minimum:
- Input validation with a schema library (Zod is the standard for TypeScript projects) on every endpoint.
- Rate limiting on authentication endpoints and any route that triggers expensive operations.
- HTTP security headers — Content-Security-Policy, X-Frame-Options, Strict-Transport-Security. Next.js makes this straightforward in next.config.js.
- CORS configuration that is not set to wildcard (*) in production.
- SQL injection protection — which you get for free with ORMs like Prisma, but only if you are using parameterised queries rather than string interpolation.
Step 6: Set up a real deployment pipeline
Vibe-coded apps are often deployed by clicking "deploy" in a browser UI and sharing the link. That is fine for a prototype. For production you need:
- A staging environment that mirrors production so you can test changes before they affect real users.
- CI/CD that runs type-checking and at least a basic test suite before deployment.
- Database migration tooling — changes to the schema need to be versioned and applied in a controlled way, not by editing tables directly.
- Automated backups of your production database on a schedule.
- A rollback plan for deployments — Vercel makes this straightforward, but you need to actually know how to execute it under pressure.
Step 7: Know what to keep and what to rebuild
Not every part of an AI-generated codebase needs to be rewritten. The UI layer — components, layouts, styles — is often the most faithful part, because AI tools are very good at translating visual descriptions into working React components. Business logic and data layer code is where the structural problems concentrate.
A useful heuristic: if the code is purely presentational and the correctness is obvious from reading it, keep it. If the code makes decisions, stores data, enforces rules, or interacts with external services, audit it carefully and rewrite the parts that do not hold up.
The goal is not a clean codebase for its own sake. The goal is a system that handles failure gracefully, scales under load, keeps user data safe, and can be maintained by a human engineer without needing to understand how the AI thought about the problem.
How long does this take?
For a typical Lovable or Bolt prototype — a single product with a database, authentication, and a handful of core features — the productionisation work takes between 4 and 10 weeks with one or two experienced engineers. That sounds like a lot relative to the hours it took to build the prototype, but it is fast relative to building the same system from scratch.
The alternative — shipping the prototype directly and fixing problems as they appear in production — is almost always slower and more expensive. Security incidents, data loss events, and reliability failures all take significant time to recover from, and they happen at the worst possible moment: when you are trying to grow.
Vibe coding is a legitimate and powerful tool for founders. The fastest path to a production product is often: vibe code the prototype, then bring in engineers to harden it. Not rebuild from scratch — harden what you have.