•
#AI#Engineering#Security#Production#SaaS

The Production Checklist for AI & Vibe-Coded Apps: From Prompt to Bulletproof SaaS

Vibe coding lets you build apps in hours. But taking them to production without security leaks, runaway bills, or broken states requires engineering discipline. Here is your pre-flight checklist.

"I just see stuff, say stuff, run stuff, copy-paste stuff, and it mostly works."
— Andrej Karpathy on Vibe Coding

Vibe coding is the superpower of the modern builder. With tools like Cursor, Claude 3.7 Sonnet, Windsurf, Lovable, v0, Bolt, and Antigravity, ideas that once required a four-person engineering sprint can now materialize in an afternoon fueled by coffee and conversational prompts.

The catch?
LLMs are world-class optimizers for making code run on localhost:3000. They are notoriously indifferent to:

  • Whether your database allows any anonymous visitor to delete tables.
  • Whether your frontend bundle leaks your secret API keys to the browser.
  • What happens to your OpenAI bill when an automated bot hits your unbounded API endpoint 10,000 times in 3 minutes.

If you vibe-coded your app and are preparing to drop the link on Product Hunt, X, or Reddit, pause and run through this pre-flight checklist first.


The Mental Shift: "Prompt & Pray" vs. "Prompt & Verify"

DimensionThe Vibe-Coded DefaultThe Production Standard
Security"It works when I click the buttons."Hardened against forged IDs, privilege escalation, and token leaks.
Architecture2,000-line single-file components with 18 useEffect hooks.Modularity, clear data boundaries, and clean separation of concerns.
Dependencies4 different icon and date libraries because each prompt imported a different one.Curated, vetted, minimal dependency footprint.
CostsUnbounded AI model calls and open database connections.Hard rate limits, token quotas, caching, and spend circuit-breakers.
Error HandlingUnhandled promise rejections triggering a blank white screen.Graceful fallbacks, toast notifications, and telemetry tracking.

1. Security & Auth: The Non-Negotiables

LLMs are eager to please. If an API call fails with a CORS error or an authentication issue, the model’s instinctive solution is often to relax permissions or pass secrets into the client rather than architecting a secure proxy.

1.1 Audit Secret Leakage in Frontend Bundles

  • Check client-side environment variables: Verify that only public identifiers are prefixed with NEXT_PUBLIC_, VITE_, NUXT_PUBLIC_, or EXPO_PUBLIC_.
  • Search bundle output: Run a regex search for sensitive patterns (sk-, ghp_, whsec_, secret, private_key) across your built JavaScript bundles.
  • Verify .gitignore: Ensure .env, .env.local, .env.production, and credential files are untracked by Git. Run git status --ignored to confirm.

Warning: If you put an OpenAI, Anthropic, or Stripe secret key in a client-side component, it will be scraped within minutes of going live. Always proxy third-party API calls through your backend or serverless route handlers.

1.2 Database & Row-Level Security (RLS)

If you built with Supabase, Firebase, or Pocketbase directly from the client:

  • Enable RLS on EVERY database table: A created table in Postgres/Supabase without RLS is publicly queryable and writable by default via the anon key.
  • Audit read/write policies: Verify that users can only read and mutate rows where auth.uid() = user_id.
  • Prevent IDOR (Insecure Direct Object Reference): Check backend endpoints taking userId or id as a parameter. Does the server take the ID from the trusted JWT/session token or blindly trust what the client passed in the request body?
-- ❌ Dangerous: Table open to the world
ALTER TABLE projects DISABLE ROW LEVEL SECURITY;

-- ✅ Safe: Explicit RLS policy binding to the authenticated user
ALTER TABLE projects ENABLE ROW LEVEL SECURITY;

CREATE POLICY "Users can only modify their own projects"
ON projects FOR ALL
USING (auth.uid() = user_id)
WITH CHECK (auth.uid() = user_id);

1.3 Validate & Sanitize All Inputs

  • Schema validation at boundaries: Use validation libraries (Zod, Valibot, Yup, or Pydantic) on every incoming payload. Never trust types declared only in TypeScript interfaces—TypeScript types do not exist at runtime!
  • Parameterized database queries: Ensure raw SQL queries generated by LLMs are using parameterized inputs or ORM abstractions (Prisma, Drizzle, Kysely) rather than string interpolation:
