The real problem isn’t that “it works”

Vibe coding has made building software accessible: you describe, the AI generates, it runs. That’s not where the problem is. It shows up six months later (sometimes much sooner), when the application buckles under load, a vulnerability lies dormant in a form, or adding one feature takes three times longer than planned because nobody really understands the code.

AI-generated code “seems to work” when you try it on your own. It cracks elsewhere: under a rush of users (load), when several people act at the same instant (two customers booking the last seat within the same second), and the day you have to evolve it. These are precisely the dimensions AI optimises the least. When someone hands us an AI-coded application to shore up, here are the points we run through, in order.

1. Load handling and performance

  • A query on every keystroke. AI happily wires up a search that hits the database on every key. Instead, we filter a list preloaded in memory, or we debounce; server-side search goes through a parameterised URL (good for caching and SEO), not a call per character.
  • No caching. We add ISR (revalidate) with targeted revalidation when content changes, instead of serving stale data for hours. And we hunt down N+1 queries: one aggregated query rather than a hundred small ones.
  • In-memory rate limiting. On serverless, each instance keeps its own in-memory counter: useless against a distributed brute-force attack. You need shared storage, put in place before you open to the public (auth, forms, APIs).

2. Logic and state

  • Misplaced useEffect. Updating state in an effect to derive another triggers cascading re-renders. We derive during render; for external state (media query, scroll, storage), we use useSyncExternalStore. And every effect cleans up in its return, otherwise you get a memory leak.
  • Race conditions. Reading then writing on the application side (stock, booking, payment) allows overbooking. We do an atomic decrement in the database, or take a lock. Payment callbacks are idempotent and verify the signature before any side effect: the server-to-server notification can arrive several times.
  • The happy path only. AI handles the nominal case and forgets the rest. We add graceful degradation (an optional section that fails doesn’t break the page), and we handle empty states, nulls and timeouts explicitly.

3. System resources

  • Memory leaks. Listeners, timers, observers, WebGL contexts, subscriptions: anything that opens has to close. Invisible in dev, they pile up over a long session, especially on mobile.
  • Connection pooling. A new PrismaClient() per request saturates the Postgres pool on serverless. We keep a single instance and a pooled connection, with a timeout.

4. Design and security

This is where “AI doesn’t grasp the critical implications”.

  • Consistency with the architecture. Code that ignores the project’s conventions (data access layer, shared predicates) creates refactoring debt. We enforce the repo’s patterns: a single source of truth, no duplicated logic.
  • SQL injection: parameterised queries, never concatenating input.
  • XSS: we sanitise any rich HTML before injecting it; we never render raw user HTML.
  • Secrets: in environment variables, never in plain text in the code, the logs or a command output.
  • Auth: a server-side check that can’t be bypassed at the endpoint, not just in the interface. A password-strength meter is UX (bypassable), not a security filter.
  • Headers: X-Frame set to DENY, nosniff, Referrer-Policy, an enforced CSP. Hardening gets documented: on a static site, forcing a nonce imposes dynamic rendering and costs you performance; if the XSS surface is already closed, that’s a deliberate trade-off, not negligence.

5. Discipline, not just the code

A serious audit judges not just the lines, but the method.

  • Verify against reality, not memory. Before claiming “it’s done”, we see it for ourselves: the live URL, the database, the Git state.
  • Never blindly run a destructive operation in production on the strength of an assumed state.
  • Strict types (no any on models and APIs), tests on critical routes (payment, sign-up), plus accessibility, performance and metadata checks before every commit.
  • Track conscious debt. The deliberate choices, written down somewhere, so they don’t turn into a surprise six months later.

Want to run this pass on your own code right now? Here’s the checklist condensed into a single file, ready to hand to your AI assistant.

Paste into your AI

Express audit prompt

# Express audit of an AI-coded app: a checklist to hand your assistant

Paste this file into your AI assistant (Claude, Cursor, ChatGPT…) with access to
your code. Ask it to work through each point in order, and for each one to say:
OK, fragile, or missing, with the file concerned and the proposed fix.

## Your role
You are a senior engineer tasked with auditing an AI-generated application before
consolidating its foundations. You do not settle for “does it run”: you look for
where it breaks under load, under concurrency, and under maintenance. Be concrete,
cite the code, oversell nothing.

## 1. Load handling
- [ ] No query on every keystroke: an in-memory filtered list or a `debounce`; server search goes through a parameterized URL.
- [ ] Caching in place (ISR / `revalidate`) with targeted revalidation; no N+1 queries.
- [ ] Rate limiting on SHARED storage, not a per-instance counter (ineffective on serverless), set up before going public.

## 2. Logic and state
- [ ] No derived state inside a `useEffect`; every effect cleans up in its `return`.
- [ ] No race condition: atomic decrement or a lock on stock, booking, payment.
- [ ] Payment callbacks are idempotent, signature verified before any side effect.
- [ ] Non-nominal cases are handled: empty, null, timeout, graceful degradation.

## 3. System resources
- [ ] Everything that opens gets closed: listeners, timers, observers, contexts, subscriptions.
- [ ] A single database-client instance, pooled connection, not one client per request.

## 4. Design and security
- [ ] Consistency with the project architecture; no duplicated logic, one source of truth.
- [ ] Parameterized queries, never input concatenation; user HTML sanitized before injection.
- [ ] Secrets in environment variables, never in plain text in the code, the logs, or a command output.
- [ ] Access control enforced server-side, non-bypassable, at every endpoint, not just in the interface.
- [ ] Security headers: `X-Frame-Options: DENY`, `nosniff`, `Referrer-Policy`, an enforced CSP.

## 5. Discipline
- [ ] Verified against reality (live URL, database, Git state), not against an assumption.
- [ ] Strict types (no `any` on models and APIs); tests on the critical routes (payment, sign-up).
- [ ] No destructive operation run blind in production.
- [ ] Conscious debt is written down somewhere, so it isn’t rediscovered six months later.

---

Checklist by BetterNotCode, a Bubble and no-code agency doing AI-assisted development.
Full article: betternotcode.com/en/blog/auditing-ai-generated-code
Download the .md

Shoring up isn’t rewriting everything

To put it without jargon: we let AI build an application that works, but we never count on it to spot where things can break. Anticipating the breaking points is our job.

And rewriting everything is rarely the answer. We start with the load-bearing points: security, load handling, consistency of the architecture. We keep what holds. Rewriting everything is expensive and often replays the same debt elsewhere.

That’s exactly our work: start from what AI produced, keep its speed, and put back foundations you can actually build on. If your vibe-coded application is starting to worry you, it’s not too late to talk it over.