faw

swe

Why I Built mf2: A SaaS Monorepo for the Agent Era

February 24, 2026


TL;DR

I built mf2 because every SaaS project I started burned two weeks wiring auth, payments, database, email, and analytics before I wrote a single feature. npx create-mf2-app@latest scaffolds a Turborepo monorepo with 6 apps, 20+ typed packages, and Convex, Clerk, Stripe, PostHog, Sentry, Resend, Vercel AI SDK, and Arcjet pre-wired. Everything lives in typed TypeScript files, not dashboards. Your coding agent reads the schema, follows the imports, and writes correct code on the first try. We're building Cleve.ai on the same stack.


The problem

Every SaaS I've built started the same way. Pick a database. Pick an ORM. Pick auth. Pick payments. Pick email. Pick analytics. Wire them together. Pray the types align across all of them.

Two weeks pass. I haven't written a single line of business logic.

And now there's a second problem: AI agents. I use Claude Code and Cursor for most of my coding. They write good application code when they can see the full picture. They break when half your configuration lives in a Supabase dashboard, an Auth0 admin panel, or a Stripe webhook config page they can't access.

Theo Browne said it well in his stack breakdown: "when everything in your codebase is a file, not some weird config in some dashboard, AI is way better at it." Simon Willison's agentic engineering patterns series argues the same point: writing code costs almost nothing now, so the bottleneck moved to architecture and integration.

mf2 is my answer to both problems at once.


What you get

npx create-mf2-app@latest

One command. Full monorepo.

6 deployable apps

AppPortWhat it does
app3000Main SaaS dashboard (Next.js 15 + App Router)
web3001Marketing / landing pages
api3002Webhooks, cron jobs, integrations
docs3004User documentation (Mintlify)
email3003Email templates (React Email)
storybook6006Component development

Each app deploys independently. One bun run dev starts them all in parallel through Turborepo's dev UI.

20+ typed packages

Every integration is a scoped package under @repo/:

  • @repo/backend — Convex database, queries, mutations, real-time sync
  • @repo/auth — Clerk with OAuth, organizations, and webhook sync to Convex
  • @repo/payments — Stripe via @convex-dev/stripe component
  • @repo/design-system — 50+ shadcn/ui components with dark mode
  • @repo/ai — Vercel AI SDK with multi-model routing
  • @repo/analytics — PostHog event tracking, sessions, experiments
  • @repo/observability — Sentry error tracking + BetterStack logging
  • @repo/email — Resend transactional email
  • @repo/security — Arcjet bot detection and secure headers
  • @repo/rate-limit — Upstash Redis rate limiting
  • @repo/feature-flags — Vercel feature flags with overrides
  • @repo/notifications — Knock in-app notification feeds
  • @repo/collaboration — Liveblocks cursors and presence
  • @repo/cms — BaseHub headless CMS
  • @repo/storage — Convex file storage + Vercel Blob
  • @repo/webhooks — Svix outbound webhook delivery
  • @repo/seo — Metadata, JSON-LD, Open Graph
  • @repo/internationalization — next-intl translations

Each package wraps one concern. Import what you need, ignore what you don't.


Why these choices

Convex over SQL + ORM

This is the biggest departure from the typical stack. No MySQL, no Postgres, no Prisma, no Drizzle, no migrations.

Convex is a TypeScript-native database. You define your schema in a .ts file, Convex generates the types, queries are reactive by default, and there are no migrations. The entire data layer is source code your agent can read.

In Theo's stack video, he walks through his old setup: MySQL on PlanetScale, hit through Prisma/Drizzle, endpoints defined in tRPC, live updates through Pusher, React Query on the client. Convex replaced all five. One system, typed end-to-end.

The tradeoff: no raw SQL. If your project needs complex joins or aggregates, or your team thinks in SQL, Convex is wrong. For a SaaS product where you want real-time data, typed queries, and zero migrations, nothing else I've tried comes close.

Convex ships pre-built components. mf2 includes five out of the box: Stripe integration, Resend email, workflow orchestration, action retries, and migrations.

Clerk over rolling your own

Auth is what I most want out of my codebase. Session management, OAuth flows, organization support, webhook sync to the database — configuration I refuse to maintain. Clerk handles it and syncs user data to Convex via webhooks.

Turborepo + Bun

Turborepo gives you caching, filtered builds, parallel dev servers, and the dev UI. Bun gives you fast package management and the runtime. bun run dev spins up every app through Turborepo's --ui flag with a live terminal dashboard.

Filtered builds matter for agents. When Claude Code makes a change in @repo/auth, I run bun check auth — just that package and its dependents, not the entire monorepo. Fast feedback loops for both humans and agents.


The agent-first design

The core philosophy: if it's not in a .ts file, your agent can't use it.

Every package exports typed interfaces. When an agent sees import { auth } from '@repo/auth', it follows that import to the source, reads the types, and writes correct code. No guessing. No MCP servers bridging dashboard state into the codebase.

The monorepo structure helps agents navigate. Colocation keeps related files together. No barrel files — explicit imports only. A CLAUDE.md at the project root describes the packages, commands, and conventions.

This matters more than people think. I watched Claude Code work with a Supabase project where half the auth config lived in the dashboard. It hallucinated table schemas. It guessed at RLS policies it couldn't see. In mf2, those errors vanish. The schema is convex/schema.ts — a file the agent reads directly.


What's next

mf2 is a web monorepo today. Next: multi-platform.

  • Mobile — React Native + Expo, sharing @repo/* packages
  • Desktop — Electron, same shared packages
  • Browser extension — Plasmo, same shared packages

One backend, multiple platforms, all typed, one repo. @repo/auth, @repo/backend, @repo/ai work the same whether you import them from a Next.js app or a React Native screen.

We're building Cleve.ai on this stack. If the architecture works for a template, it works for production.


Try it

npx create-mf2-app@latest my-app
cd my-app
bun run dev

Open source. Feedback welcome. If something's broken or missing, open an issue.


February 24, 2026