The Course
chapter 08core course

Build It — Your First Claude-Built SaaS

Capstone: ship your AI-backed SaaS, personalized start to finish

phase 1/7·Tell us about your app

The capstone. You've learned five primitives — time to use every one of them on a real project. By the end of this chapter you'll have a live-on-the-internet AI-powered SaaS that you built by driving Claude Code through the habits from the last seven chapters.

This isn't a copy-paste-this-exact-code tutorial. It's a partnered build. You paste the prompts into Claude Code, Claude writes the code, you verify each stage before moving on. If CLAUDE.md, skills, commands, and memory are wired up in your repo from the earlier chapters, you'll move fast.

capstone~2–3 hours focused5 phasesneeds + Anthropic accounts
before you startset your expectations
new terms you'll meet
by the end of this chapter

You'll have shipped your own AI-backed SaaS. Magic-link auth, per-user data isolation via RLS, a Claude-powered feature, deployed on Vercel with a URL you can send to your friends.

phase 1 · tell us about your app

What are we building?

Every prompt in this chapter personalizes to your answers. Fill this in once and the rest of the chapter follows your idea — or skip and use the default Notes app.

Short and concrete beats clever. Example: Track which freelance clients owe me money.

What kind of app is it?
Who can see the data?
How will it make money (or not)?

Auto-derived from your idea. Used in SQL snippets later. Edit if you want something different.

lesson

The pattern, not the product

The Notes app we build here is the pattern, not the goal. Auth + a per-user data table + one AI feature + deploy — that's the shape of 90% of small SaaS apps. Once you can run this loop with Claude Code, the next app takes you a third of the time. The AI feature is the part that varies; everything else is muscle memory.

The prompts in this chapter are deliberately high-level ("wire up magic-link auth", "add a notes table with RLS"), not step-by-step line-level instructions. That's the point: you've taught Claude your rules via CLAUDE.md, given it reusable skills, and framed every request with Plan → Implement → Self-audit → Ship. You direct; it codes.

Each phase ends with a concrete verify step. Don't skip them. "It seems to work" is how Chapter 9's silent bombs get planted.

what you'll have at the end
  • A Next.js app deployed on , reachable at a real URL.
  • auth via . No passwords.
  • A notes table with per-user — users only see their own.
  • A "Summarise" button that calls Claude and returns a one-paragraph summary, cost tracked per user.
  • A /feature-check-clean codebase that passes the Chapter 9 ship checklist.
phase 2

Your idea → a master prompt

This is the skill nobody teaches. You already have a 1-line idea. Claude needs 5 sections. Every section is a pre-filled textarea — edit to improve, don't stare at a blank page. The seed text is deliberately slightly imperfect for your archetype, so your edits are the lesson.

Fill out the capture form above first. You can still edit the sections below, but nothing will personalize until an idea is saved.
2.1

Context

Tells Claude who this is for and why it exists. Without it, Claude has to guess the audience.

bad · Too vague — Claude will guess
Build me a SaaS.
good · Specific enough for Claude to act
I'm building a tool for freelance designers to track which clients owe them money, as a private per-user app. Users sign in with magic links. It should feel like Linear — fast, keyboard-driven, no clutter.
2.2

Constraints

Rules Claude is not allowed to violate. This is where CLAUDE.md-style contracts go.

bad · Too vague — Claude will guess
Write clean code.
good · Specific enough for Claude to act
- use pnpm (never npm)
- TypeScript strict mode
- RLS on every user-owned table
- Zod safeParse on every public API route
- never commit API keys
- all server-side state lives in Supabase (no in-memory caches)
2.3

Stack

Explicit tech choices. Prevents Claude from reaching for defaults you don’t actually want.

bad · Too vague — Claude will guess
Modern web stack.
good · Specific enough for Claude to act
Next.js 16 App Router, Supabase (auth + Postgres), @anthropic-ai/sdk for AI calls, TailwindCSS 4. Stripe Checkout if we need payments.
2.4

Deliverable

What must physically exist when Claude stops. Concrete artifacts, not behaviors.

bad · Too vague — Claude will guess
A working app.
good · Specific enough for Claude to act
A running dev skeleton:
- pnpm dev starts cleanly on localhost:3000
- CLAUDE.md at repo root (under 25 lines)
- .env.local.example with all required vars
- a smoke page at / showing "<app name> — coming soon"
- git initialized with one "initial commit"
2.5

