Engineering

How to Refactor Vibe-Coded Apps for Production

Sara O'Neill··14 min read

In the past year we've audited more than 40 codebases built primarily with AI-first tooling like Lovable, Cursor, v0, Bolt, and Windsurf. The patterns repeat. This is our field guide to taking one of these codebases from 'cool demo with real users' to 'production business.' It is opinionated, and it comes from our scars.

The first thing we do on any audit is enumerate the attack surface. What routes exist? What data can a signed-out user reach? What can a signed-in user reach that they shouldn't? What secrets ship to the browser bundle? What edge functions can be invoked without authentication? You will find things. On the last 40 audits, the average codebase had 6 findings we rated as critical or high.

Row-level security is where the majority of critical findings live. The pattern we see over and over: a table has RLS enabled, but the policy is USING (true), which permits anyone to read anything. Or the table has no policies at all and grants to authenticated, which means every signed-in user has full access. The fix is boring: for every table, write the policy as if the caller is malicious.

The second common failure is secrets in the client bundle. If your Stripe secret key, your OpenAI key, or a service-role Supabase key ends up in code that runs in the browser, the entire app is compromised. AI codegen tools sometimes generate 'call the API directly from the frontend' patterns that assume you'll fix the auth later. You need to fix it before the first real user shows up.

The third common failure is missing input validation. AI-generated forms tend to look great and pass zero server-side validation. That's fine until someone sends a 5MB message body, a SQL-injecting name field, or a script tag that lands in your admin dashboard.

Once the security gaps are closed, we look at architectural smells: N+1 queries, missing indexes, missing pagination, blocking work on the request path, and lack of any observability. These are less scary but they will eat your runway if they aren't addressed before the userbase grows.

Finally, we set up the boring stuff: CI, tests on the critical paths, error monitoring, and a runbook. The goal is not to freeze the app. The goal is to give the team the ability to keep shipping fast without losing sleep about what will break in production. Every refactor we do is designed to preserve the founders' velocity — because that velocity is what got them to product-market fit in the first place.

If you're staring at a codebase that fits this description, do the audit yourself first. Enumerate your routes. Read every RLS policy out loud. Grep your client bundle for anything that looks like a key. You will be surprised. Then decide whether you want a partner to help.