Back to Blog
Vibe Coding

5 Specific Patterns Where Bolt and Lovable Fail in Production — with the Production-Lift Fix

Real anti-patterns from Bolt/Lovable exports that fail when paying users arrive: app-layer tenancy, mock auth, missing webhook verification, generic error handlers, no a11y. Each with the production fix.

Alvi Lika11 min read

TL;DR. AI app builders (Bolt.new, Lovable, v0, Cursor, Replit Agent) generate prototypes optimized for the prompt-to-preview loop. Five specific patterns recur across every Bolt/Lovable codebase I've audited for Soatech Production Lift engagements: (1) application-layer tenancy with no database enforcement, (2) auth that handles the happy path only, (3) third-party integrations without webhook signature verification, (4) generic try/catch error handling, (5) zero accessibility coverage. Each pattern is the failure mode that surfaces when paying users arrive. Each has a verifiable production fix derived from the Wintura.ai build.

Why this matters (the verified market gap)

Per Chrono Innovation's Feb 2026 cost breakdown, the rebuild-to-production cost for an AI-generated prototype runs $25K-$75K with AI-supervised builds and $30K-$150K with traditional freelancer engagements. Per AppyCodes' "Lovable / Bolt to Production: The Real Cost & Timeline" study covering 20 engagements (Apr 2026, updated May 2026), the average AI prototype-to-production engagement rewrites 59% of the original code — rising to 76% for complex projects (marketplaces, fintech, multi-tenant SaaS) and 27% for simple internal tools.

This isn't a critique of Bolt or Lovable. They're excellent for the prototyping phase — see my own Claude pair-programming experience for what AI excels at. The point is: the prototype is not the production app, and the gap has five recurring shapes.

Pattern 1: Application-layer tenancy with no database enforcement

The most common failure mode and the most catastrophic when it fires.

What Bolt/Lovable generates

// Generated by AI prototype builder
export async function listProposals(userId: string) {
  return db.query('SELECT * FROM proposals WHERE user_id = $1', [userId]);
}

Looks correct. Is correct in the happy path. The problem: every other query function in the codebase needs to remember to pass userId. The day a junior developer (or you, three months in) writes db.query('SELECT * FROM proposals') without the WHERE user_id clause, you have a cross-tenant data leak. The application has no defense.

The production fix (from Wintura)

Per the Wintura.ai case study multi-tenancy section:

// /lib/db/proposals.ts — Wintura production pattern
// TypeScript strict mode + required tenantId param at the function signature
// prevents anyone from writing a query without tenancy enforcement.
export async function listProposals(tenantId: string): Promise<Proposal[]> {
  return db.query(
    'SELECT * FROM proposals WHERE tenant_id = $1',
    [tenantId],
  );
}

Plus Row-Level Security policies on the proposals table at the database layer:

ALTER TABLE proposals ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON proposals
  USING (tenant_id = current_setting('app.current_tenant_id')::uuid);

Now even if someone bypasses /lib/db and writes raw SQL, the database refuses to return rows for the wrong tenant.

This is the first thing Soatech's Production Lift installs.

Pattern 2: Auth that handles the happy path only

What Bolt/Lovable generates

A working sign-up + sign-in flow. The user can register, log in, log out. Looks complete. Ship it.

What's missing for production

  • Password reset that resists enumeration: a generated reset endpoint typically returns "Email not found" if the user doesn't exist. That's an enumeration attack vector — attackers can iterate emails to find valid accounts. Production endpoints respond identically whether the email exists or not.
  • Magic-link tokens that are single-use and expiry-enforced: AI-generated tokens often work multiple times or don't expire. Either is a serious vulnerability.
  • Session cookie locked to the apex domain: subdomain-wildcard cookies leak across subdomains. Apex-locked cookies don't.
  • CSRF tokens that rotate on privilege change: when a user upgrades role or switches tenant, the CSRF token must rotate to prevent session fixation.
  • Rate limiting per IP per endpoint: brute-force prevention.

The production fix (from Wintura)

NextAuth v5 beta.30 configured per the Wintura.ai case study auth section. Cookie domain locked to apex. Password reset endpoint returns identical 200 response regardless of email existence (matched response timing to within 50ms). Magic-link tokens are 32-byte random, single-use, 15-minute expiry. CSRF tokens rotate on role change. Rate limiting via Vercel Edge Middleware at 10 requests per IP per minute per auth endpoint.

I'd estimate this is the second thing Soatech audits during Production Lift, and it's almost always the second thing that needs fixing.

