Vibe Coding Is Fine. Just Know When to Call Us.
A client came to us last week with a working app. He had built it himself — no engineering background, no team, just an idea and a few weekends with an AI coding assistant. The product worked. Users were signing up. He had validated the idea.
Then he opened the codebase and asked us to take it from here.
What we found was what you’d expect, and I want to be clear about this upfront: none of it surprised us, and none of it was his fault.
No database migrations. Schema changes were being made by editing the schema file and praying. No async anywhere — every operation blocking the event loop, the app freezing under any real concurrency. No error handlers. Errors were either swallowed silently or crashed the whole process. Zero observability. No logs, no metrics, no tracing. When something broke in production, the debugging strategy was “refresh and hope.”
This is the part of the industry discourse I find tiring. Engineers look at a codebase like this and sneer. They write LinkedIn posts about “the vibe coding apocalypse.” They act as if the founder did something wrong by shipping software with an AI instead of hiring them first.
He didn’t do anything wrong. He did something right.
What vibe coding is actually for
The hardest part of building a software business is not the software. It’s finding out whether anyone wants the thing you’re building. Most products fail not because the code was bad but because the idea was wrong — nobody needed it, nobody would pay for it, the market didn’t exist the way the founder imagined.
Traditional software development treats this risk backwards. You hire engineers, spend months building a “proper” system with migrations and async and observability and tests, launch it, and then discover nobody wants it. You’ve burned six months and a lot of money to learn something you could have learned in two weeks.
Vibe coding inverts this. You build the crappiest possible version of the thing, put it in front of real users, and let reality tell you whether to continue. The code is disposable because the idea might be disposable. Spending engineering rigor on an unvalidated idea is like hiring an architect to design a house before you’ve decided whether you want to live in the city or the countryside.
Our client did exactly this. He spent almost nothing. He shipped in weeks. He got real users. He learned the idea worked. And then he came to us, with revenue signals and a validated hypothesis, to build the real thing.
This is not a failure mode. This is the new correct workflow.
What the handoff actually looks like
Here is what refactoring a vibe-coded app actually involves, because I think the “just rewrite it” crowd underestimates the work and the “leave it alone” crowd underestimates the risk.
Migrations first. The very first thing we do is freeze the schema and introduce a migration tool. Before any feature work, before any refactor, before anything. Because the moment you have real users, you have real data, and the moment you have real data, “edit the schema and pray” becomes “edit the schema and lose customers.” We set up a migration framework, snapshot the current schema as the baseline, and from that point forward every schema change is a reviewed, versioned, reversible artifact.
Async and queues. Most vibe-coded apps do expensive work synchronously in the request handler — sending emails, calling third-party APIs, processing uploads. This works at ten users and collapses at a hundred. We identify every blocking operation, move it to a background queue, and add retries with backoff. This is usually the single highest-impact change for real-world stability.
And this is where I want to stop and say something about Node specifically, because almost everything AI assistants generate these days is Node, and almost nobody building with them seems to know what Node actually is.
Node runs your code on a single thread. One. That means every request your server handles shares one CPU core’s worth of JavaScript execution time. If one request does something expensive — a big JSON parse, a tight loop, a synchronous file read, a badly-awaited database call — every other request waits. Not a little. Completely. Your app freezes for everyone until that one operation finishes.
This is not a flaw. It’s a design choice, and it’s a good one for I/O-heavy work where most of your time is spent waiting on databases and network calls. But it means Node demands a specific discipline: never block the event loop, push anything heavy to a background worker or a separate process, and treat every synchronous operation as a potential production outage.
AI assistants do not know this. They generate Node code that parses huge payloads synchronously, runs CPU-bound work inline, reads files with the blocking API because it’s shorter, and chains ten awaits where one Promise.all would do. It works on the developer’s laptop with one user. It dies in production at fifty.
The fix is not to rewrite everything in Go or Rust, which is the other tedious take you see online. Node is fine. Node runs half the internet. The fix is to respect what Node is: a single-threaded I/O machine that punishes you the moment you forget it. Queues for heavy work. Streams for large payloads. Worker threads for CPU-bound tasks. Circuit breakers on slow dependencies. These are not exotic patterns. They are the baseline for running Node in production, and they are exactly what the AI-generated starter code leaves out.
Error handling and the observability layer. You can’t fix what you can’t see. Before we even think about rewriting features, we add structured logging, error tracking, and basic metrics. Suddenly the mystery crashes have stack traces. The “it’s slow sometimes” complaints have percentiles. The codebase goes from a black box to something you can reason about.
Then, and only then, the refactor. With visibility in place, the actual refactoring becomes boring. You see what’s slow, you fix what’s slow. You see what errors, you fix what errors. You don’t rewrite things that work. You don’t throw away the parts the AI got right — and a surprising amount of it is right, because the AI has seen more CRUD endpoints than any human engineer ever will.
Why we charge for the second half
The honest economics of this: our client spent a few hundred dollars getting to product-market validation. He will spend considerably more with us to make the system production-grade. And this is correct. This is how it should work.
The mistake is thinking these are the same job. They are not. The first job is discovery — figuring out what to build. The second job is engineering — building it to last. An AI coding assistant is extraordinary at the first job and currently inadequate at the second. A good engineering team is extraordinary at the second job and, frankly, overkill for the first.
Use each for what it’s for.
What I actually want to say to founders
If you are a non-technical founder sitting on an idea, do not wait until you can afford to hire an engineering team. Do not spend a year trying to find a technical co-founder. Open Claude or Cursor or whatever tool you prefer, and build the ugliest possible version of your idea this weekend. Ship it. Put it in front of people. Learn.
Then, when it works — when you have users, when you have revenue, when you have the signal that says this is real — come find an engineering team. Not to throw away what you built, but to carry it forward into the stage where the rules change.
Vibe coding is fine. Knowing when it stops being enough is the whole skill.

Ahmed essyad
the owner of this space
A nerd? Yeah, the typical kind—nah, not really.
View all articles by Ahmed essyad→Comments
If this resonated
I write essays like this monthly.