My Page Loads Thousands of Rows at Once, Should I Add Pagination?
6 min read
If your page fetches every row in a table on load and it has started to feel slow, then yes, you should add pagination before real users arrive. The direct answer: stop loading the whole table and load one page at a time. A query that returns 50 rows stays fast whether the table holds 100 records or a million. A query that returns everything gets slower with every signup.
Why this happens
Your AI agent built exactly what you asked for: "show the list of orders." The
simplest thing that works is select * from orders, render all of them, done.
In the demo the table had twelve rows, so it was instant. Nobody told the agent
the table would one day have twenty thousand rows, so nobody put a limit on the
query. It works, it shipped, and the slowness only appears once real data piles
up, which is precisely when your first users are watching.
The cost is paid three times over: the database reads every row, the network ships every row, and the browser renders every row into the page. Each one gets heavier as the table grows, and the browser part is often the worst.
How to check
- Open your browser dev tools, go to the Network tab, and reload the slow page. Find the request that fetches your data and look at its size and time. A response measured in megabytes, or a request taking more than a second, is the smoking gun.
- Look at the query in your code. Search for the fetch behind that page. If it
has no
limit,range, or.take()on it, you are pulling the whole table. - Count the rows. In Supabase, run
select count(*) from your_table. In Firebase, check the collection size. If it is already in the thousands and climbing, you are past the point where loading everything is fine.
The fix
Pick one of three patterns. All three rest on the same idea: ask the database for a small slice, not the whole thing.
- Numbered pages (best for tables and dashboards). Fetch a fixed page size
and offer next/previous. In Supabase:
.range(0, 49)for the first 50 rows,.range(50, 99)for the next. Order the query so the pages are stable, for example.order('created_at', { ascending: false }). - A "Load more" button (best for feeds). Fetch the first page, and when the user clicks, fetch the next range and append it. Simple, predictable, and the user stays in control.
- Infinite scroll (best for social-style feeds, more work to get right). Fetch the next page automatically as the user nears the bottom. Only reach for this if the experience genuinely calls for it.
- Order by an indexed column. Pagination that sorts on an unindexed column
can still be slow because the database sorts the whole table to find your
page. If you sort by
created_at, make sure that column has an index. See why a slow Supabase query usually needs an index. - Show a count without fetching everything. If you need "1,240 results," ask the database for the count separately rather than downloading every row to count them in the browser.
The trap to avoid
Do not "fix" this by fetching all the rows and then paginating in the browser
with JavaScript. That hides the symptom and keeps the disease: the database and
network still move the entire table on every load, and the browser still holds
all of it in memory. Real pagination means the database returns only the
page you asked for. If your query still says select * with no range, you have
not fixed it, you have only added buttons.
Where this fits
Loading everything is one of the quietest performance traps in a vibe-coded app, because it is invisible until your data grows, which is exactly when beta users show up. It often travels with a related problem, a single page firing hundreds of database queries, and the two together can make a page crawl. The free Readiness Report runs against your real project and flags the pages that pull unbounded data before a user finds them the hard way. If you would rather have the fixes handed to you in order, that is what the Finishing Pass is for.