Pattern 3: Third-party integrations without webhook signature verification

What Bolt/Lovable generates

A Stripe webhook handler that processes incoming events:

// Generated prototype code
export async function POST(req: Request) {
  const event = await req.json();
  if (event.type === 'checkout.session.completed') {
    await processOrder(event.data.object);
  }
  return Response.json({ received: true });
}

This handler will happily process any HTTP POST that arrives at the endpoint with a Stripe-shaped JSON body. An attacker who finds the URL can fire fake checkout.session.completed events all day and the system will create orders for them.

The production fix (from Wintura)

// Production pattern — verify signature, use idempotency, structured errors
import Stripe from 'stripe';

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET!;

export async function POST(req: Request) {
  const sig = req.headers.get('stripe-signature');
  if (!sig) {
    return Response.json({ error: 'Missing signature' }, { status: 400 });
  }

  const body = await req.text();
  let event: Stripe.Event;
  try {
    event = stripe.webhooks.constructEvent(body, sig, webhookSecret);
  } catch {
    return Response.json({ error: 'Invalid signature' }, { status: 400 });
  }

  // Idempotency — process each event exactly once
  if (await alreadyProcessed(event.id)) {
    return Response.json({ received: true, deduped: true });
  }

  // ... handle event with structured error types per failure mode
}

Same pattern for HubSpot webhooks, Resend bounce notifications, Cloudflare R2 upload-complete events. Every webhook endpoint verifies signature + idempotency + structured error handling. This is the third Production Lift fix.

Pattern 4: Generic try/catch error handling

What Bolt/Lovable generates

try {
  const result = await externalApi.call();
  return Response.json({ success: true, result });
} catch (error) {
  console.error(error);
  return Response.json({ error: 'Something went wrong' }, { status: 500 });
}

This is wrong for production for three reasons:

  1. All failures look the same to the client — the UI can't show "rate limited, try in 30s" vs "service down, try later."
  2. No retry policy — rate-limited requests should retry with backoff; service-down errors should not.
  3. No observabilityconsole.error doesn't surface to your monitoring dashboard.

The production fix (from Wintura)

// Structured error types per failure mode
type ExternalApiError =
  | { type: 'rate_limited'; retryAfterMs: number }
  | { type: 'service_unavailable'; retryAfterMs: number }
  | { type: 'invalid_request'; field?: string }
  | { type: 'auth_failed' }
  | { type: 'unknown'; message: string };

try {
  const result = await externalApi.call();
  return Response.json({ success: true, result });
} catch (error) {
  const classified = classifyError(error);
  posthog.capture('external_api_error', { type: classified.type });
  
  switch (classified.type) {
    case 'rate_limited':
      return Response.json(classified, { 
        status: 429, 
        headers: { 'Retry-After': String(Math.ceil(classified.retryAfterMs / 1000)) },
      });
    case 'service_unavailable':
      // Queue for retry, return optimistic response
      await retryQueue.enqueue({ ...originalRequest, attemptAt: Date.now() + classified.retryAfterMs });
      return Response.json({ queued: true, retryAt: Date.now() + classified.retryAfterMs });
    case 'invalid_request':
      return Response.json(classified, { status: 400 });
    case 'auth_failed':
      // Alert the team — auth failures with third parties shouldn't happen in steady state
      await alerting.page('Third-party auth failure', { service: 'externalApi' });
      return Response.json({ error: 'Service authentication issue' }, { status: 503 });
    case 'unknown':
      posthog.capture('unclassified_error', { message: classified.message });
      return Response.json({ error: 'Unexpected error' }, { status: 500 });
  }
}

Verbose, yes. Production code is verbose. Per the Wintura.ai case study, the proposal-send flow alone has 12 distinct failure modes, each with its own classification and recovery policy. This is the fourth Production Lift fix.

Pattern 5: Zero accessibility coverage

What Bolt/Lovable generates

A working UI. The form elements are inputs. The buttons are buttons. The visual design is clean. axe-core is not run anywhere.

What's missing for production

  • Keyboard navigation through every flow (Tab, Shift+Tab, Enter, Escape, Arrow keys for menus)
  • ARIA labels on icon-only buttons (sr-only text for screen readers)
  • Focus management when modals open/close
  • Color contrast ratios ≥ 4.5:1 for body text, ≥ 3:1 for large text (WCAG 2.1 AA)
  • Form field labels properly associated (not just visual labels — <label for=...> or aria-labelledby)
  • Skip-to-content links so keyboard users don't tab through nav on every page
  • Reduced-motion respect — disable parallax + transform animations when prefers-reduced-motion: reduce

