Query budgets for server-rendered pages
A page that runs forty queries is slow even when every query is fast. Count them.
The invisible multiplier
When a server-rendered page is slow, the first instinct is to look for a slow query. Often there is none. Every individual query takes a few milliseconds, and the page is slow because it runs forty of them, many sequentially, each paying a network round trip to the database.
The N plus one problem
The classic cause is loading a list and then loading related data for each item separately. A blog index fetches twenty posts, then fetches the author for each post, then the tags for each post. Twenty posts become forty-one queries. The code looks clean, because each step is simple, and the cost only appears in aggregate.
The fix is to batch: fetch all the authors for all twenty posts in one query, and all the tags in another. Most data access libraries offer a way to do this, whether through joins, eager loading, or a batching loader.
Set a budget
Decide how many queries each page type is allowed. A listing page might be allowed five; a detail page three. Log the query count for every request, and alert or fail tests when a page exceeds its budget. Like a bundle size budget, this turns a slow drift into a visible, discussable change.
Parallelize what is independent
If a page needs the post, the site navigation, and the related posts, and none depends on the others, fetch them at the same time. Three parallel queries take as long as the slowest one, not the sum of all three. Many server-rendering frameworks make it easy to start several fetches and await them together.
Distance matters
Each query pays the network latency between the application server and the database. If they are in the same data center, that is a fraction of a millisecond. If they are in different regions, it can be fifty milliseconds or more, and forty sequential queries become two seconds. Keep rendering close to the data, or reduce the number of round trips sharply.
Cache the stable parts
Some data changes rarely: site settings, navigation menus, category lists. Cache it in memory or in a fast shared store for a short time rather than querying it on every request.
Measure in production
Query counts in development, with a tiny dataset, can differ from production. A page that loads five items locally might load a hundred in production, and a hidden per-item query multiplies accordingly. Record query counts and durations per route in production logs.
The result
Teams that adopt query budgets often cut server render times in half without touching a single slow query, simply by doing fewer, larger, parallel fetches.