From Lovable to 100K MAU: the six-week rebuild we'd do again

July 24, 2026engineering15 min read

100K monthly active users is where many vibe-coded stacks start showing their real shape. It is rarely raw traffic, Vercel and Supabase can technically serve it. It is spiky dashboard load, serverless connection bursts, RLS policies doing sequential scans on large tables, request handlers pretending to be job queues, type drift between client and server, and an auth schema nobody can safely migrate because it is load-bearing for the live app.

We have rebuilt enough of these to know the path is short and unglamorous. Six weeks. Next.js App Router on the front, Drizzle as the schema source of truth, Supavisor in front of Postgres, Inngest for jobs, Sentry from day one. This post is the week-by-week plan, the numbers behind every decision, and the gotchas that bite teams trying to do it themselves.

Why 100K MAU breaks Lovable-shaped apps

Lovable, Bolt, and v0 share the same ceiling: they get you about 70% of the way to production. The remaining 30% is complex business logic, edge cases in authentication, payment flows, and data validation, exactly the parts that hold up under real users. The common output we inherit is a Vite + React SPA that calls Supabase directly from useEffect hooks with the anon key, default Supabase Auth templates, no real job system, and no observability beyond the browser console.

That stack ships an MVP in a weekend. It also fails in five predictable ways once traffic shows up:

  • Connection storms. Supabase connection limits depend on compute size and connection path. A Micro project currently lists 60 direct connections and 200 pooler clients; Large lists 160 direct and 800 pooler clients. Serverless code can still stampede those limits if every request opens its own connection.
  • Unindexed RLS. A naive user_id = auth.uid() policy can force Postgres into bad plans if user_id is not indexed. Supabase's own RLS benchmark on a 100K-row table shows indexing the policy column taking one test from 171ms to under 0.1ms.
  • Background work in request handlers. Sending an email from a Next.js route is not a job queue. Vercel function limits now vary by runtime, plan, and Fluid Compute settings, but the failure mode is the same: user latency, retry ambiguity, duplicate side effects, and poor visibility.
  • Type drift. Vibe-coded apps lean on any. The codegen step gets skipped. Server and client disagree on shapes silently.
  • Security debt. Do not publish a vulnerability percentage unless you can name the scan and methodology. The source-safe version is still ugly: GitGuardian's 2026 secrets report says about 29M new hardcoded secrets hit public GitHub commits in 2025, and AI-assisted commits leaked secrets at roughly 2x the public baseline.

Imagine you are scaling fast: a TikTok mention or one good B2B launch thread sends thousands of users into a private dashboard at 9pm on a Tuesday. The pooler client limit starts climbing, every dashboard query waits behind slow RLS, one unhandled error in a React component takes the whole app down because nobody added an error boundary, and Sentry is not installed so you find out by reading angry replies the next morning. That is the failure mode we get hired to fix.

If you send this post to your engineer, ask for three numbers before week 1 starts: current peak concurrent database connections, p95 dashboard query time under an authenticated test user, and the auth-provider bill at 100K monthly active users.

The good news: the Lovable app is the spec. The domain model is encoded. The UI is a reference implementation. The team is small. That is exactly the setup that makes a six-week rebuild possible, and a 90-day build unnecessary.

The six-week plan

The plan below is composite, synthesised from a dozen rescues we have done and from public postmortems like the OnboardingHub Rails rewrite (8 weeks, 727 commits, 25-45 hours of human effort because the architecture document was reverse-engineered from a working product). We compress it to six because the spec already exists and we do not let the team drift into "while we are here" rewrites.

The six-week clock only works under three constraints: keep the auth provider, keep the core product scope, and treat the existing app as the spec. Change any two of those and this becomes a 90-day rebuild.

Week 1: Audit, freeze, forklift

Run a dependency and security audit on the Lovable export. Identify hardcoded secrets, missing error boundaries, missing RLS, missing indexes. Forklift the Vite + React SPA into a Next.js App Router skeleton, components stay, data-fetching dies. Keep the Supabase project live and untouched; clients still talk to it. Set up a parallel staging Supabase project for destructive testing.