// ❌ Vulnerable to SQL Injection
db.query(`SELECT * FROM users WHERE email = '${email}'`);

// ✅ Safe
db.query(`SELECT * FROM users WHERE email = $1`, [email]);

1.4 Beware "Slopsquatting" (Hallucinated Packages)

  • Audit package.json: LLMs frequently hallucinate package names (e.g., react-perfect-responsive-carousel). Attackers register these hallucinated package names with malicious payloads.
  • Run npm audit or use snyk / socket.dev to verify that all installed packages have established registries, maintainers, and download histories.

2. Cost Defense & AI Safety (Preventing the $5,000 Surprise)

When your vibe-coded app integrates LLMs, an unchecked prompt loop or open endpoint can drain your API credits overnight.

2.1 Hard Billing Limits and Quotas

  • Set hard monthly spend limits directly in the dashboards of OpenAI, Anthropic, DeepSeek, Together AI, Replicate, and AWS.
  • Set webhook/email alerts at 50%, 75%, and 90% of your projected budget.

2.2 Rate Limiting & Abuse Prevention

  • Add IP & User-based rate limiting: Implement Upstash Redis (@upstash/ratelimit) or Cloudflare Turnstile / WAF rules on any endpoint that calls an LLM or generates assets.
  • Bot protection: Place automated checks (Cloudflare Turnstile or reCAPTCHA v3) on registration, login, and public guest-generation endpoints.
// Protecting an AI completion route with Upstash
import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis";

const ratelimit = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(10, "1 m"), // 10 requests per minute per IP/user
});

2.3 Token Limits & Stream Timeouts

  • Enforce max_tokens: Always pass an explicit max_tokens ceiling to LLM calls to prevent runaway generational loops.
  • Client disconnect termination: Ensure your server halts LLM generation if the user closes the browser tab or aborts the request (AbortController).
  • Input truncation: Truncate user prompt inputs before sending them to the LLM (e.g., limit text fields to 4,000 characters). Unbounded inputs result in unexpected prompt injection and excessive token bills.

3. Database & State Performance

AI models love to fetch data in loops, skip pagination, and ignore database indexing because in local development, datasets only contain 5 test rows.

3.1 Pagination on Every List Endpoint

  • No unbounded SELECT *: Replace any query fetching lists with cursor-based or limit-offset pagination (e.g., .limit(20)).
  • Defend against payload bloat: Never fetch large JSON blobs, vector embeddings, or text descriptions when only displaying a summary card.

3.2 Database Indexes

  • Index foreign keys and query filters: If your code frequently executes WHERE user_id = ... or ORDER BY created_at DESC, add explicit database indexes:
CREATE INDEX idx_projects_user_id ON projects(user_id);
CREATE INDEX idx_projects_created_at ON projects(created_at DESC);
  • Connection pooling: If running serverless functions (Vercel, AWS Lambda, Supabase Functions), ensure you are connecting via a connection pooler (e.g., PgBouncer, Supabase Pooler, Prisma Accelerate) to avoid running out of Postgres connections under concurrent traffic.

4. Architecture & De-Spaghettification

AI coding tools tend to introduce subtle entropy: they tack on code to the end of existing files, create redundant utility functions, and import duplicate libraries.

4.1 Kill the 2,000-Line Component Monster

  • Separate logic from presentation: Extract stateful hooks, business rules, and API clients out of massive UI components.
  • Component breakdown: If a component requires 500+ lines of code, ask your AI:

    "Refactor Dashboard.vue into modular subcomponents (Header, AnalyticsGrid, ActivityFeed, Modals) and a composable useDashboardData. Maintain strict TypeScript types without altering existing functionality."

4.2 Deduplicate Packages and Dependencies

  • Review imports: Check if your bundle accidentally loaded multiple libraries doing the same thing (e.g., lucide AND heroicons, or date-fns AND dayjs).
  • Run bundle analysis: Use your framework's bundle analyzer to identify oversized chunks.

4.3 Eliminate Infinite useEffect / Watcher Render Traps

  • Inspect dependency arrays & watchers: LLMs often place too many reactive triggers in watcher dependencies, creating hidden infinite re-fetch loops.
  • Test with strict lifecycle checks: Verify that components render without firing duplicate mutation requests or spamming your backend.

5. User Experience & The "Vibe Polish"

