Is your Supabase + Next.js app ready for the traffic spike?
The moment this is for
You're about to do something that concentrates traffic into a short window: launch publicly, run a paid campaign, post to Product Hunt or Hacker News, or hit a seasonal surge (Black Friday, back-to-school, a partner's newsletter). A date — and often a spend — is already committed. A load-readiness check is cheap insurance against that spend buying you a support queue full of timeouts instead of customers.
Why it works at 20 users and falls over at 2,000
None of this shows up in normal development, because normal development never has enough rows or enough concurrent requests to trigger it:
1. N+1 queries
A list page fetches its rows, then issues one more query per row inside the render loop — a related record, a lookup, a count. At 20 rows that's 20 extra queries and nobody notices. At 2,000 rows it's 2,000 queries on one page load, and the page times out.
2. Missing indexes
Supabase does not index foreign keys automatically. A filter or join on an unindexed column does a full table scan — trivial at a few hundred rows, and the first thing to blow your query budget once a table has real volume.
3. Row-Level Security evaluated per row
An RLS policy that calls auth.uid() directly gets re-evaluated once per row scanned. On a 100,000-row table that's 100,000 evaluations for one query — invisible in a demo, expensive under load.
4. Connection-pool exhaustion
Serverless functions that open a fresh Postgres connection per request exhaust Supabase's connection limit under concurrency long before CPU or memory become the bottleneck — a spike-traffic failure mode that a single-user load test cannot reproduce.
5. Unbounded selects and overfetching
A query with no .limit(), or a client component fetching an entire table to filter it in the browser, scales the response payload — and the egress bill — linearly with data size instead of with what the page actually renders.
A quick self-audit
- Does any list page issue a query inside a loop over another query's results?
- Do your foreign-key and filter columns have indexes — checked, not assumed?
- Do your RLS policies wrap
auth.uid()in a subquery, or call it per row? - Are Postgres connections pooled (PgBouncer / Supabase's pooler), not opened per request?
- Does every list query have a
.limit(), or does it hand the client the whole table? - Have you tested any of the above at 10× your current data volume, not just today's?
These are exactly the classes Harvey's Performance module (M7) flags on your source, cross-referenced against the Hotspots module (M3) so the ones sitting in your most complex, most-changed code are ranked first — the files most likely to be touched, and to break, right before launch.
Know what breaks before launch day does.
The free scan flags N+1s, missing indexes, and unbounded selects on your source — no database access needed.