Case Studies: 0 to Launch
Theory is useful, but nothing teaches vibe coding like watching it happen. This chapter walks through three small-but-real projects, built and shipped end to end by directing an AI. None of them are toys. Each one has users, a domain name, and money or time on the line.
For each, you'll see the idea, the spec we handed the AI, the stack we chose, the prompts that actually moved the needle, the obstacles that nearly derailed us, and how it went live. Read these as runbooks, not highlight reels.
Case Study 1: A Multilingual Landing + Payments Page
The idea
A friend sells a $29 PDF guide to expats moving to Korea. She wanted a single page that loaded fast worldwide, spoke English, Korean, and Chinese, and took card payments without her touching a server.
The spec
We kept the spec to one paragraph before writing any code:
Build a single-page marketing site for a $29 digital product.
Three languages (en/ko/zh), auto-detected from the browser but
switchable with a toggle. One "Buy" button that opens Stripe
Checkout and redirects to a thank-you page on success. No backend
server I have to maintain. Must load fast from anywhere.
The stack
"No backend I maintain" plus "fast worldwide" pointed straight at static hosting on an edge CDN with a tiny serverless function for the one thing that needs a secret: creating the Stripe session. We chose a static site on Cloudflare Pages with a single Pages Function.
The key prompts
We started broad and let the AI propose structure:
Scaffold a static landing page (plain HTML/CSS/JS, no framework)
with a language toggle for en/ko/zh. Store the copy in a single
JS object keyed by language. Detect the default from
navigator.language. Keep it to one index.html plus one main.js.
Then the payment path, where the secret lives:
Add a Cloudflare Pages Function at /api/checkout that creates a
Stripe Checkout session for a single $29 product and returns the
redirect URL. Read STRIPE_SECRET_KEY from the environment, never
hardcode it. The Buy button should POST to this function and then
window.location to the returned URL.
The obstacle
The first live test failed: the Buy button did nothing, and the browser console showed a CORS error. We didn't guess. We pasted the exact error back:
Clicking Buy logs: "Access to fetch at '/api/checkout' blocked
by CORS policy." The function and page are on the same domain.
Here's the function code: [pasted]. What's actually wrong?
The AI spotted it instantly: the function was returning the Stripe URL as JSON but the fetch was being made before the function's OPTIONS preflight was handled, because we'd deployed the static page and the function to two different Pages projects by mistake. The real fix was deployment topology, not code. We moved the function into the same project's /functions directory and the error vanished. Lesson: paste the literal error and the surrounding context, and don't accept the first plausible explanation if it doesn't match your setup.
The launch
We added the live Stripe keys as encrypted environment variables in the Pages dashboard (never in the repo), pointed her domain's DNS at Pages, and ran one real $29 test purchase with a card, then refunded it. Live in an afternoon. She made her first sale that evening.
Case Study 2: A Small SaaS Tool with Auth + Database
The idea
A freelance designer wanted a private dashboard to log billable hours per client and export a monthly CSV. Nothing fancy, but it needed accounts (so her data was hers alone) and persistence.
The spec
Auth and a database raise the stakes, so the spec got more specific about boundaries:
A logged-in web app where a user can: sign up / log in with email,
create clients, log time entries (date, client, hours, note),
see a table of entries filtered by month, and download that month
as CSV. Each user only ever sees their own data. Mobile-friendly.
The stack
For a solo builder, the winning move is a stack where auth and database are managed services, not code you write. We chose a Next.js app deployed on a serverless host, with a hosted Postgres database and a drop-in auth provider that handles email login, sessions, and password resets for us. Less code to get wrong is less code an AI can get wrong on our behalf.
The key prompts
We let the auth provider's own template do the heavy lifting, then directed the AI to layer the domain logic on top:
We're using [auth provider]'s Next.js starter. Add a Postgres
schema with two tables: clients (id, user_id, name) and
time_entries (id, user_id, client_id, date, hours, note). Every
query MUST filter by the logged-in user's id from the session.
Generate the migration and the typed data-access functions.
The "MUST filter by user_id" line was the most important sentence in the whole project. We repeated that constraint in nearly every prompt that touched data, because the single scariest bug in a multi-user app is one user seeing another's rows.
For the export:
Add a /api/export route that takes a month (YYYY-MM), pulls the
logged-in user's time_entries for that month joined to client
names, and streams a CSV download. Reject the request if there's
no valid session.
The obstacle
In testing, we created two accounts and discovered account B could see account A's clients in a dropdown. This is exactly the bug we feared. Rather than ask the AI to "fix it," we made it prove the problem first:
Account B is seeing Account A's clients. Show me every database
query in the codebase that reads the clients table, and for each
one tell me whether it filters by the session user_id. Don't fix
anything yet — just audit.
The audit surfaced one query — the dropdown loader — that had been written before we added the constraint and slipped through. We had it add the missing filter, then asked for a guard:
Add a single helper that every read goes through, which takes the
session and injects the user_id filter, so no future query can
forget it. Refactor the existing queries to use it.
That turned a one-off fix into a structural guarantee. The lesson: when an AI introduces a security bug, don't just patch the instance — direct it to remove the category of mistake.
The launch
We seeded a test month of data, exported the CSV, opened it in a spreadsheet to confirm the numbers and encoding were right, then set a strong database password and rotated the credentials out of any local file. We deployed to the serverless host, added the production environment variables in its dashboard, and gave her the URL. She onboarded herself with a real signup. The whole build was a weekend.
Case Study 3: A Personal Automation Utility
The idea
A purely selfish one: a script that every morning grabs the day's calendar events and the weather, formats a one-line summary, and sends it to a personal Telegram chat. The point was to stop opening three apps before coffee.
The spec
A scheduled job that runs at 7am my timezone. It reads today's
events from my calendar, fetches the forecast for my city, and
sends one Telegram message like: "3 events today, first at 9:30.
High 24C, rain after 4pm." If any source fails, still send what
you have and note what's missing.
That last sentence — degrade gracefully instead of crashing — is the kind of behavior you have to ask for explicitly. The AI won't assume it.
The stack
A single scheduled serverless function (a Cron-triggered Worker) so there's no machine to keep running. The whole thing is one file plus a schedule.
The key prompts
Write a Cloudflare Worker triggered by cron at 7am Asia/Seoul.
It calls a weather API and a calendar API (I'll provide both
tokens as secrets), builds a one-line summary, and POSTs it to
the Telegram Bot API. Wrap each external call in try/catch so one
failure doesn't kill the message. Read every token from env.
When we wanted to iterate on wording without waiting for 7am:
Add a manual trigger: if the Worker is hit with a GET request and
a ?test=1 query param, run the same logic immediately and return
the message text in the response instead of sending it. Keep this
behind a secret token so only I can trigger it.
That test trigger saved us from a brutal feedback loop — we could see the output on demand instead of once per day.
The obstacle
The Telegram message arrived, but the times were wrong: events showed in UTC, not Korean time. The weather was fine. We isolated it:
Calendar times are 9 hours off — showing UTC, not Asia/Seoul.
The weather time ("rain after 4pm") is correct. Here's how I
format both: [pasted]. Fix only the calendar formatting and tell
me why the weather path was already right.
The AI explained the weather API already returned localized strings while the calendar API returned UTC timestamps we were printing raw. It added a single timezone conversion at the calendar formatting step. Scoping the fix to "only the calendar path" stopped it from rewriting the working weather code — a common way AI fixes break things that were fine.
The launch
"Launch" here just meant deploying the Worker, setting the cron schedule, and adding the API tokens as encrypted secrets. We hit the ?test=1 endpoint a few times to confirm the message read well, then left it. It's run every morning since.
What the three have in common
- The spec came first, and it was short. A paragraph, not a document. But it named the constraints that mattered — languages, "only sees their own data," "still send what you have."
- Secrets never touched the repo. Every project read keys from environment variables or encrypted secrets, set in a dashboard, not in code.
- Obstacles were solved by feeding the AI exact evidence — the literal error, the actual code, the real symptom — and by scoping fixes narrowly so working code stayed working.
- Launch meant a real test against reality: a live purchase, a second user account, an on-demand trigger. Not "it compiled."
You don't need a big idea to start. You need a small one, a one-paragraph spec, and the willingness to direct rather than to type.