Your app is live. You tested signup, you tested the main feature, everything worked. Then a week later someone replies to your launch post: "Is your site broken? I can't log in."
Too often, that is how founders find out their app is broken: from a user, days late, after other people quietly gave up and left. AI tools like Lovable, Bolt, v0, Cursor and Claude Code are great at building the happy path. They usually do not set up the part that tells you when something fails in production.
This guide fixes that in an afternoon. By the end you will have three things: an error tracker that catches crashes and silent failures, an outside uptime check that notices when the app or database is down, and alerts that actually reach you. All of it starts on free plans.
Before installing anything, decide what "broken" means for your app. You do not need to watch everything. Fill in this template:
MUST-NEVER-BREAK LIST
1. Signup (new account, new device, logged out)
2. Login and password reset
3. The one core action (the reason people use the app)
4. Saving data (create / edit / delete the main thing)
5. Payment and access after paying (if you charge)
For each one:
- Where does it run? (browser / API route / Edge Function / webhook)
- What does the user see if it fails? (error message / blank screen / nothing)
- How would I find out today? (honest answer)
The last line is the important one. If the honest answer is "a user would tell me", that flow goes to the top of your list.
A hypothetical example: in a habit-tracker app, the core action is "mark today as done". If that save fails quietly, the user sees the checkmark, refreshes the next day, and finds their streak gone. Nothing crashed, so nobody gets told. That is exactly the kind of failure this guide is about.
Create a free account at sentry.io, create a project, and pick your framework. Sentry's free Developer plan is for one person and includes error monitoring and email alerts. Check the pricing page for the current limits.
If your app is Next.js (common with v0, Cursor and Claude Code), run the official wizard in your project folder:
npx @sentry/wizard@latest -i nextjs
It installs the SDK, creates the browser, server and edge config files, and wraps your next.config so source maps upload on build. It also asks whether to route browser requests through your own server to avoid ad blockers. Saying yes means fewer missed reports, with one trade-off from Sentry's docs: it can add a little server load and hosting cost.
If your app is React + Vite (common with Lovable and Bolt), install the SDK:
npm install @sentry/react
Then create one small file and import it first:
// src/instrument.ts
import * as Sentry from "@sentry/react";
Sentry.init({
dsn: import.meta.env.VITE_SENTRY_DSN,
environment: import.meta.env.MODE, // "production" on your live build
enabled: import.meta.env.PROD, // ignore errors from your laptop
});
python
// src/main.tsx - this import must be the FIRST line
import "./instrument";
Put the DSN in your host's environment variables as VITE_SENTRY_DSN. A VITE_ variable ends up in the browser, which is fine for a DSN and never fine for secret keys.
Finally, wrap your app so a crash shows a friendly message instead of a white screen, and gets reported:
<Sentry.ErrorBoundary fallback={<p>Something went wrong. Please refresh.</p>}>
<App />
</Sentry.ErrorBoundary>
Test it properly. Add a temporary button that throws new Error("Sentry test"), deploy, click it on the live site, and check the Issues page. Sentry's docs warn that errors you trigger from the browser DevTools console are sandboxed and will not show up, so use a real button. Delete the button afterwards.
This is the step most guides skip, and it matters most for vibe-coded apps. Sentry automatically catches uncaught errors. But many real failures never throw:
const { data, error } = await supabase.from(...).... If the code ignores error, the save fails silently and Sentry never hears about it. fetch() does not fail on 404 or 500.response.ok. try/catch blocks that only console.log
Add one helper and use it everywhere something important can fail:
// src/lib/report.ts
import * as Sentry from "@sentry/react"; // or "@sentry/nextjs"
export function report(where: string, err: unknown) {
const error =
err instanceof Error ? err : new Error(JSON.stringify(err));
Sentry.captureException(error, { tags: { where } });
}
Then use it on your must-never-break flows:
const { error } = await supabase.from("habits").insert(newHabit);
if (error) {
report("save-habit", error);
showToast("Could not save. Please try again.");
return;
}
js
const res = await fetch("/api/generate", { method: "POST", body });
if (!res.ok) report("generate", new Error(`API returned ${res.status}`));
The user gets a clear message instead of a fake success, and the where tag lets you filter Sentry by flow later.
You do not have to do this by hand. Paste this into your AI tool:
Find every Supabase query, fetch() call and try/catch block in this
project. For each one on these flows: [paste your must-never-break list],
make sure errors are (1) passed to the report() helper in
src/lib/report.ts with a short "where" tag, and (2) shown to the user
as a clear message. Never show success if the operation failed.
Do not change any other behavior. List every file you changed.
Errors in your server code never reach the browser, so the frontend setup will not see them.
instrumentation.ts file. Nothing extra to do.
import * as Sentry from "npm:@sentry/deno@^8";
Sentry.init({ dsn: Deno.env.get("SENTRY_DSN"), defaultIntegrations: false });
// inside your handler:
try {
// ...your existing code...
} catch (e) {
Sentry.captureException(e);
await Sentry.flush(2000); // send before the function shuts down
return Response.json({ error: "Internal error" }, { status: 500 });
}
Add SENTRY_DSN under Edge Function Secrets in the Supabase dashboard. Sentry recommends one project per part of your app, so create a second Sentry project for the backend and use its DSN here. That way frontend and backend errors do not mix.
Do webhooks (Stripe, auth hooks) first. When those fail, no user is on screen to complain; the work just never happens.
An error tracker you never open is the same as no error tracker. In Sentry, go to Alerts and create an alert for your project with:
production only, so local testing never pings you.
Use the "Send Test Notification" button before saving, and confirm the email actually lands in your inbox, not spam.
Sentry only sees errors from code that runs. If your host is down, your domain expired, or your database is unreachable, there may be nothing to report. You need something outside your app knocking on the door.
First, add a tiny health endpoint that also touches the database. For Next.js:
// app/api/health/route.ts
import { createClient } from "@supabase/supabase-js";
export const dynamic = "force-dynamic"; // never serve a cached "ok"
export async function GET() {
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
);
const { error } = await supabase.from("habits").select("id").limit(1);
if (error) return new Response("db-fail", { status: 500 });
return new Response("ok");
}
For a Lovable or Vite app, ask your AI tool to create the same thing as a Supabase Edge Function named health. Because the uptime robot has no login token, that one function needs verify_jwt = false under [functions.health] in supabase/config.toml. Keep it boring: it reads one row through Row Level Security and returns only "ok" or "db-fail", never data.
Then create a free UptimeRobot account. The free plan includes 50 monitors with checks every 5 minutes, plus keyword monitors. Set up two:
/api/health:
Five-minute checks are fine for a first SaaS.
A raw production error often points to something like index-a8f3.js:1:48213, which tells you nothing. Source maps fix that. Next.js users already got this from the wizard. For Vite, run:
npx @sentry/wizard@latest -i sourcemaps
It configures your build to upload source maps to Sentry. Sentry's docs warn that public source maps can expose your source code, so keep the option that deletes .map files after upload. Keep the Sentry auth token in your host's environment variables, never in Git.
Now the fun part. Sentry runs an official MCP server, so your AI tool can read your real errors. In Claude Code:
claude mcp add --transport http sentry https://mcp.sentry.dev/mcp/YOUR-ORG/YOUR-PROJECT
Cursor can add the same URL in its MCP settings, and Lovable lists Sentry as a chat connector. Then use a prompt like this:
Read the newest unresolved Sentry issue in production. Explain in plain
English what the user was doing and why it failed. Propose the smallest
fix, show me the diff before applying it, and add a check so this
error is reported with report() if it happens again.
Finish with a 15-minute weekly habit: open Sentry, sort by users affected, fix the top issue, mark it resolved, and ignore known noise (like browser-extension errors).
catch block that only logs, or a Supabase call that ignores @sentry/react for Vite), deploy, and test with a real button.report() helper and use it wherever Supabase, Your users should never be your monitoring system. Set this up once, and the next time something breaks, you will be the first to know.