The production fix (from Wintura)

axe-core 4.11.3 baked into Playwright tests. Per the Wintura.ai case study test matrix, 24 e2e files include dedicated audit-a11y projects: public + private + onboarding routes × mobile + tablet + desktop. Every PR runs the full matrix. Conformance baseline: WCAG 2.1 AA. Verified accessibility is a Healthcare Success E-E-A-T 2026 ranking factor for B2B procurement.

This is the fifth Production Lift fix.

The pattern under the patterns

All five failures share one cause: AI app builders optimize for the prompt-to-preview loop. The prompt-to-preview loop rewards code that runs. Production rewards code that fails safely. These are different optimization targets.

Bolt and Lovable are excellent for the loop they target. The Soatech Production Lift closes the gap to the loop production targets. €3,500 / 1 week / fixed price.

If you're on a Bolt or Lovable prototype right now and any of the five patterns above sound familiar, the Lift is the cheapest path to production. See the Bolt-vs-Production-Lift and Lovable-vs-Production-Lift side-by-sides for the full feature comparison.

Frequently asked questions

How do I know if my Bolt or Lovable prototype has these problems?

Self-check: (1) Can you write a database query in your codebase without specifying a tenant ID, and would it return data? (2) Does your password-reset endpoint behave identically whether the email exists or not? (3) Does your Stripe webhook handler call stripe.webhooks.constructEvent? (4) Do your error handlers have more than one catch (error) block per type of failure? (5) Have you ever run axe-core against your app? Any "no" answers indicate this Production Lift work hasn't been done yet.

How much does it cost to fix this manually vs via Soatech Production Lift?

Per AppyCodes' 20-engagement dataset, the median Lovable/Bolt-to-production engagement costs $10,000 over 35 days. Simple internal tools average $3,750 over 13 days; complex marketplaces average $22,300 over 65 days. Soatech's Production Lift targets the simple-to-medium complexity tier at €3,500 fixed, 1 week — for complex multi-tenant fintech/marketplace prototypes, scope the MVP Sprint (from €8.5K, under 30 days) at the Blueprint phase. The difference vs hourly agencies: AI accelerates the rote work, the Architect (per Wintura experience) knows exactly what needs auditing, and the fixed-price commitment absorbs scope variance.

Are there Bolt/Lovable patterns that DON'T need fixing?

The UI itself is usually fine. Tailwind classes, layout structure, shadcn/ui components — these ship straight to production. The fixes are in the data layer, auth layer, integration layer, error handling layer, and accessibility layer. Visual design typically survives the Lift unchanged.

What if my prototype isn't in Bolt or Lovable — it's in v0 or Cursor or Replit Agent?

Same patterns apply. v0 generates better-typed code than Bolt but the same five gaps exist. Cursor produces whatever you prompt — if you didn't prompt explicitly for tenancy enforcement, you don't have it. Replit Agent has its own quirks but the same production-hardening gap. The Production Lift is framework-agnostic.

Can I just learn this myself and fix it?

Yes — and many founders do. The patterns are documented in OWASP, the Stripe webhook docs, the Next.js production checklist, and my Wintura case study. Learning takes ~2-6 weeks of focused study + 1-3 weeks of implementation. The Lift trades €3,500 for 1 week of expert work instead.

What if I don't have a prototype yet?

Start with the Technical Blueprint (€2,500, 5 days) — architecture sprint. Then either build it yourself with the plan in hand, or commit to an MVP Sprint (from €8.5K, under 30 days) where the Blueprint converts toward Sprint One. See the Wintura Playbook for the day-by-day breakdown.

Does Soatech handle one-off security audits without a full Lift?

For a pure security audit without the rest of the production hardening, contact via #booking — that's a custom scope. Most prototype audits surface enough work that the Lift is more cost-effective than a la carte audit + remediation.

What's the deliverable timeline for a Production Lift on my Bolt app?

5 working days (Mon-Fri or equivalent). Day 1-2: audit + architecture mapping. Day 3-4: implementation across all 5 patterns (multi-tenant, auth, integrations, errors, a11y). Day 5: deployment + handover. Full source ownership, no platform lock-in. Optional 30-day post-ship support included.

vibe-codingBoltLovableproduction liftmulti-tenantStripe webhooksNextAuth

Ready to build something great?

Architect-led, AI-accelerated. Let's turn your idea into a shipped product.

Book a 30-min Blueprint call