A vibe-coded prototype usually breaks the moment the browser window is resized or an unexpected API status code returns.

5.1 The Responsive & Mobile Smoke Test

  • Test mobile breakpoints (375px & 414px): Check for horizontal overflow, clipped text, and modals that extend below the mobile screen without scrolling.
  • Touch targets: Ensure buttons and interactive icons are at least 44x44 pixels for touch devices.
  • Input zoom on iOS: Ensure mobile inputs have at least font-size: 16px (or 1rem) so iOS Safari doesn't trigger an automatic, jarring viewport zoom.

5.2 Empty, Loading, and Error States

  • Skeleton loaders: Replace abrupt layout-shifting content loads with subtle skeleton placeholders.
  • Empty states: Provide a helpful illustration/copy and a "Call to Action" when a list contains 0 items (e.g., "No projects found. Create your first project").
  • Error boundaries: Wrap critical sections of your application in Error Boundaries so that a single render crash in an activity feed doesn't take down the entire app.

6. Observability & SRE: Know When You’re Down

If your app crashes in production and you don't have telemetry, your users will simply close the tab and never come back.

6.1 Crash & Error Reporting

  • Install Sentry / Highlight / LogRocket: Set up error capturing on both the frontend and backend.
  • Filter out noisy errors: Exclude user network disconnects (Load failed, Failed to fetch, browser extension errors).

6.2 Health Checks & Uptime

  • /api/health endpoint: Deploy a lightweight endpoint that pings your primary database and returns { status: "ok", timestamp: ... }.
  • Free uptime monitor: Configure Better Stack, UptimeRobot, or Checkly to ping your health endpoint every 3–5 minutes.

  • Terms of Service & Privacy Policy: Use an automated generator (Termly, Iubenda, or standard SaaS templates) detailing that data may be processed by AI model vendors (OpenAI, Anthropic, etc.).
  • Favicon & Open Graph (OG) Images: Remove the default framework favicon. Generate a custom 32x32 favicon and a 1200x630px OG social card image for previews on X, LinkedIn, and Discord.
  • Clean Custom Domain & SSL: Verify that https://yourdomain.com redirects cleanly to https://www.yourdomain.com (or vice versa) and that SSL certificates are provisioned with HSTS enabled.

The AI Auditor: Copy-Paste Audit Prompts

You can use your AI coding assistant to audit its own code. Open a fresh session and feed it these prompts:

Prompt 1: The Security & Secret Scan

Conduct a strict security audit on this repository:
1. Identify any secret keys, credentials, or backend URLs exposed to the client bundle.
2. Check all API routes/server actions for missing authentication, authorization, and IDOR vulnerabilities.
3. Verify that all external user input is validated using schemas before being passed to databases or third-party APIs.
Report vulnerabilities ordered by severity with specific file links and code fixes.

Prompt 2: The DB & Performance Optimization Scan

Review our database queries and API handlers:
1. Highlight any queries that lack pagination or fetch excessive data (e.g., SELECT * without limits).
2. Identify missing database indexes on foreign keys and frequently filtered columns.
3. Check for potential N+1 query patterns or unbounded Promise.all calls.
Provide concrete migration and code snippets to resolve these issues.

Prompt 3: The Architecture Cleanup Scan

Inspect our component tree and dependencies:
1. Flag components over 300 lines that mix data fetching, state management, and presentation.
2. Check for duplicate or conflicting npm dependencies.
3. Identify potential infinite loops in lifecycle hooks or unnecessary re-renders.
Recommend a minimal, non-breaking refactor plan.

Summary: The Pre-Flight Cheat Sheet

Before you tweet, launch, or invoice:

[ ] 1. SECRETS     : No private keys in client code or git history.
[ ] 2. AUTH & RLS  : All tables locked with RLS; endpoints check session identity.
[ ] 3. SPEND CAPS  : Hard billing limits set on all AI & Cloud providers.
[ ] 4. RATE LIMITS : AI and auth routes protected against spam and scraping.
[ ] 5. PAGINATION  : All list queries bounded with limit & cursor.
[ ] 6. TELEMETRY   : Sentry + Uptime monitoring active.
[ ] 7. MOBILE      : Tested at 375px with zero horizontal scroll.
[ ] 8. BRANDING    : Custom favicon + OG meta tags updated.

Vibe coding gets you to the starting line in record time. Engineering discipline gets you across the finish line.