"No team. No funding. No prior SaaS experience. Just me, 3 AI tools, and 30 days. Here's everything โ the wins, the failures, the revenue, and the exact stack I used."
I'm going to tell you something that would've sounded insane 2 years ago. ๐
I built a complete SaaS product. Alone. In 30 days.
Authentication. Database. Payments. Dashboard. Landing page. Email notifications. The whole thing. ๐ช
And I'm not a 10x developer. I'm not some genius. I make mistakes constantly. I Google basic CSS all the time. ๐
The difference? I used AI as my co-founder, my senior dev, my code reviewer, and my rubber duck โ all at once.
This post is the complete story. Day by day. Tool by tool. What worked, what completely failed, and what I'd do differently.
No fluff. No "AI will change everything" vague inspiration. Just the real playbook. ๐
Let's go. ๐
๐ก The Idea โ Day 1
Every SaaS starts with a problem. Mine was embarrassingly simple. ๐
The problem: Developers building with AI APIs had no easy way to test their prompts, compare outputs across models, and track which prompts performed best.
Every time I built an AI feature, I was:
- Hardcoding prompts in my code
- Testing manually by running the app
- Losing track of which version worked best
- Wasting API credits on bad prompts
The solution: A prompt testing playground. Paste your prompt. Test across GPT-4o, Claude, Gemini simultaneously. See outputs side by side. Save the winners. Track performance over time.
Product: PromptLab ๐งช
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Core feature: Test prompts across multiple AI models
Target user: Developers building AI features
Pricing: Free tier (5 tests/day) + Pro ($9/month)
Goal: Launch in 30 days
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Simple idea. Real problem. Let's build. ๐
๐๏ธ Week 1 โ Foundation (Days 1-7)
Day 1-2: Planning with Claude ๐ง
Before writing a single line of code โ I spent 2 days planning with Claude. This saved me at least a week of wrong decisions later.
My conversation with Claude ๐ฌ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Me: "I want to build a prompt testing SaaS.
What's the minimal database schema I need
for the core features?"
Claude: [gave me this schema]
Users
id, email, passwordHash, createdAt, plan
Prompts
id, userId, title, content, createdAt
TestRuns
id, promptId, model, output, latency,
tokensUsed, cost, rating, createdAt
Comparisons
id, userId, promptIds[], createdAt
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Me: "What am I missing for a v1 launch?"
Claude: "You'll need: rate limiting per plan,
API key storage for user's own keys,
and a usage tracking table for billing.
Skip analytics for v1 โ add post-launch."
That conversation saved me from building 3 features I didn't need yet. Claude as an architect = underrated. ๐๏ธ
Day 3-4: Stack Setup โก
# The exact stack I chose (and why) ๐ ๏ธ
npx create-next-app@latest promptlab --typescript --tailwind --app
# Database โ Supabase (free tier, Postgres, auth built-in)
npm install @supabase/supabase-js
# Payments โ Stripe (industry standard, great docs)
npm install stripe @stripe/stripe-js
# Email โ Resend (simple API, generous free tier)
npm install resend
# UI components โ shadcn/ui (free, beautiful, accessible)
npx shadcn-ui@latest init
# Form handling โ React Hook Form + Zod
npm install react-hook-form zod @hookform/resolvers
Why this stack? Every tool has a generous free tier. Everything integrates well with Next.js. Claude knew all of them deeply โ important when you're using AI to help you build. ๐ฏ
Day 5-7: Auth + Database ๐
This is where most solo builders get stuck for weeks. I did it in 3 days using Copilot + Claude together.
// Supabase auth โ Copilot autocompleted most of this โก
// app/auth/callback/route.ts
import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs'
import { cookies } from 'next/headers'
import { NextResponse } from 'next/server'
export async function GET(request: Request) {
const requestUrl = new URL(request.url)
const code = requestUrl.searchParams.get('code')
if (code) {
const supabase = createRouteHandlerClient({ cookies })
await supabase.auth.exchangeCodeForSession(code)
}
return NextResponse.redirect(requestUrl.origin + '/dashboard')
}
-- Database schema โ Claude wrote this from my requirements
-- I just reviewed and tweaked โ
CREATE TABLE prompts (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE,
title TEXT NOT NULL,
content TEXT NOT NULL,
tags TEXT[] DEFAULT '{}',
is_favorite BOOLEAN DEFAULT false,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE test_runs (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
prompt_id UUID REFERENCES prompts(id) ON DELETE CASCADE,
model TEXT NOT NULL, -- 'gpt-4o', 'claude-sonnet', 'gemini-flash'
output TEXT,
latency_ms INTEGER,
tokens_used INTEGER,
cost_usd DECIMAL(10,6),
rating INTEGER CHECK (rating BETWEEN 1 AND 5),
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Row Level Security โ Claude explained WHY this matters ๐
ALTER TABLE prompts ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users own their prompts"
ON prompts FOR ALL
USING (auth.uid() = user_id);
๐ก The AI workflow that worked: Use Claude to design and explain. Use Copilot to write the actual code fast. Use Claude again to review what Copilot wrote. 3 tools, one pipeline. ๐
Week 1 result:
โ
Auth working (login, signup, logout)
โ
Database schema live on Supabase
โ
Protected routes working
โ
Basic dashboard skeleton
Days used: 7 โ
๐๏ธ Week 2 โ Core Feature (Days 8-14)
This was the fun week. Building the actual product. ๐ฅ
Day 8-10: The Prompt Testing Engine
The core feature โ run a prompt against multiple AI models simultaneously.
// app/api/test/route.ts
// The heart of the product ๐งช
import OpenAI from 'openai'
import Anthropic from '@anthropic-ai/sdk'
import { GoogleGenerativeAI } from '@google/generative-ai'
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY })
const anthropic = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY })
const gemini = new GoogleGenerativeAI(process.env.GEMINI_API_KEY!)
export async function POST(req: Request) {
const { prompt, models, variables } = await req.json()
// Replace template variables in prompt ๐
// e.g. "Explain {{topic}} to a {{level}} developer"
const processedPrompt = Object.entries(variables || {}).reduce(
(acc, [key, val]) => acc.replace(`{{${key}}}`, val as string),
prompt
)
// Run all models in PARALLEL โ not sequential! โก
// This was Claude's suggestion โ cuts wait time by 3x
const results = await Promise.allSettled([
models.includes('gpt-4o') ? runOpenAI(processedPrompt) : null,
models.includes('claude') ? runClaude(processedPrompt) : null,
models.includes('gemini') ? runGemini(processedPrompt) : null,
].filter(Boolean))
return Response.json({
results: results.map((r, i) => ({
model: models[i],
success: r.status === 'fulfilled',
output: r.status === 'fulfilled' ? r.value.output : null,
latency: r.status === 'fulfilled' ? r.value.latency : null,
error: r.status === 'rejected' ? r.reason.message : null,
}))
})
}
// Each model runner measures latency ๐
async function runOpenAI(prompt: string) {
const start = Date.now()
const res = await openai.chat.completions.create({
model: 'gpt-4o-mini',
messages: [{ role: 'user', content: prompt }]
})
return {
output: res.choices[0].message.content,
latency: Date.now() - start,
tokens: res.usage?.total_tokens
}
}
async function runClaude(prompt: string) {
const start = Date.now()
const res = await anthropic.messages.create({
model: 'claude-haiku-4-5-20251001', // fast + cheap for testing ๐ฐ
max_tokens: 1024,
messages: [{ role: 'user', content: prompt }]
})
return {
output: res.content[0].type === 'text' ? res.content[0].text : '',
latency: Date.now() - start,
tokens: res.usage.input_tokens + res.usage.output_tokens
}
}
async function runGemini(prompt: string) {
const start = Date.now()
const model = gemini.getGenerativeModel({ model: 'gemini-1.5-flash' })
const res = await model.generateContent(prompt)
return {
output: res.response.text(),
latency: Date.now() - start,
tokens: null // Gemini free tier doesn't return token counts
}
}
Day 11-12: The Comparison UI ๐จ
ChatGPT helped me design the layout. Claude helped me write the component. Copilot wrote the repetitive parts. Classic 3-tool pipeline. ๐
// The side-by-side comparison component
// Copilot autocompleted about 60% of this ๐ค
const ComparisonGrid = ({ results }: { results: TestResult[] }) => {
return (
<div className={`grid gap-4 ${
results.length === 2 ? 'grid-cols-2' :
results.length === 3 ? 'grid-cols-3' : 'grid-cols-1'
}`}>
{results.map((result) => (
<ResultCard key={result.model} result={result} />
))}
</div>
)
}
const ResultCard = ({ result }: { result: TestResult }) => {
const [rating, setRating] = useState(0)
const [copied, setCopied] = useState(false)
const handleCopy = async () => {
await navigator.clipboard.writeText(result.output || '')
setCopied(true)
setTimeout(() => setCopied(false), 2000)
}
return (
<div className="border rounded-xl p-4 space-y-3 bg-white shadow-sm">
{/* Header */}
<div className="flex justify-between items-center">
<div className="flex items-center gap-2">
<ModelBadge model={result.model} />
<span className="text-xs text-gray-400">
{result.latency}ms โก
</span>
</div>
<button onClick={handleCopy} className="text-xs text-gray-400">
{copied ? 'โ
Copied' : '๐ Copy'}
</button>
</div>
{/* Output */}
<div className="text-sm leading-relaxed text-gray-700 min-h-[120px]">
{result.success ? result.output : (
<span className="text-red-400">โ {result.error}</span>
)}
</div>
{/* Rating */}
<div className="flex gap-1 pt-2 border-t">
<span className="text-xs text-gray-400 mr-1">Rate:</span>
{[1,2,3,4,5].map(star => (
<button
key={star}
onClick={() => setRating(star)}
className={`text-lg ${star <= rating ? 'text-yellow-400' : 'text-gray-200'}`}
>
โ
</button>
))}
</div>
</div>
)
}
Day 13-14: Rate Limiting ๐ก๏ธ
This nearly broke me. Claude saved the day. ๐
// app/api/test/route.ts โ adding rate limiting
// Claude designed this entire system when I was stuck ๐
import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs'
const LIMITS = {
free: 5, // 5 tests per day
pro: 500, // 500 tests per day
}
async function checkRateLimit(userId: string, plan: string) {
const supabase = createRouteHandlerClient({ cookies })
// Count today's tests for this user
const today = new Date()
today.setHours(0, 0, 0, 0)
const { count } = await supabase
.from('test_runs')
.select('*', { count: 'exact', head: true })
.eq('user_id', userId)
.gte('created_at', today.toISOString())
const limit = LIMITS[plan as keyof typeof LIMITS] || LIMITS.free
return {
allowed: (count || 0) < limit,
used: count || 0,
limit,
remaining: Math.max(0, limit - (count || 0))
}
}
Week 2 result:
โ
Multi-model testing working
โ
Side-by-side comparison UI
โ
Rating system
โ
Rate limiting by plan
Days used: 14 โ
๐๏ธ Week 3 โ Payments + Polish (Days 15-21)
Day 15-17: Stripe Integration ๐ณ
I was dreading this. Stripe docs are good but overwhelming. Claude summarized exactly what I needed. Copilot wrote it.
// app/api/create-checkout/route.ts
// Stripe checkout โ Claude explained the flow, Copilot wrote the code
import Stripe from 'stripe'
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!)
export async function POST(req: Request) {
const { userId, email } = await req.json()
const session = await stripe.checkout.sessions.create({
payment_method_types: ['card'],
mode: 'subscription',
customer_email: email,
line_items: [{
price: process.env.STRIPE_PRO_PRICE_ID, // created in Stripe dashboard
quantity: 1,
}],
metadata: { userId }, // pass userId to webhook ๐
success_url: `${process.env.NEXT_PUBLIC_APP_URL}/dashboard?upgraded=true`,
cancel_url: `${process.env.NEXT_PUBLIC_APP_URL}/pricing`,
})
return Response.json({ url: session.url })
}
// app/api/webhooks/stripe/route.ts
// Handle payment success โ update user's plan in DB
export async function POST(req: Request) {
const body = await req.text()
const sig = req.headers.get('stripe-signature')!
let event: Stripe.Event
try {
event = stripe.webhooks.constructEvent(
body, sig, process.env.STRIPE_WEBHOOK_SECRET!
)
} catch {
return new Response('Webhook signature invalid', { status: 400 })
}
// When payment succeeds โ upgrade the user ๐
if (event.type === 'checkout.session.completed') {
const session = event.data.object as Stripe.CheckoutSession
const userId = session.metadata?.userId
if (userId) {
await supabase
.from('users')
.update({ plan: 'pro' })
.eq('id', userId)
}
}
return new Response('OK')
}
Day 18-19: Landing Page ๐จ
I used ChatGPT for copywriting. Fastest landing page I've ever built.
My ChatGPT prompt for copy ๐
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
"Write landing page copy for a SaaS called PromptLab.
It helps developers test AI prompts across multiple
models side by side.
Write:
- Hero headline (under 8 words, punchy)
- Hero subheadline (1-2 sentences)
- 3 feature benefit pairs
- 2 pricing tier descriptions
- FAQ (5 questions)
- CTA button text
Tone: Direct. Developer-friendly. No buzzwords."
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Output in 15 seconds:
Headline: "Test Once. Ship the Best Prompt."
Subheadline: "Run any prompt across GPT-4o, Claude, and Gemini
simultaneously. See outputs, latency, and cost โ
side by side."
Day 20-21: Email Notifications ๐ง
// Welcome email with Resend โ 10 lines ๐
import { Resend } from 'resend'
const resend = new Resend(process.env.RESEND_API_KEY)
export async function sendWelcomeEmail(email: string, name: string) {
await resend.emails.send({
from: 'PromptLab <hello@promptlab.dev>',
to: email,
subject: '๐งช Welcome to PromptLab!',
html: `
<h1>Hey ${name}! ๐</h1>
<p>You're in. Start testing your first prompt:</p>
<a href="https://promptlab.dev/dashboard"
style="background:#6366f1;color:white;padding:12px 24px;
border-radius:8px;text-decoration:none">
Open Dashboard โ
</a>
<p>You get 5 free tests per day. <a href="/pricing">Go Pro</a>
for 500/day.</p>
`
})
}
Week 3 result:
โ
Stripe payments working
โ
Webhooks handling upgrades
โ
Landing page live
โ
Welcome emails sending
Days used: 21 โ
๐๏ธ Week 4 โ Launch (Days 22-30)
Day 22-24: Bug Fixing Marathon ๐
Real talk โ the AI tools shone brightest here. ๐
Bug #1: Supabase RLS blocking dashboard queries
โ Pasted error into Claude
โ Fixed in 8 minutes
Bug #2: Stripe webhook failing in production
โ Claude identified I was missing raw body parsing
โ Fixed in 12 minutes
Bug #3: React state update after unmount warning
โ Copilot suggested the cleanup pattern
โ Fixed in 3 minutes
Without AI: These would've taken 2-3 hours each
With AI: 23 minutes total ๐คฏ
Day 25-27: Performance + SEO ๐
// Claude suggested this optimization I hadn't thought of
// Cache prompt results for identical inputs โ saves API costs ๐ฐ
const CACHE = new Map<string, { result: any; expires: number }>()
async function getCachedOrFresh(cacheKey: string, fetchFn: () => Promise<any>) {
const cached = CACHE.get(cacheKey)
const now = Date.now()
if (cached && cached.expires > now) {
return cached.result // cache hit! โก
}
const fresh = await fetchFn()
CACHE.set(cacheKey, {
result: fresh,
expires: now + 5 * 60 * 1000 // 5 minute cache
})
return fresh
}
Day 28-30: Launch Day ๐
Launch checklist โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Deployed to Vercel production
โ Custom domain connected
โ Stripe in live mode (not test)
โ All environment variables set
โ Error monitoring (Sentry) added
โ Posted on Twitter/X
โ Posted on Dev.to ๐
โ Submitted to Product Hunt
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
๐ The 30-Day Results
Final numbers ๐
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Signups day 1: 47 ๐
Week 1 total users: 183
First paid user: Day 3 ๐ฐ
Month 1 MRR: $127
Month 1 pro users: 14
Code written:
Without AI (estimate): ~3,000 lines
With AI help: ~1,200 lines I actually typed
Time saved: ~3 weeks ๐คฏ
Bugs fixed with AI: 23
Time saved on bugs: ~18 hours
Total build time: 30 days โ
Solo developer: Yes ๐ค
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
$127 MRR in month 1. Not life-changing. But REAL. From a product I built alone in 30 days. That felt insane. ๐
๐ฏ The AI Workflow That Actually Worked
After 30 days, here's the exact system I used: ๐
The 3-Tool Pipeline ๐
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
1. PLAN with Claude
โ Architecture decisions
โ Database schema
โ "What am I missing?"
โ Security review
2. BUILD with Copilot
โ Writing actual code fast
โ Autocomplete patterns
โ Boilerplate generation
3. REVIEW with Claude
โ Paste what Copilot wrote
โ "Any issues? Any edge cases?"
โ Performance optimization
โ Bug investigation
4. COPY with ChatGPT
โ Landing page copy
โ Email templates
โ Error messages
โ Documentation
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Each tool does what it's best at. That's the system. ๐ฏ
๐ก What I'd Do Differently
Honest mistakes โ learn from mine: ๐
โ Mistake 1: Built too many features for v1
Should have launched with just the core test feature.
2 extra features nobody asked for = 5 extra days wasted.
โ Mistake 2: Didn't talk to users before building
Spent 3 days building a "prompt history" feature.
First 20 users: nobody used it.
Should've asked first.
โ Mistake 3: Trusted AI blindly on security
Copilot wrote auth middleware that had a subtle bug.
Always review security-critical code yourself.
AI is a tool, not a safety net. ๐
โ
What worked perfectly:
Claude for architecture = 10/10
Copilot for speed = 10/10
ChatGPT for copy = 10/10
Shipping imperfect v1 = best decision I made
๐ฌ Your Turn!
Have you built or tried to build a SaaS solo? ๐
What was the hardest part? Drop it in the comments โ I'll give specific advice for each! ๐
And if you're thinking about your own SaaS idea but haven't started โ drop your idea below. Sometimes just writing it out is the first step. ๐ก
Drop a โค๏ธ if this made a solo SaaS feel achievable โ it genuinely is, with the right tools! ๐
Go build something. The best time was yesterday. Second best time is now. ๐
๐ P.S. โ The 3-Tool Pipeline (Plan with Claude โ Build with Copilot โ Review with Claude โ Copy with ChatGPT) works for any project, not just SaaS. Screenshot it. Use it.
Top comments (0)