Vibe coding is everywhere right now. Describe a feature in English, let AI generate the code, run it, ship it. Never open a file. Andrej Karpathy coined the term, CEOs are predicting 90% of code will be AI generated within months, and my Twitter feed is full of people shipping SaaS products in under two weeks without reading a single line of their own codebase.
I was curious. Not about the hype. About the code.
So I reviewed six codebases that were built primarily or entirely through vibe coding. Three were early stage startup MVPs. One was a freelancer's client project. Two were side projects that their creators were planning to scale into real products.
Here is what I actually found.
The Good Parts Were Genuinely Impressive
I want to be fair first. The scaffolding quality was surprisingly solid across all six projects. Component structure in the React apps was reasonable. Naming conventions were consistent. The generated code followed modern patterns like custom hooks for data fetching, proper separation of API calls, and clean JSX without excessive nesting.
One of the MVPs had a complete Stripe integration with subscription management, webhook handling, and customer portal redirects. It worked. The implementation followed Stripe's recommended patterns. A mid level developer would have written something very similar.
For CRUD operations, form handling, authentication flows, and standard UI components, the vibe coded output was production quality. Not just "works in a demo" quality. Actually deployable, reasonable code.
If your entire application is CRUD with auth and a payment form, vibe coding genuinely works.
The 80/20 Pattern Showed Up in Every Single Project
Every codebase hit the same wall. The individual features worked. The interactions between features did not.
Here is a concrete example from one of the startup MVPs. It was a marketplace app. Sellers could list products. Buyers could purchase. Both features worked independently. But when a seller edited a product that was currently in someone's cart, nothing happened. The cart still showed the old price. If the seller deleted the product, the cart broke entirely with an unhandled null reference because the product lookup returned undefined and nobody checked for it.
The AI generated the product listing feature. Then the AI generated the cart feature. Each feature was self contained and correct in isolation. But nobody, not the AI and not the developer, thought about the state synchronization between them.
This pattern repeated across all six codebases.
A scheduling app where booking a recurring event worked fine, but canceling one instance of a recurring series deleted all future instances because the data model did not distinguish between the series and individual occurrences.
A dashboard where each widget fetched its own data independently, resulting in 14 parallel API calls on page load, six of which hit the same endpoint with slightly different parameters.
A multi step form where navigating back to step 2 after reaching step 4 cleared steps 3 and 4 because the state management treated navigation as reinitialization.
These are not exotic bugs. These are the normal complexity of real applications. And vibe coding consistently missed them because the AI generates features one prompt at a time without a mental model of the whole system.
Security Was Consistently the Weakest Layer
This is the part that concerned me most.
Four of the six projects had API routes that checked if a user was authenticated but did not check if that user was authorized to access the specific resource. A logged in user could hit /api/orders/[id] with any order ID and get back someone else's order data. Classic IDOR vulnerability. The AI generated auth middleware that verified the JWT but the route handlers trusted any authenticated request.
Two projects stored sensitive configuration in client accessible environment variables. One had NEXT_PUBLIC_STRIPE_SECRET_KEY in the .env file. The developer told me the AI suggested that variable name and they did not notice the NEXT_PUBLIC_ prefix makes it bundled into client JavaScript.
Three projects had no rate limiting on any endpoint. One had a password reset flow that would send unlimited emails to any address, which is both a security issue and a potential cost explosion with a transactional email provider.
None of the six had CSRF protection beyond what the framework provided by default. Two were using cookie based auth without SameSite attributes set explicitly.
The pattern was consistent. The AI generated code that handled the happy path of security (user logs in, gets a token, token is checked) but missed the adversarial paths (user manipulates requests, user accesses resources they should not, user automates abuse).
Error Handling Was Copy Pasted Everywhere
Five of the six codebases had the same error handling pattern in nearly every async function:
try {
// actual logic
} catch (error) {
console.error(error)
return { error: "Something went wrong" }
}
Every error returned the same generic message. No distinction between validation errors, auth errors, not found errors, rate limit errors, or server errors. No structured error types. No error boundaries in the React components beyond the default Next.js error page.
When I asked one developer what happens when the payment provider returns a card declined error, they did not know. The answer was that the catch block swallowed it and the user saw "Something went wrong." No retry option. No specific message. No logging that would help debug which step of the payment flow failed.
This is what happens when code is generated one function at a time without an error handling strategy. Each function gets the same generic try catch because the AI defaults to "safe" error handling. But generic error handling is not safe. It is invisible failure.
What Actually Works as a Workflow
After reviewing all six, I do not think vibe coding is useless. But I think the "never read the code" version is dangerous for anything beyond prototypes.
The workflow that produced the best results in the projects I reviewed was what I would call guided vibe coding. The developer described features to the AI but also:
Reviewed every database schema change manually. Made architectural decisions (state management approach, API structure, auth strategy) before prompting. Wrote security critical paths themselves or did line by line review of AI output. Had a testing strategy where AI generated tests were run after every feature.
The two projects that used this hybrid approach had significantly fewer of the interaction bugs and security issues. They were not perfect. But they were in a different league from the four that were pure vibe coded.
The Practical Takeaway
If you are vibe coding, here is the minimum checklist that would have caught 80% of the issues I found:
After every feature, ask yourself one question: what other features does this interact with, and did I test those interactions? The AI will not ask this for you.
Review every auth check manually. Not just "is there middleware." Read the actual route handler and verify it checks that this specific user can access this specific resource.
Search your codebase for NEXT_PUBLIC_ or equivalent. Make sure nothing secret is in there. This takes 30 seconds.
Grep for generic catch blocks. If every error returns the same message, your users and your debugging self will suffer.
Run a single API endpoint test with a different user's ID. If it returns data, you have an IDOR vulnerability. Fix it before you ship.
Vibe coding is a multiplier. But it multiplies whatever you bring to it. Deep understanding of systems produces great code faster. No understanding produces broken code faster.
The code was never the hard part.
I write about JavaScript, AI tools, and the developer job market at jsgurujobs.com
Top comments (0)