How to monitor a vibe-coded app in production
Explore with AI
Get the vibe-coded app to report its own failures. Replace swallowed errors with structured logs, put a health route that queries the database behind an outside uptime check, and stamp every log line with the release that produced it. Then give scheduled jobs heartbeats, and alert only on what users feel: the app is down, server errors are rising, sign-in is failing, the database is out of connections, or a job has stopped.
On this page
- What production monitoring means for a vibe-coded app
- Why vibe-coded apps fail quietly after launch
- The signals that tell you a vibe-coded app is broken
- Step 1: Map what the generated app depends on
- Step 2: Turn swallowed errors into loud, structured logs
- Step 3: Add a health route that queries the database
- Step 4: Stamp every log line with the release
- Step 5: Put heartbeats on cron jobs and webhooks
- Step 6: Write the few alerts worth waking up for
- Getting your coding agent to add the instrumentation
- Where monitoring a vibe-coded app goes wrong
- A launch checklist for a vibe-coded app
- Watching a vibe-coded app with Polylane
- Common questions
To monitor a vibe-coded app in production, add the layer the generator left out. Every failure should raise an error and log a structured event. A health route should sit behind an outside uptime check. Each log line should carry the release that produced it, and every scheduled job needs a heartbeat. On top of that, write a handful of alerts for what users feel first: the app is down, server errors are climbing, sign-in is failing, the database is running out of connections, or a job has stopped running.
You don’t need a new platform on day one. Your host’s logs, a small logging helper, one health route and one external check cover most of it. The rest of this page takes you through each step with code you can paste, then covers the alerts worth having and the gaps that bite a few weeks after launch.
What production monitoring means for a vibe-coded app
A vibe-coded app is built mostly by prompting an AI tool such as Lovable, Bolt, v0, Replit, Cursor or Claude Code, then shipping what it produces once the preview works. Production monitoring is the set of signals that tell you the live app is failing before a user does, with enough detail to find the cause.
Those signals come in three kinds:
- Logs: one record per event, such as a failed payment or a request that timed out.
- Metrics: numbers over time, such as requests per minute, error rate or open database connections.
- Traces: the path of one request through your frontend, API, database and third-party calls.
For one app on one host with a hosted database, logs and a few metrics do most of the work. Traces start to pay off once a request crosses several services.
A vibe-coded app is different because nobody on the team has read every file. Monitoring is how you learn what the code actually does with real users and real data. I built and led the Workers observability team at Cloudflare, and the rule held on every platform we touched: a person or an agent can only debug what the code emits.
Why vibe-coded apps fail quietly after launch
Generated code is written for the path the prompt described. Check the other paths for gaps like these:
- A token that never expires, or an endpoint with no rate limit.
- An API call with no retry and no timeout. It fails or hangs when the other side is slow.
- A database connection pool sized for a demo, which runs out when real users arrive at the same time.
- A query with no index. It is fast on test data and slow on real data.
- A
catchblock that swallows the error and returns an empty result.
The last one is why these apps fail quietly. A failed request turns into an empty list on screen, the server logs nothing, and every dashboard stays green. The user sees a broken page and you see nothing.
These gaps can also stay hidden for a while. In one widely reported case, a founder ran a public coding trial on Replit. About nine days in, the agent ignored an instruction not to change anything without approval. It ran commands that wiped the production database and deleted records for more than 1,200 executives and over 1,190 companies. It then said a rollback wasn’t possible, though a backup was available. Monitoring can’t stop an agent that has write access to production. What it can do is tell you within minutes that the data is gone, and show you which release or action came just before.
The signals that tell you a vibe-coded app is broken
Start with the signals that match what a user feels. Each one maps to a gap in the list above.
- Reachability of the main route. Can someone outside your host load the app and reach the database behind it?
- Server error rate per route. Count 5xx responses by route. A rise straight after a deploy points at that deploy.
- Sign-in and access failures. A jump in 401 and 403 responses usually means a broken auth flow or a changed access rule.
- Database pressure. Open connections compared with the limit, and statements whose average time keeps climbing.
- Background work that stopped. Cron jobs, queue consumers, webhooks and email sends. When these stop, nothing throws an error. The work just never happens.
- Third-party calls. Error rate and latency for your payment provider and any AI API, plus the AI API spend. Spend climbs fast when a loop keeps retrying.
That’s the whole starter list. Everything else can wait until one of these points you at it.
Step 1: Map what the generated app depends on
You can’t monitor a dependency you don’t know about. Ask your coding agent to list them, then check its answer against the environment variables set on your host.
Write the result into a short file in the repository, such as OPERATIONS.md. Record where the frontend is hosted, where the API and functions run, which database you use, which scheduled jobs exist and which third-party APIs the app calls. While you’re in there, check that no secret key is read in client-side code. When the generator doesn’t treat API keys as secrets, they can end up in the browser bundle.
Step 2: Turn swallowed errors into loud, structured logs
First, find the error handling that hides failures. A search catches most of it:
grep -rn -A3 "catch" src app lib | grep -E "return (\[\]|null|undefined)"
Next, add a small logger that writes one JSON line per event. Every line gets the release and the environment, so you can filter on them later:
// lib/log.ts
const release = process.env.RELEASE_SHA ?? "dev";
const env = process.env.APP_ENV ?? "development";
type Fields = Record<string, unknown>;
function write(level: "info" | "warn" | "error", event: string, fields: Fields = {}) {
console.log(JSON.stringify({ ts: new Date().toISOString(), level, event, release, env, ...fields }));
}
export const log = {
info: (event: string, fields?: Fields) => write("info", event, fields),
warn: (event: string, fields?: Fields) => write("warn", event, fields),
error: (event: string, fields?: Fields) => write("error", event, fields),
};
Then rewrite each swallowed error. This is what generated code often looks like:
export async function getOrders(userId: string) {
try {
const res = await fetch(`${API_URL}/orders?user=${userId}`);
return await res.json();
} catch (e) {
return [];
}
}
This version has a timeout, a structured event and a real failure:
import { log } from "@/lib/log";
export async function getOrders(userId: string) {
const started = Date.now();
const res = await fetch(`${API_URL}/orders?user=${userId}`, {
signal: AbortSignal.timeout(8000),
});
if (!res.ok) {
log.error("orders.fetch_failed", { userId, status: res.status, ms: Date.now() - started });
throw new Error(`orders request failed: ${res.status}`);
}
log.info("orders.fetched", { userId, ms: Date.now() - started });
return res.json();
}
The rule is simple. Every failure is either handled on purpose or logged and raised. Log one event per request or job, with the fields you’d search by: event name, route, user or tenant id, status and duration. Keep passwords, tokens and personal data out of those fields, and check generated schemas and log lines for personal data stored in plain text.
To prove it works, throw a test error from a route in staging and check that it shows up in your host’s logs with the route and the release attached.
Step 3: Add a health route that queries the database
A health route that returns success without doing anything tells you the process is up and nothing else. Make yours run one real query:
// app/api/health/route.ts
import { db } from "@/lib/db";
export async function GET() {
const started = Date.now();
try {
await db.query("select 1");
return Response.json({ ok: true, release: process.env.RELEASE_SHA, dbMs: Date.now() - started });
} catch (err) {
console.error(JSON.stringify({ level: "error", event: "health.db_failed", message: String(err) }));
return Response.json({ ok: false }, { status: 500 });
}
}
Check it from your own machine:
curl -s -w "\n%{http_code} %{time_total}s\n" https://your-app.example.com/api/health
A healthy app answers like this:
{"ok":true,"release":"3f9c2a1","dbMs":12}
200 0.184s
Now point an external uptime checker at the route. It has to run outside your host’s network, because a check on the same host goes down with it and stays silent.
Step 4: Stamp every log line with the release
When errors jump, the first question is what changed. If every log line carries the commit that produced it, the answer is a filter away. Set the variable in your build step, and make sure your host passes it through to runtime:
export RELEASE_SHA=$(git rev-parse --short HEAD)
npm run build
The logger from Step 2 already adds release to every line, and the health route reports it. This is the first thing I’d add to any app, generated or not. It turns “something broke this afternoon” into “errors started with release 3f9c2a1”.
Step 5: Put heartbeats on cron jobs and webhooks
Background work fails by not happening, so you have to watch for something that is missing. Make each job report when it finishes:
import { log } from "@/lib/log";
export async function nightlyInvoices() {
const started = Date.now();
try {
const { sent } = await sendInvoices();
log.info("job.invoices.done", { sent, ms: Date.now() - started });
await fetch(process.env.INVOICES_HEARTBEAT_URL!, { method: "POST" });
} catch (err) {
log.error("job.invoices.failed", { message: String(err), ms: Date.now() - started });
throw err;
}
}
Point the heartbeat URL at any monitor that alerts when a ping doesn’t arrive within the job’s schedule plus a margin. For webhooks, log two separate events: one when the webhook arrives and one with the processing result. Alert when arrivals fall to zero during hours that normally see traffic.
Step 6: Write the few alerts worth waking up for
Page someone only for signals a user would notice. Send everything else to a daily summary.
- App down. The external health check fails on consecutive runs.
- Server errors rising. Supabase’s published detection checks give a sensible starting rule: 20 or more server errors, a rate of at least 1%, and at least double the previous hour’s rate. The same shape works for any API.
- Database connections near the limit. Those checks flag client connections at 80% of
max_connections. On Postgres you can read both numbers with one query:
select count(*) filter (where backend_type = 'client backend') as client_connections,
current_setting('max_connections')::int as max_connections
from pg_stat_activity;
- A slow statement getting slower. Flag a statement whose mean time reaches 100 ms and doubles hour on hour, with 20 or more calls in each hour. A deploy that drops an index shows up here first.
- A missed heartbeat on any job that moves money, sends email or syncs data.
- A jump in sign-in failures, compared with the same window the day before.
If your app runs on Supabase, the Metrics API publishes Postgres health metrics that refresh every minute, and log drains can stream platform logs to another tool on the Pro, Team and Enterprise plans.
Getting your coding agent to add the instrumentation
The same agent that wrote the app can do most of Steps 2 to 5 in one pass, as long as the request is precise about what you want.
Read the diff before you merge it. Look for three things: secrets or personal data in log fields, new catch blocks that still return empty results, and a health route that skips the database. Then run the test error from Step 2 again.
Where monitoring a vibe-coded app goes wrong
- The health route never queries the database. It stays green while every real request fails. Fix: run one real query, as in Step 3.
- The uptime check runs on the same host. When the host goes down, the check goes down with it. Fix: run it from outside.
- Alerts land in an inbox nobody reads. Fix: send pages to a phone or a chat channel someone owns, and keep that list short.
- Preview deployments page on-call. Fix: tag every event with the environment, and filter alerts to production.
- Full request bodies get logged. That puts tokens and personal data into your logs. Fix: log named fields only, and redact anything that looks like a secret.
- Only the browser reports errors. Server functions and jobs fail out of sight. Fix: send server logs somewhere you can search, and alert on them.
- Platform logs are off or kept briefly. Fix: confirm logging is on for every function, and drain logs to a store with the retention you need.
- A regenerated file drops the logging. Agents rewrite whole files. Fix: add a test that fetches the health route in CI, and check new code for the logger import during review.
- The agent that built the app holds production write credentials. Fix: give it read-only access, send every change through a pull request that a person merges, and test a backup restore before you need one.
A launch checklist for a vibe-coded app
- Every external service and secret is listed in the repository, and no secret is read in client code.
- No catch block returns empty data silently. A test error shows up in the logs with its route and release.
- A health route queries the database, and an outside checker calls it.
- Every log line carries the release and the environment.
- Every scheduled job and webhook has a heartbeat or an arrival alert.
- Alerts cover the app being down, rising server errors, connection pressure, slow statements, missed heartbeats and sign-in failures, and they reach a person.
- Production credentials held by coding agents are read-only.
Watching a vibe-coded app with Polylane
Polylane connects your repository, your cloud accounts and any observability tool you already use. Clouds such as AWS, Cloudflare, Vercel, Fly, Render, Railway and Kubernetes count as a telemetry source by themselves. It builds checks from that telemetry with no alert rules to write, and flags resources that serve traffic but emit no logs. For a connected repository, it reports which route handlers emit no telemetry, and you can hand each gap to an agent. It also reviews every pull request against the production it deploys to before merge, whoever opened it, your coding agent included.
Running on Supabase? See how Polylane monitors Supabase in production.
Common questions.
Do I need a paid observability tool to monitor a vibe-coded app?
Not on day one. Your host's logs, a structured logger, a health route that queries the database and an outside uptime check cover the first signals. Add an error tracker or a log drain once you have real traffic and need to search several days of history.
What should I monitor first on a vibe-coded app?
Start with six signals. Can people outside your host reach the main route and the database? Watch server errors per route, spikes in 401 and 403 responses at sign-in, database connections and slow statements, background jobs that stopped, and errors and spend on third-party APIs. Each one matches a gap that generated code often leaves.
How do I find out whether my app is swallowing errors?
Search for catch blocks that return an empty array or null, and ask your coding agent to list every one. Then throw a test error from a route in staging and check that it appears in your logs with the route and release. If it never appears, something between the route and the logger is swallowing it.
My vibe-coded app runs on Supabase. What changes?
You get two extra sources. The Metrics API publishes Postgres health metrics in Prometheus format and refreshes them every minute. Log drains stream platform logs to another tool on the Pro, Team and Enterprise plans. Supabase's published checks flag connection pressure at 80% of max_connections, which makes a good first alert.
Should the AI agent that built my app have access to production?
Read-only access at most. In one reported Replit trial, an agent had been told not to make changes without approval. About nine days in, it wiped the production database anyway, deleting records for more than 1,200 executives and over 1,190 companies. Keep writes behind pull requests that a person merges, and test your backup restore before you need it.
How many alerts should a small vibe-coded app have?
Start with the few that match what users feel: the health check failing, server errors rising after a deploy, a missed job heartbeat, and database connections near the limit. Send everything else to a daily summary. An alert that fires often and needs no action teaches people to ignore the one that matters.
Do I need tracing for a vibe-coded app?
Usually not at first. With one frontend, one API and a hosted database, structured logs that share a request id answer most questions. Add tracing once requests cross several services and the logs can't tell you which hop is slow.
Should preview deployments be monitored?
Keep their logs, but route their alerts away from on-call. Tag every event with the environment and filter production alerts on that tag. A broken preview branch then never pages anyone.
Sources
- Polylane documentation (full text)
- Polylane full content, including How to Monitor a Supabase App in Production
- From Vibe Code to Production: How to Make AI-Generated Software Production-Ready (Rishabh Software)
- Vibe Coding to Production: The Checklist (Slash)
- AI-powered coding tool wiped out a software company's database (Fortune)
- Vibe coding service Replit deleted production database (The Register)
Boris Tane is the founder of Polylane. He previously founded Baselime, observability for the future of the cloud, which Cloudflare acquired. At Cloudflare he built and led the Workers observability team.
Related
- How to Monitor a Supabase App in Production
Monitor a Supabase app in production: scrape the Metrics API into Prometheus, drain logs, run SQL health checks, trace requests and alert on what matters.
- How to Monitor a FastAPI App on Railway: Logs, Traces and Alerts
Set up health checks, JSON logs, OpenTelemetry traces, monitors and crash alerts for a FastAPI service on Railway, with the commands and config to copy.
- How to keep AI coding agents from breaking production
Stop AI coding agents breaking production: scoped credentials, branch protection they can't bypass, required checks that block, and production-aware review.
- How to Use Claude Code to Debug Production Issues
Debug production issues with Claude Code: get logs and traces into the session, test hypotheses against evidence, stay read-only and ship a verified fix.
- Is It Safe to Give an AI SRE Read Access to Production?
Read access for an AI SRE is a sound first step if you scope it. The risks are secrets, customer data and exfiltration. Here are the controls and a checklist.