Decisions made in week 1, written down:

  • What stays: UI components, copy, brand, route structure.
  • What dies: every supabase.from(...) call inside a useEffect, every client-side join, every RPC glued together with any types.
  • What gets added immediately: per-route error boundaries, Sentry SDK with session replay, a basic CI pipeline (typecheck, lint, unit, Playwright on preview).

Sentry goes in week 1, not week 5. You will not find half of the bugs without session replay because most AI-generated code does not handle session expiration mid-flow.

Week 2: Schema as source of truth

Pull the live Supabase schema into a Drizzle file. Drizzle wins over Prisma here for one specific reason: Prisma's drift detector can trip on Supabase auth schema changes, and that is a long-running open issue. With Drizzle, the application schema lives in TypeScript and there is no separate Prisma-style client-generation step. You still generate and review migrations; you just remove one common source of type drift.

Then the database cleanup:

  • Add an index on every column referenced inside an RLS policy. Every one.
  • Wrap auth.uid() in (select auth.uid()) so the optimiser caches the result via initPlan instead of running it per-row.
  • Replace cross-table joins inside RLS policies with denormalised tenant columns.
  • Split read policies (RLS) from write policies (server-only via service role).

The performance delta is not subtle. Supabase's published RLS tests show 100x-plus improvements from indexing policy columns, and some function-wrapping cases move from seconds to single-digit milliseconds. Do not copy those numbers into your investor deck; use them as the reason to run EXPLAIN (ANALYZE, BUFFERS) on your own top five authenticated queries.

Week 3: API layer, type safety, edge

Replace ad-hoc client calls with a tRPC router or typed Server Actions. The point is not religious, it is that tRPC eliminates the API contract drift class of bug entirely, and Server Actions do the same for simple mutations. Reads move to React Server Components. Public-cacheable list routes get ISR with Cache-Control: s-maxage=60, stale-while-revalidate=300.

One gotcha worth circling: the edge runtime is not compatible with ISR. Use the Node runtime for ISR routes and reserve edge for middleware (auth checks, geo, A/B). Mixing them up costs a day of debugging cache misses.

Target by end of week 3: 80%+ cache-hit ratio on public routes. That single number eats more Postgres load than any tier upgrade.

Week 4: Auth, RLS audit, background jobs

The auth decision is the one founders agonise over and the one with the clearest answer at this scale. As of May 2026, the source-safe table looks like this:

Provider100K-user framingWhat we would do
Supabase AuthPro is $25/month and currently includes 100K MAU; overage starts after that quota.Keep it if the product already uses Supabase and does not need enterprise SSO right now.
ClerkCurrent public pricing advertises 50K monthly retained users included, then $0.02 per additional retained user in the next band, plus the Pro base and feature add-ons.Use it when the product needs Clerk's UI, session, org, or enterprise-connection features enough to justify the bill.
Auth0The public page exposes self-serve tiers and custom user tiers; a clean 100K B2C price is not safe to publish without a live quote.Quote it directly if procurement, enterprise identity, or compliance is the reason you are moving.

Keep Supabase Auth at 100K MAU unless you have a specific reason not to. Clerk can be worth the spend if its user-management features save you weeks of product work, but the decision is no longer "Clerk is cheap below 50K and impossible above it." The decision is whether you want a dedicated auth product enough to pay for it as retained users, orgs, SSO, domains, and support needs grow. Migrating between providers is painful, it touches every authenticated route, every session token format, every password reset email. Do not do an auth migration during a six-week rebuild unless auth is the actual reason for the rebuild.

Then move all background work, emails, embeddings, webhook fan-out, retries, out of Next.js handlers and into Inngest. If you are deploying serverless, BullMQ is the wrong choice unless you also stand up a separate persistent worker process. Inngest is the default for Vercel-native; Trigger.dev wins for AI workflows with many steps. Either way, this is a 1-day refactor that fixes multiple incident classes at once.

Week 5: Caching, observability, load test

