Why your vibe-coded MVP keeps breaking.
The demo worked. The launch didn't. After two dozen rescues, the failure modes in AI-generated codebases are almost always the same four— and they fail in a predictable order.
An AI coding tool will get you to a working demo faster than any team in history. That part is real, and it's not going away. The trouble starts the day the demo becomes a product — the day real users, real money, and real data show up and start leaning on code that was never built to be leaned on.
It's not that the generated code is bad. Most of it reads fine. The problem is subtler: nothing in it was ever load-bearing. Every shortcut that was invisible at ten users becomes the thing that pages you at ten thousand. Here are the four we find every time, in the order they tend to take you down.
“It's not that the code is bad. It's that nothing was ever load-bearing — until the day it had to be.”
— From two dozen MVP rescuesAuth that trusts the client.
The single most common — and most dangerous — pattern. The generated frontend hides the admin button when you're not an admin, and everyone assumes that's the security boundary. It isn't. The API behind it will happily answer the same request from anyone who knows the URL.
We find this within the first hour of almost every rescue. The fix isn't glamorous: every endpoint re-checks identity and authorization server-side, every time, with no exceptions for “internal” routes.
// ❌ the frontend hid the button. that is not authorization.
router.get("/invoices/:id", async (req, res) => {
const invoice = await db.invoices.find(req.params.id);
res.json(invoice); // anyone with the id wins
});
// ✅ re-check on the server, every request
router.get("/invoices/:id", requireUser, async (req, res) => {
const invoice = await db.invoices.find(req.params.id);
if (invoice.orgId !== req.user.orgId) return res.sendStatus(404);
res.json(invoice);
});A data layer with no indexes.
Generated schemas are almost always shaped right and tuned wrong. The columns are sensible; the indexes are missing. At demo scale every query is fast because every table fits in memory. At real scale the same query does a full table scan, and your p95 latency quietly triples every time the table doubles.
- The tell: response times that creep up week over week with no code changes.
- The fix: index every column you filter or join on, then watch the slow-query log instead of guessing.
- The trap: adding indexes blindly — each one costs you on writes, so measure first.
If a query appears in a request handler, it needs an index behind it. If it appears in a loop, it needs to not be in a loop.
Everything runs on the request.
Sending the email, resizing the image, calling the third-party API — in a vibe-coded MVP, all of it happens inline, while the user waits. It feels fine when you're the only one clicking. Under load, one slow dependency holds a connection open, the pool drains, and the whole app stops responding for reasons that look unrelated.
The shape of the fix is always the same: get the slow, failure-prone work off the request path and into a queue where it can be retried, rate-limited, and observed.
No tests, so no way to change anything.
This is the quiet one, and it's why the other three never get fixed. With no test harness, every change is a gamble. The team stops touching the scary parts of the codebase — which are, of course, exactly the parts that need touching. The MVP doesn't break because it's fragile; it breaks because nobody dares repair it.
You don't need 100% coverage. You need a thin layer of tests around the money paths — auth, payments, the three flows that matter — so that fixing failures one through three doesn't introduce failure five.
The order matters
Fix them out of order and you'll waste the rescue. Tests first, so you can change things safely. Auth second, because it's the one that ends companies. Then the data layer, then the request path. We've never once needed to deviate from that sequence — and we've never once regretted following it.
If any of this sounds like the app you're staring at right now, that's the good news: it's a known shape, and known shapes are fixable. Usually in weeks, not quarters.