Is my vibe-coded app production ready? A 12-point check.

Working is not production ready. Twelve checks: backups, authorization, secrets, error visibility, rate limits and rollback — and what each costs if skipped.

By UmaizPublished 10 min read

Probably not yet — but the gap is usually smaller and more specific than it feels. “Production ready” isn’t a quality judgement about your code. It’s a short list of concrete properties: you can recover from a mistake, users can’t reach each other’s data, secrets aren’t public, and you find out about failures before your customers tell you.

Here are twelve checks, ordered by what they cost you if you skip them. The first four are genuinely blocking. The rest you can launch without, if you know you’re doing it.

Blocking

1. Backups you have actually restored from

A backup you’ve never restored is a belief, not a backup. Managed databases usually enable daily snapshots by default, which is good, but the recovery path is the part that fails — wrong region, expired retention, or a snapshot of a database that was already corrupt.

Restore one into a scratch database today. It takes twenty minutes and converts an assumption into a fact.

If you skip it: one bad migration and the company’s data is gone. Nothing else on this list matters as much.

2. Authorization, not just authentication

These are different, and AI-generated code very often implements only the first. Authentication asks who are you. Authorization asks are you allowed to touch this specific record.

// Authenticated, and completely broken.
app.get('/api/orders/:id', requireLogin, async (req, res) => {
  const order = await db.orders.findById(req.params.id)
  res.json(order)  // any logged-in user can read any order
})
// The ownership check is the entire point.
app.get('/api/orders/:id', requireLogin, async (req, res) => {
  const order = await db.orders.findById(req.params.id)
  if (!order || order.userId !== req.user.id) {
    return res.status(404).json({ error: 'Not found' })
  }
  res.json(order)
})

Return 404 rather than 403 for records the user doesn’t own — 403 confirms the record exists, which is information you didn’t mean to give away.

Test it the direct way: log in as one user, take a record ID belonging to another, and request it.

If you skip it: a data breach that is trivially discoverable by incrementing a number in a URL.

3. No secrets in the client or the repository

Anything shipped to a browser is public, regardless of how it’s named. Check your built output:

npm run build
grep -rniE "sk_live|api[_-]?key|secret|password" dist/ .next/static/ 2>/dev/null

Then check your git history, which keeps things you deleted:

git log --all --full-history -- .env .env.local

If a key was ever committed, rotate it. Removing the file does not remove it from history, and history is what gets cloned.

If you skip it: someone else’s usage on your bill, or your database in someone else’s hands.

4. Errors you can see

Without error tracking you learn about failures from users, if they bother to tell you. Wire up any hosted error tracker — the free tiers are ample at your stage — and confirm it works by throwing something on purpose.

If you skip it: you are blind. Every other problem on this list becomes invisible until it’s expensive.

Important, but launchable without

5. Rate limits on anything that costs money

Login, signup, password reset, file upload and any endpoint calling a paid API. Without limits, one script turns your LLM or email bill into a story.

6. Server-side validation

Client-side validation is a convenience for honest users. Anyone can send whatever they like directly to your API. Validate on the server, with a schema, and reject rather than coerce.

7. Database indexes

A table scan is instant at a thousand rows and fatal at a million. Index the columns you filter and sort by — particularly foreign keys, which most ORMs do not index automatically.

8. A rollback path

Deploying is easy. Undoing a deploy at 2am, while the site is down, is the thing you need to have practised. Know the command before you need it.

9. Timeouts on every external call

A third-party API that hangs will exhaust your connection pool and take your app down with it, even though your code is fine.

const response = await fetch(url, { signal: AbortSignal.timeout(10_000) })

10. Security headers

HTTPS enforced, Strict-Transport-Security, X-Content-Type-Options: nosniff, and a Content Security Policy if you can manage one. Most hosts set the first for you and none of the rest.

11. Uptime monitoring

An external check that pings your site every few minutes and messages you when it stops answering. Five minutes of setup.

12. A staging environment

Somewhere to try the risky change that isn’t production. This is the one most small teams skip, and the one they regret first.

The honest summary

If you can answer yes to the first four, you can launch. You’ll have real gaps, and that’s a normal position to be in — the difference between a team that gets away with it and one that doesn’t is whether the gaps are known and written down, or discovered during an incident.

Write the remaining eight into a list with owners and dates. Not fixing something on purpose is engineering. Not knowing about it isn’t.

← All insights

Show me what’s stuck.

Don’t spend another six hours fighting the same bug. Send me what you’ve got — the messy version is usually the useful version.

Both open with a short template already filled in.