Guides · 6 min read

Vibe Coding to Production: What Breaks First and How to Fix It

Five things a vibe-coded app gets wrong first, each with a 30-second test and a paste-ready prompt for Claude Code or Cursor.

Published Updated

It works on your laptop. Customers run a different test.

Vibe coding gets you a prototype that works for exactly one person: whoever wrote the prompt. Going from vibe coding to production means passing a different test, and the failures show up in the same order every time.

Below are the five things a vibe-coded app gets wrong first. Each has a 30-second test and a prompt to paste into Claude Code, Cursor, or Codex. Run them before your first customer does.

1. One customer can see another's data

The usual cause is a server that trusts an ID sent by the browser. The app looks fine to you because you only ever open your own records.

Test: create two accounts and a record in each. Signed in as the second account, request the first account's record. Expect a 403 or 404, not the data.

Prompt
Audit every API route and server action that reads or writes customer data. Confirm each query is scoped to the owner (user or organization) taken from the verified session, never from a request parameter or body. Fix any that are not, and add a test that signs in as user B, requests user A's record, and expects a denial.

The scope belongs in the query itself, so it covers updates, deletes, exports, and background jobs too. Here $2 comes from the session, not the request:

sql
SELECT id, name
FROM projects
WHERE id = $1 AND organization_id = $2;

Hiding a button is not authorization.

2. Data disappears when the app restarts

Prototypes often keep state in memory or in a local file. On most hosts an app's local disk is thrown away on every restart or deploy, and the data goes with it.

Test: create a record, restart the app, and reload. It should still be there.

Prompt
Find every place this app keeps state outside a database: in-memory arrays or maps, local JSON or SQLite files, and files written to disk. Move customer data to PostgreSQL, reading the connection URL from an environment variable, and add a migration for the schema. Keep uploads out of the local filesystem. Tell me what you changed and how to verify it survives a restart.

3. Secrets end up in the repo or the browser

Generated code often hard-codes a key, commits a .env file, or reads a secret through a browser-exposed prefix such as NEXT_PUBLIC_ or VITE_. Anything with that prefix is shipped to every visitor.

Test: search the repository, its git history, and your built JavaScript for your API keys. Any hit is already public.

Prompt
Search this repository, its git history, and the client-side bundle for API keys, tokens, passwords, and connection strings. Move each into an environment variable read only on the server, make sure no secret uses a browser-exposed prefix, add .env to .gitignore, and list the variables I must set on the host. Tell me which keys were committed and must be rotated.

Rotate anything that was ever committed. Deleting it from the latest commit does not un-publish it. For the database URL specifically, see Connect an AI-Generated App to Production Data Securely. The Twelve-Factor App explains why configuration belongs in the environment.

4. A release takes the app down

A schema change made by hand-editing the production database, or a release that renames a column the running version still reads, takes the app down mid-deploy. A prototype has a plan for neither.

Test: make a small schema change and deploy it. Did it apply from a migration file, and did the app stay up while it rolled out?

Prompt
Set up database migrations with the standard tool for this stack. Make each change additive first: add the new column, backfill it, switch the code to it, and drop the old column in a later release, so the previous version keeps working while a new one rolls out. Add a /health route that returns 200 only when the app can reach the database, and document how to redeploy the previous version.

5. A retried webhook corrupts billing

Payment providers retry webhooks, and a success page only proves the browser was redirected. Without a safeguard, a retry creates a second subscription row, and a failed payment never changes access.

Test: replay the same webhook event twice. The stored state must be identical after the second delivery.

Prompt
Review the payment webhook handler. Verify the provider's signature on the raw request body, make processing idempotent by storing each event ID and ignoring repeats, and change subscription state only from webhook events, never from the checkout success redirect. Add a test that delivers the same event twice and asserts the state is unchanged after the second delivery.

Skip this one until you take payments, but do it before. Stripe's webhook guidance covers retries and signatures.

What a deploy covers, and what stays in your code

With the five checks passing, the deploy itself should be one prompt. In the recorded Claude Code session on the home page, the agent reads the project, creates a PostgreSQL 17 database, uploads the code, deploys it, and reports the live HTTPS address.

app.productecho.com/dashboard/deployments?app=fieldnotesFull size
ProductEcho console, application overview for fieldnotes. The app is Ready and Healthy at fieldnotes.productecho.com. Status details read: application containers passing health checks.
fieldnotes after the agent deployed it: Ready, Healthy, with a public HTTPS address.
app.productecho.com/dashboard/postgres/xgos8u?section=overviewFull size
ProductEcho console, database overview. The database is Running with a Connection card marked TLS required, showing the host, database name feedback_board and user feedback.
The database the agent created: running, TLS required, with host, name and user supplied to the app.

That covers check 2: the data lives in a managed database, outside the container. It also gives check 3 a home, because the app's keys and connection details are set as environment variables, not written into the code.

app.productecho.com/dashboard/deployments?app=fieldnotesFull size
ProductEcho console, Environment tab for fieldnotes. Three variables are listed: NODE_ENV is production, ADMIN_KEY is shown as dots with a reveal icon, and the value of DATABASE_URL is hidden. A note reads: Secrets stay hidden until you reveal them.
The Environment tab. Secrets stay hidden until you reveal them, and the DATABASE_URL value is hidden in this screenshot.

It cannot cover the other three. ProductEcho can't tell whether your queries are scoped to the right customer, whether your migrations are safe to roll out, or whether your webhook handler survives a retry. Those live in your code, which is why the prompts above are worth running first.

Poster titled Five checks before your first customer, listing: 1. One customer sees another's data, 2. Data vanishes on restart, 3. Secrets in the repo or browser, 4. A release takes the app down, 5. A retried webhook corrupts billing.
The five checks on one page, to save or share.

When the checks pass, connect your agent and deploy.