Acceptance

The checkpoints you’ll actually verify before calling the phase done.

bad · Too vague — Claude will guess
It works.
good · Specific enough for Claude to act
- pnpm dev starts without errors
- localhost:3000 renders the smoke page
- CLAUDE.md mentions our 5 rules
- git log shows one commit, git status is clean
the translationwhat you had → what Claude actually needs
your 1-line idea

[fill the capture form above]

your master prompt
[starts filling in as you edit the 5 sections above]
Save this as a slash command

Drop the copied text into .claude/commands/scaffold-mine.md so you never re-type it. Running /scaffold-mine will kick off your whole build from this master prompt.

phase 3scaffold your repo

Goal: get Next.js + a Supabase client + the Anthropic SDK talking to each other on localhost:3000. About 15 minutes.

3.1

Create a new Next.js app

Outside Claude Code, run this in a fresh directory:

IN YOUR TERMINAL
pnpm create next-app@latest notes-ai --typescript --tailwind --app --src-dir --import-alias "@/*"
cd notes-ai
sh

Open the new folder in Claude Code (claude) from here on.

3.2

Drop in a CLAUDE.md

Before Claude writes a single file, give it the contract. Type this into the Claude Code session — not your terminal:

TELL CLAUDE CODE
Create a CLAUDE.md at the repo root for [your idea] — a [your archetype]. Use the Next.js + Supabase + Anthropic starter template. Include hard rules for:
- use pnpm, never npm
- every anthropic.messages.create() must check isLoadTestMode() first
- Zod safeParse on every public API route body
- RLS must be enabled for every user-owned table
- data model is [your data shape]
Keep it under 25 lines.
claude

Watch what Claude produces. Tweak it until it actually matches how you want to work.

3.3

Install the baseline dependencies

One prompt, one dependency set. Let Claude wire the imports as it goes:

TELL CLAUDE CODE
Install these dependencies and wire up a basic client + server setup:
- @supabase/supabase-js and @supabase/ssr for auth + DB
- @anthropic-ai/sdk for AI calls
- zod for input validation
Add .env.local.example with all needed env vars (NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY, SUPABASE_SERVICE_ROLE_KEY, ANTHROPIC_API_KEY). Create src/lib/supabase/{client,server}.ts singletons.
claude
3.4

Verify it boots

Create a Supabase project (free tier) and an Anthropic API key, then fill in your and run the dev server:

IN YOUR TERMINAL
cp .env.local.example .env.local
# fill in the 4 values
pnpm dev
sh

Visit localhost:3000. You should see the default Next.js landing page. Phase 3 done.

phase 4auth + your data model

Goal: a user can request a magic link, click it, and land signed in — and their first piece of data is protected by RLS. About 45 minutes, split across auth then the data model.

4a · authmagic-link sign-in
4.1

Write a memory file for auth BEFORE building

You're about to build auth. This is exactly the kind of thing future-you will want memory on. Do this first:

TELL CLAUDE CODE
Create memory/MEMORY.md with an entry pointing to project_auth.md, and create memory/project_auth.md using the template from Ch 6. Fill the "Why this feature exists" and "Core invariants" sections based on these decisions: magic-link only (no passwords), 30-day session max, profiles table mirrors auth.users via a trigger.
claude

If CLAUDE.md doesn't already point at memory, update it now.

4.2

Wire the auth surface