Sentry is already in. Add Axiom or Logtail for structured logs. Skip the full Datadog rollout at this scale unless you already have a team that knows how to run it. The problem in week 5 is not buying the biggest observability platform; it is making errors, traces, logs, retries, and user sessions visible enough for a small team to debug production.

Run a k6 or Artillery load test against staging at 3x expected peak. Tune the Supavisor pooler, transaction mode for short queries, session mode only where you need prepared statements. Confirm CDN cache hit ratios in production-like conditions. Proper CDN configuration is the difference between a 90% hit ratio and a sluggish worldwide experience.

The numbers we want to see leaving week 5:

  • p95 server response time under 200ms on cached routes, under 600ms on uncached.
  • Cache hit ratio above 80% on public list/detail routes.
  • Pooler usage staying comfortably under the documented client limit for your Supabase compute tier under 3x load.
  • Sentry error rate under 0.5% of requests in staging soak tests.

Week 6: Cutover and cleanup

If you are staying on Supabase, the cutover is just deploying the new Next.js app to Vercel or to a Hetzner VPS via Coolify, then flipping DNS. Smoke tests, watch Sentry for 30 minutes, rollback plan armed.

If you are leaving Supabase, the cutover uses Postgres logical replication. The mechanics matter:

  1. Provision target Postgres at the same major version or higher.
  2. Add a primary key (or REPLICA IDENTITY FULL) to every table receiving UPDATE or DELETE.
  3. Open a direct connection to the source, port 5432, not the pooler. Logical replication requires it.
  4. Create publication on source, subscription on target.
  5. Wait for backfill, verify row counts and a sample of recent rows.
  6. At cutover: pause writes (or queue them in Inngest), wait for replication lag to hit zero, flip the connection string, resume writes.
  7. Run the app in dual-read mode for 24h if paranoid; otherwise monitor Sentry and keep a one-command DNS rollback ready.

Total downtime measured in minutes, not hours. Tear down the Lovable preview once the new domain has been live for a week.

Database move: stay or leave

The Supabase question comes up every time. Two paths, pick on lock-in tolerance and budget.

Stay on Supabase, fix what is broken. Cheapest, fastest. Pro is $25/month and currently includes 100K MAU, 8 GB database, and 100 GB storage, with usage-based fees after the included quotas. A Large compute add-on is about $110/month before the paid-plan compute credit if the workload needs dedicated CPU. The Team plan jumps to $599/month, only worth it for SSO, SOC 2, and 28-day log retention. For many 100K-MAU SaaS products, Pro plus a measured compute upgrade and the pooler is enough.

Leave Supabase for managed Postgres (Neon, Render, RDS, Hetzner-hosted). Reasons that justify it: cost predictability above ~250K MAU, escaping the auth schema, or needing extensions Supabase does not ship. The migration mechanic is the logical replication recipe above. Do not leave for the sake of leaving: the auth schema alone makes it a multi-week project.

Either way, put the right pooler in front of Postgres before the cutover. Supabase includes Supavisor, but the usable ceiling is tied to your compute tier: current public pricing lists 200 pooler clients on Micro, 800 on Large, 3,000 on 4XL, and only reaches five figures on the largest compute tiers. The right lesson is not "Supavisor gives every app 10,000 connections." The lesson is that transaction pooling protects Postgres from serverless spikes, and you should test the added latency from your region and workload.

The cost envelope

Two real-world bills for the same Next.js + Postgres + auth + jobs stack at three traffic tiers. These ranges were checked in May 2026 and reflect SSR-heavy versus static-heavy workloads, team size, included quotas versus overages, and whether someone is paid to own the VPS.

Layer1K MAU10K MAU100K MAU
Hosting (Vercel Pro)Free-$20$20-60$100-500+
Hosting (Hetzner + Coolify)$4-10 app server$7-25 app server$20-80 app hosting only
Supabase DB/platformFree$25 Pro$25 Pro + compute if needed
Supabase Auth incremental$0$0$0 while under Pro's included 100K MAU
Clerk, if replacing authFreeUsually free unless paid features are neededAbout $1,025+ for 100K retained users before org/SSO/domain add-ons
Background jobs (Inngest)FreeFree if under Hobby limits$75+ once Pro limits are needed
SentryFree-$26$26$26-80+ before event overages
Axiom logsFreeFree-$25$25+ depending ingest and retention

