Why Is My App Making Hundreds of Database Queries to Load One Page?
6 min read
If one page in your app fires off hundreds of database queries, the usual cause is the N+1 query problem: your code loads a list, then runs one extra query for every item in that list instead of fetching everything at once. Load 200 items and you get 1 query for the list plus 200 more, one per row. The fix is to fetch the related data in a single query. That is the whole answer, and the rest of this explains how to spot it and collapse it.
Why this happens
An AI agent writes code that reads cleanly, line by line, and N+1 reads beautifully. "Get the orders, then for each order get the customer name" becomes a loop that queries the customer inside it. It is the obvious way to say what you meant, so the agent wrote it that way. It works perfectly in the demo, because your demo had three orders.
Nobody told the agent that "for each order, go and ask the database again" turns into a stampede once real data shows up. You asked for a page that shows orders with customer names. You got exactly that. You did not ask it to fetch them efficiently, so it didn't. The page still works. It is just slow in a way that gets worse the more successful you are, which is the worst time to find out.
How to check
You do not need to read the code first. You can see this from the outside.
- Watch the query log while you load the page. In Supabase, open the dashboard and go to Logs then Postgres (or the Query Performance view). In Firebase, check the usage tab. Refresh the slow page once and watch the count. One page load producing dozens or hundreds of near-identical queries is the signature.
- Look at the queries themselves. N+1 shows up as the same statement
repeated over and over with only the id changing:
select * from customers where id = 1,... id = 2,... id = 3. Rows of that pattern are the tell. - Time it as data grows. If the page is quick with 5 records and crawls with 100, and the time climbs roughly in step with the row count, that is N+1, not a one-off slow query.
The fix
- Fetch related data in one query, not in a loop. Instead of looping over
orders and querying each customer, ask the database for all of them together.
On Supabase this is a nested select:
.from('orders').select('*, customers(name)')returns each order with its customer in a single round trip. In a SQL ORM (Prisma, Drizzle) use the include/relation option, for example Prisma'sinclude: { customer: true }. - Or load the lookups in one batch. Collect every customer id from the
list, then run one query with
where id in (...)and match them up in code. This turns N+1 into exactly 2 queries regardless of list size. - Re-run the check from the "How to check" step. The same page load should now show 1 or 2 queries, not hundreds. If it still fans out, one of your loops is still querying inside itself. Find the query that repeats and lift it out.
- Add an index on the columns you filter and join on (the
idanduser_idstyle columns). It will not fix N+1 by itself, but once you are down to one query, an index keeps that query fast as the table grows.
The trap to avoid
The tempting wrong fix is to make the slow page load anyway: add a spinner, a loading skeleton, or caching on top. That hides the symptom and leaves the stampede in place. It also tends to break under real traffic, because caching a query that hammers the database just means the database gets hammered a little less often. Collapse the queries first. Cache a fast page, never a slow one.
Where this fits
A page that runs fine in your demo and buckles the moment a real user has real data is one of the most common things that goes wrong right after launch, and it is invisible until someone hits it. The free Readiness Report runs your own agent against your real project and flags pages that fan out into query storms before your users feel them. If you would rather have the rewrites handed to you in order, that is what the Finishing Pass is for. It sits alongside the data-layer checks in can anyone read my Supabase database from the browser and what the anon key can do: same database, different ways it surprises you in front of real users.