TELL CLAUDE CODE
Build magic-link auth using Supabase. Create:
- src/app/login/page.tsx — email input + "Send link" button, POSTs to /api/auth/magic-link
- src/app/auth/callback/route.ts — handles the Supabase redirect, creates a session cookie
- src/middleware.ts — redirects unauthenticated users from /app/* to /login
- src/app/api/auth/magic-link/route.ts — validates email with Zod, calls supabase.auth.signInWithOtp()

Every route handler uses Zod safeParse per our CLAUDE.md rules. Include loading/error states on /login.
claude
4.3

Configure Supabase for local dev

Two browser-side steps. In your Supabase dashboard, open your project and navigate to Authentication → URL Configuration.

IN YOUR BROWSER
  • Site URL: http://localhost:3000
  • Redirect URLs: add http://localhost:3000/auth/callback
4.4

Test the flow

IN YOUR TERMINAL
pnpm dev
# visit localhost:3000/login
# enter your email, click "Send link"
# check inbox, click the link
# you should land on localhost:3000/app (or whatever protected route exists)
sh

Ask Claude to run /feature-check auth — it should catch anything missing (middleware not checking every protected route, sign-out not clearing cookies, etc.).

4b · data modelyour first user-owned table + RLS

Goal: one user-owned table where every user only sees their own rows. Enforced by in the database — not your app code. The table name defaults to what you picked in the capture form.

4.5

Migration + RLS policies

TELL CLAUDE CODE
Create a Supabase migration for [your idea] that:
- adds public.[your_table] (id uuid pk, user_id uuid fk to auth.users, title text, body text, created_at timestamptz default now(), updated_at timestamptz default now())
- enables RLS on public.[your_table]
- adds 4 RLS policies (select, insert, update, delete) — all scoped by auth.uid() = user_id
- adds an index on user_id

Data shape: [your data shape]. Put the migration in supabase/migrations/ and run it against my local dev DB.
claude
4.6

CRUD API + list/detail pages

TELL CLAUDE CODE
Build the [your_table] CRUD surface:
- src/app/api/[your_table]/route.ts (GET list, POST create) — Zod-validated, uses the logged-in user's session
- src/app/api/[your_table]/[id]/route.ts (GET, PATCH, DELETE) — RLS handles auth; return 404 on miss
- src/app/app/page.tsx — list of my [your_table] with a "New" button
- src/app/app/[your_table]/[id]/page.tsx — detail view with Edit / Delete buttons

Use server actions where appropriate. Keep the UI minimal — we'll style later.
claude
4.7

Verify RLS actually works

This is the non-skippable test. Open two browser profiles:

  • Sign in as user A. Create two notes.
  • Sign in as user B (separate email). Create one note.
  • As B, visit /app— you should see B's one note only. Not A's.
  • As B, try to curl A's note ID directly. You should get 404 / 403.
If B can see A's notes, RLS is not wired. Most common cause: your API uses the service role key instead of the user session. Fix immediately — this is exactly the kind of bug that ships silently (Chapter 9, silent bomb #6).
phase 5your core AI feature

Goal: one AI-powered feature wired end-to-end. This chapter uses Summarise as the default — swap in whatever your archetype needs (generate / moderate / draft / auto-curate). About 45 minutes.

5.1

Prompt-from-file + load-test-guard

TELL CLAUDE CODE
Create:
- prompts/summarize.md — a system prompt that returns ONE paragraph (~50 words), plain language, no fluff
- src/lib/prompts.ts — a loadPrompt() helper that reads the file and validates required tokens
- src/lib/ai/helpers.ts — a cachedSystem() wrapper that wraps the prompt in cache_control: ephemeral
- src/lib/load-test.ts — an isLoadTestMode() helper that returns true when process.env.LOAD_TEST === '1'

Follow the prompt-from-file pattern from Ch 10's prompt contract.
claude
5.2

The endpoint + cost tracking

TELL CLAUDE CODE
Build POST /api/[your_table]/[id]/summarize:
- Check auth; RLS handles ownership
- If isLoadTestMode(), return a deterministic mock { summary: "LOAD TEST MOCK" }
- Otherwise: load the [your_table] row body, call anthropic.messages.create with cachedSystem(SUMMARIZE_SYSTEM)
- Log cost to a user_costs table (create migration too — simple: user_id, operation, cost_cents, created_at)
- Return { summary: string }

Export maxDuration = 30 so Vercel doesn't time out.
claude
truncation is the classic cause of half-finished summaries. Cap your system prompt at ~200 tokens and set max_tokens: 300 on the call.
cost calculator · ai focused

What will this AI feature cost per month?

Line itemMathUSD / mo
Anthropic (claude-sonnet-4-7)10 users × 8 calls × (2000 in × $3/M + 400 out × $15/M)$0.96
Monthly totalAI only$0.96 / mo
5.3

Wire the UI button

TELL CLAUDE CODE
On the [your_table] detail page: add a "Summarise" button. On click, POST to /api/[your_table]/[id]/summarize, show a loading state, render the returned summary in a small bordered box above the main body. If the POST fails, show the error in-place.
claude
5.4

Sanity check before moving on

  • Summary makes sense for the note body you wrote
  • Run with LOAD_TEST=1 pnpm dev — confirm you get the mock response, not a billed call
  • Check your Anthropic console — one successful call appears. Cost logged in the user_costs table

Then run /feature-check summarize. The 12 silent bombs from Ch 9 will all be considered. Anything flagged, fix now — not later.

phase 6polish + launch prep

Goal: the things every beginner skips that shipping actually requires — legal pages, error monitoring, analytics, and basic SEO. End with a realistic monthly cost estimate for your stack. About 30 minutes.

6.1

Legal pages (privacy + terms)

You are collecting emails. That alone means you need a privacy policy (GDPR / CCPA minimum), and terms if you're charging. These are not optional, but the template route is fine for v1.

TELL CLAUDE CODE
Generate two pages for [your idea]:
- src/app/privacy/page.tsx — a plain-language privacy policy. Cover: what data we collect (email for magic-link auth, user-owned rows in our database, minimal analytics), how long we keep it, third parties (Supabase for hosting, Anthropic for AI calls, our email provider), user rights (export, delete), and a contact email.
- src/app/terms/page.tsx — terms of service. Cover: acceptable use, no warranty, liability cap, governing law placeholder (ask me to fill in my jurisdiction), termination.

Data shape is [your data shape]. Monetization is [your monetization] (adjust terms accordingly). Keep each page under 250 words. Link both from the site footer.
claude
6.2

Error monitoring with Sentry

When something breaks in prod, the first signal should not be a user tweeting at you. Sentry gives you a stack trace in your inbox within seconds.

IN YOUR TERMINAL
pnpm add @sentry/nextjs
npx @sentry/wizard -i nextjs --saas
sh
TELL CLAUDE CODE
Audit the Sentry wiring in this repo:
- Confirm sentry.client.config.ts, sentry.server.config.ts, and sentry.edge.config.ts exist
- Ensure NEXT_PUBLIC_SENTRY_DSN is in .env.local.example
- Tag every captureException with user.id (when authed) and the operation name
- For the [your idea] app specifically, wrap the AI endpoint in Sentry.startSpan so slow calls surface as performance issues

Show me the diff before applying.
claude
Sentry's free tier is generous (5k errors/mo). If you outgrow it, a $26/mo Team plan covers most indie apps. Factor that in below.
6.3

Analytics (Plausible default)

A single pageview script that respects privacy, doesn't need a cookie banner, and doesn't slow the site down. Plausible is the default here; the alternatives GoDeeper covers Posthog (for product analytics) and GA4 (free but heavier).

TELL CLAUDE CODE
Wire Plausible Analytics into src/app/layout.tsx:
- Script tag with defer, data-domain set to the prod domain (read from NEXT_PUBLIC_SITE_URL)
- Only include in production (skip on localhost)
- No cookies, no consent banner needed
- Add a /api/health endpoint that Plausible can hit for uptime

The app is [your idea] — a [your archetype]. Prefer lightweight over feature-rich.
claude
6.4

SEO essentials

You are not going to rank for generic keywords. But people who land on your page via Twitter, a friend's share, or a Product Hunt launch should see a real page title, a real meta description, a real Open Graph image. That means: sitemap, robots.txt, and a metadata object on every route.

TELL CLAUDE CODE
Add SEO essentials to the [your idea] app:
- src/app/sitemap.ts (Next.js-native) listing every public route
- src/app/robots.ts allowing all, pointing at sitemap
- A Metadata export on every public route (landing, /privacy, /terms) with title, description tailored to [your idea], and an og:image reference
- A single /public/og-image.png placeholder (1200x630) — tell me what tool to generate one in
- Update src/app/layout.tsx Metadata base with the prod domain

Keep titles under 60 chars and descriptions under 160 chars.
claude
6.5

How much will this cost you monthly?

Now that you know the pieces (Vercel + Supabase + Anthropic + domain + maybe Sentry), look at the realistic monthly cost at your expected user scale. This is the number to keep in your head when you're thinking about pricing.

cost calculator · full stack

Your full monthly stack cost

Line itemMathUSD / mo
Anthropic (claude-sonnet-4-7)10 users × 8 calls × (2000 in × $3/M + 400 out × $15/M)$0.96
Vercel · Hobby100 GB-hours, 100k serverless invocations, 100 GB bandwidth$0.00
Supabase · Free500 MB database, 1 GB file storage, 50k monthly active users$0.00
Monthly totalVercel + Supabase + Anthropic$0.96 / mo
If your expected cost is $X/mo and you're charging $Y/user, you need X / Y users just to break even. A useful reality check before Phase 8.
phase 7deploy + domain

Goal: live on a real URL, real magic-link auth, a first real user able to sign up. About 30 minutes (+15 if you're pointing a custom domain at it).

7.1

Pre-flight checklist

TELL CLAUDE CODE
Run /ship notes-ai. This should chain:
- /feature-check across auth, notes, and summarize
- /theme-check on the diff (if you support light mode)
- /security-audit (secrets, CORS, RLS)

Fix any blockers. Don't deploy on yellow.
claude
7.2

Deploy to Vercel

TELL CLAUDE CODE
Guide me through deploying to Vercel:
1. Push to a new GitHub repo
2. Import to Vercel (dashboard)
3. Set env vars: NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY, SUPABASE_SERVICE_ROLE_KEY, ANTHROPIC_API_KEY
4. Deploy
5. Tell me what to add to Supabase Auth → URL Configuration so magic-link redirects work on the prod domain
claude
7.3

Update Supabase for production

Back in the Supabase dashboard:

IN YOUR BROWSER
  • Go to Authentication → URL Configuration.
  • Update Site URL to your Vercel production domain (e.g. https://notes-ai.vercel.app).
  • Add your production /auth/callback path to Redirect URLs.
Forget this step and your magic links will email users — but clicking them redirects to localhost:3000 instead of prod. One of the most common first-deploy fails.
7.4

Point a custom domain at it (optional)

Vercel's <project>.vercel.appis fine for day one, but a real domain is ~$12/year and the single biggest credibility bump you'll make. Buy it from a good registrar (Porkbun or Cloudflare — avoid GoDaddy), then point DNS at Vercel.

IN YOUR BROWSER
  1. In Vercel → your project → Settings → Domains → add your new domain. Vercel shows you the exact DNS records it needs.
  2. In your registrar's DNS panel, add either:
    • An A record on @pointing to Vercel's IP (shown in the dashboard), or
    • A CNAME on www pointing to cname.vercel-dns.com.
  3. Wait for propagation (usually a few minutes; up to 24h in worst cases). Vercel auto-provisions the SSL cert once DNS resolves.
  4. Update Supabase Site URL + Redirect URLs again — this time with the new custom domain — or magic links will send users to the .vercel.app URL.
  5. Update NEXT_PUBLIC_SITE_URL env var in Vercel to the new canonical domain and redeploy.
If the padlock in the browser isn't green within 10 minutes, it's almost always DNS — dig your-domain.comshould return Vercel's CNAME target. If it returns your registrar's parking IP, the records haven't saved or propagated.
7.5

The smoke test that matters

In an incognito window on your phone or a different machine:

  • Sign up with a fresh email.
  • Create a row.
  • Hit your AI feature button. Confirm a real response comes back.
  • Sign out, sign back in — your data is still there. (Confirms auth + DB wiring survives a clean session.)

If every step works: you shipped. Tick Phase 7. Close the laptop. Text a friend.

phase 8monetize — skipped for the MVP path

You picked the MVP path (free, no payments in v1). That's the right call for validating an idea — you'll learn more from shipping and watching real users than from a pricing page. Monetization is a separate skill, worth learning deliberately when you have a product to charge for.

When you're ready, change the capture form's monetization field at the top of the chapter, and this phase will unlock the full Stripe walkthrough.

you built it

Every primitive used, real app shipped

Zoom out at what just happened: in five phases you used every primitive in the course. gave Claude your rules. preserved auth decisions. /plan kept architecture from drifting. /feature-check caught silent bombs. Hooks (if you wired any) stopped bad edits cold. Skills made the repeatable bits repeatable.

Chapter 9 is next — a 12-bomb sanity check you'll run against the thing you just built (and every future app). It's short. Then Chapter 10 is optional advanced material if you want to go deeper.

exercisestry this in your own repo0/11