The goal isn’t a perfect architecture. It’s getting to paying customers before you run out of motivation.

Every technology choice in this stack optimizes for one thing: speed to revenue. Generous free tiers mean $0 infrastructure cost until you have customers. Managed services mean no ops burden. Agent-friendly patterns mean you can build faster than ever.

The Stack

Backend: Convex

  • Database, functions, real-time subscriptions, file storage
  • TypeScript end-to-end with generated types
  • Built-in job queues via workpools
  • Free tier: 1M function calls/month, 1GB storage

Frontend: Vite + React

  • Fast builds, instant HMR
  • Prerender marketing pages for SEO
  • SPA for authenticated routes
  • Free (open source)

Auth + Billing: Clerk

  • Authentication with social logins, MFA
  • Billing v2 handles Stripe integration
  • Multi-tenant organizations
  • Free tier: 10k MAUs

Components: shadcn/ui

  • Copy-paste components, not a dependency
  • Tailwind-based, fully customizable
  • Free (open source)

Hosting: Cloudflare Pages

  • Global CDN, automatic SSL
  • Deploy from GitHub in seconds
  • Free tier: unlimited sites, unlimited bandwidth

Email: Resend

  • Transactional email with React templates
  • Simple API, good deliverability
  • Free tier: 3k emails/month
The pattern

Every component is a managed service with a generous free tier. You pay nothing until you have paying customers, then costs scale with revenue.

Why These Choices

Convex Over Firebase/Supabase

Convex feels like the backend React developers actually wanted. Functions are just TypeScript. Queries are reactive by default. The type safety is real, not bolted on.

// Define a query
export const getSquiggles = query({
  args: { userId: v.id("users") },
  handler: async (ctx, { userId }) => {
    return await ctx.db
      .query("squiggles")
      .withIndex("by_user", (q) => q.eq("userId", userId))
      .order("desc")
      .collect();
  },
});

// Use it in React - automatically reactive
const squiggles = useQuery(api.squiggles.getSquiggles, { userId });

The workpool component handles background jobs with retry logic. I use it for AI generation tasks that might fail or timeout.

Firebase is mature but shows its age. Supabase is good if you want Postgres. Convex is what you pick when you want to move fast and trust the abstractions.

Vite Over Next.js

Next.js is overkill for most SaaS apps. The complexity cost is real: RSC mental overhead, build-time surprises, deployment constraints.

Vite is simple. It builds fast. It deploys anywhere.

The SEO objection is valid but overblown. For a SaaS:

  • Marketing pages (/, /pricing, /about) need SEO
  • App routes behind auth don’t

Prerender the marketing pages at build time. Use vite-ssg or Vike if you need more control. The app itself is a plain SPA.

Clerk Over Auth0/Cognito

Clerk’s developer experience is unmatched. Drop in the component, auth works.

<ClerkProvider>
  <SignedIn>
    <App />
  </SignedIn>
  <SignedOut>
    <SignInButton />
  </SignedOut>
</ClerkProvider>

Billing v2 is the real unlock. Clerk handles Stripe integration, subscription management, pricing tables. You focus on your product, not payment infrastructure.

The pricing table is a component:

<PricingTable />

Webhooks sync subscription changes to your backend. Free tier, pro tier, enterprise tier: just check the JWT claims.

Cloudflare Over Vercel

Vercel is excellent but expensive at scale. Cloudflare Pages is free for static sites with no bandwidth limits.

Deploy flow:

  1. Build with Vite
  2. Deploy Convex backend
  3. Deploy static files to Cloudflare Pages
- run: npm run build
- run: npx convex deploy --yes
- run: npx wrangler pages deploy dist --project-name ${{ secrets.PROJECT_NAME }}

Three commands. Zero infrastructure management.

Agent-First Development

The stack matters less than how you build with it. Agents change the calculus.

Beads for Memory

Beads (bd) is Steve Yegge’s memory system for coding agents. Install it, point your AGENTS.md/CLAUDE.md at it, and Claude suddenly tracks work across sessions.

bun add beads
bd init

Every task gets an ID, dependencies, an audit trail. Not scattered markdown files. Addressable work items that agents can query and update.

The difference is stark. Without Beads, every session starts fresh. With Beads, Claude remembers what was decided, what failed, what’s next.

Ralph Wiggum for Autonomous Loops

Ralph Wiggum is the official Claude Code plugin for autonomous development. Give it a prompt, let it run for hours.

/ralph-wiggum:ralph-loop "Work on the next open bead. After completing each bead, run /headless:test to verify. When all beads are closed, respond with: all beads done" --max-iterations 50 --completion-promise "all beads done"

Claude picks up the next issue from Beads, implements it, runs headless browser tests to verify, closes the bead, moves to the next. The loop continues until all work is done or iterations run out.

This works for batch operations: migrations, test coverage, feature backlogs. For judgment-heavy work, you still want human-in-the-loop.

Browser Automation for Testing

Claude in Chrome closes the dev loop. Build in terminal, Claude tests in your actual browser with your sessions and logins intact.

For CI and parity testing, Headless agents run parallel Playwright sessions. Compare legacy site to migration target. Run E2E tests without maintaining Selenium scripts.

Visual verification catches agent lies. Make Claude prove changes work by comparing screenshots to references.

The Harness Pattern

Agent harnesses formalize progress tracking. Anthropic published their patterns: progress files, feature lists, session protocols.

For a SaaS build, this means:

  • AGENTS.md with project context
  • Beads for issue tracking
  • Structured prompts that reference prior work
  • Git commits as checkpoints

The agent doesn’t start fresh each session. It reads the progress docs, checks the issue tracker, picks up where it left off.

Real Example: Squigglify

I built Squigglify with this stack. Draw a squiggle, Gemini completes it as a kid-style drawing.

Tech decisions:

  • Convex for real-time squiggle storage and user management
  • Workpool for queued Gemini API calls with retry logic
  • Clerk for auth with free/pro tiers via Billing v2
  • Cloudflare Pages for global hosting
  • React + Konva for the drawing canvas

What I didn’t build:

  • Payment infrastructure (Clerk handles it)
  • User management UI (Clerk components)
  • Background job system (Convex workpool)
  • CDN configuration (Cloudflare defaults)

Total infrastructure cost: $0. The only variable cost is API calls to Gemini via OpenRouter.

Draw a squiggle, AI completes it. The entire backend is 200 lines of Convex functions.

Trade-offs

This stack optimizes for speed and cost. Trade-offs exist.

Convex lock-in: The abstractions are opinionated. Migrating to Postgres later means rewriting queries. For most SaaS apps, you’ll never need to migrate. If you’re building the next Figma, think harder.

No SSR for dynamic content: Pure SPA means authenticated pages aren’t server-rendered. Fine for SaaS apps. Not fine for SEO-critical dynamic content (marketplaces, user-generated content sites).

Clerk ceiling: 10k MAUs free, then $25/month + per-MAU pricing. If you have 10k active users, you can afford it. If you’re building a free consumer app expecting millions of users, auth costs add up.

Agent dependency: The workflow assumes AI coding assistance. Without agents, the speed advantage diminishes. You’re still building on good technology, but the 10x multiplier comes from agent-assisted development.

Getting Started

  1. npx create-vite@latest my-saas --template react-ts
  2. npx convex init
  3. Add Clerk provider, configure auth
  4. npx shadcn@latest init, add components as needed
  5. Connect Cloudflare Pages to your repo

Write AGENTS.md documenting your project structure. Initialize Beads with bd init. Tell Claude what you’re building.

The stack handles infrastructure. Agents handle implementation. You handle product decisions.

That’s the game now.