A founder running Vercel Pro + Supabase Pro + Clerk at 100K retained users is no longer automatically looking at the old $2,000-plus auth math. Under current public Clerk pricing, a simple 100K retained-user calculation is closer to $1,025 before orgs, SSO, satellite domains, Business features, and overages. The same product on Supabase Auth avoids that incremental auth line while it stays under the Pro included quota. That gap is still real. It is just not a fake 10x spreadsheet you should defend without reading the pricing pages.

The bandwidth piece is worth pulling out with the right caveat: Vercel Pro currently includes 1TB of Fast Data Transfer, then starts at $0.15/GB. Hetzner EU cloud servers include at least 20TB and charge about €1.00 / $1.20 per extra TB in EU and US regions. That comparison only matters after included buckets and regional rules, but for 500K+ MAU on a media-heavy app, the bill can diverge fast.

Our default recommendation for a post-Lovable rebuild at 100K MAU: start on Vercel Pro + Supabase Pro + Supabase Auth + Inngest + Sentry. Move to Hetzner + Coolify when the Vercel bill is painful enough to fund the operational work, or when you have someone on the team who actually wants to own infra. Not before.

RLS gotchas we see every time

Most Lovable exports we have rebuilt have at least one unindexed RLS column and at least one of the following landmines:

  • Default-off. New Supabase tables ship with RLS disabled. Lovable-generated tables often inherit this. Run the Security Advisor in week 1, fix every flagged table, redeploy.
  • Empty-result silent failures. Enable RLS but forget the policies and every query returns empty results, no error, just a dashboard showing nothing. This is the single most common "the app is broken" report we get.
  • Superuser blind spot. The SQL Editor runs as the postgres superuser, which bypasses RLS. You test queries there, see the expected results, ship, and real users see nothing. Test policies with set role authenticated or via the actual app under a test user.
  • UPDATE needs SELECT. To run an UPDATE under RLS, you also need a corresponding SELECT policy. Without it, the UPDATE silently fails for the user.
  • Read via RLS, write via service role. Cleanest production pattern: route mutations through a server with the service role key, keep RLS only on the read path. This only works if the server owns authorization checks and the service role key never leaves the server, because that key bypasses RLS.

What we would not do again

A short list, paid for in our own incidents:

  1. Auth migration during a rebuild. Migrate auth in its own quarter. Six weeks is not enough room.
  2. Cache-after-scaling Postgres. ISR and edge cache can shed most read traffic on public routes. That is cheaper than reaching for a bigger Postgres tier first.
  3. Background jobs in API routes. A function timeout setting is not a job queue. Move to Inngest in week 4, it pays itself back in week 5.
  4. Skipping observability until "later." Sentry installs in 20 minutes. The first time a paying customer reports a bug you did not see, you will wish it had been week 1.
  5. Treating the schema as a side effect. Pin it in Drizzle. Generate migrations. Review them in PRs. The database is the product.

Six weeks is a rebuild, not a rewrite

The reason this works in six weeks is the same reason it does not work in six weeks for a greenfield product: the Lovable app is the spec. The domain model is fixed. The UI is a reference. The decisions that take a discovery sprint on a new product, what does the dashboard show, what is in a user record, what triggers an email, are already answered. The job is to take those answers and put them on a stack that survives the next 10x.

If your Lovable, Bolt, or v0 app caught traction and is starting to wobble at 100K MAU, this is the path we would walk you down. We have rebuilt a dozen of these in six weeks. Same team that does the rebuild stays for the engineering work after, day one to scale, no handoffs.

Book a call. Bring your Supabase project, your Sentry alerts, and your last invoice. We will tell you in 30 minutes whether the right move is six weeks of rebuild or six months of rewrite.