When a Next.js app backed by Postgres feels slow, the first request is usually more infrastructure. A bigger database. A read replica. A cache. A queue. Sometimes those are right. More often, the app is asking Postgres a question that does not match the product screen.
Query shape is the boring performance problem that pays back quickly. The page wants ten published posts with titles, slugs, dates, categories, and a short description. The API returns full rich-text bodies, every relationship, author records, and image metadata. The dashboard wants a count per status. The code loads every row and counts in JavaScript. The search page wants cursor pagination. The query uses offset on a growing table.
Start from the screen
The useful question is not “is Postgres slow?” It is “what does this screen need to render?” Write the fields down. Then compare that list to the query. In Payload CMS and many ORM-backed apps, default population can quietly fetch more than the page needs. That is convenient for development and expensive under traffic.
Select fewer fields. Limit relationships. Paginate at the database. Push filters into SQL. Sort on indexed columns. Avoid loading rich content for cards that only show a title and excerpt. The fastest byte is the one you never ask Postgres to send.
For article listings, this can be the difference between a cheap query and a page that drags a CMS-shaped object graph through the server for every request. A public portfolio site should not pay that cost just to show a list of posts that changes once a week.
Indexes should match real filters
Indexes are not decoration. They should match the filters and ordering the app actually uses. A common blog query filters by published status, orders by published date, and joins categories. A dashboard may filter by organization id and status. A search workflow may filter by tenant, created date, and soft-delete state.
Do not add five speculative indexes because a tool suggested them. Run `EXPLAIN`, inspect the slow query, and add the smallest index that supports the real access pattern. Composite indexes are powerful when the column order matches the query. They are noise when nobody understands why they exist.
After adding an index, verify the plan changed and the endpoint improved. If the query still returns a huge payload or waits on an external API, the index was not the whole problem.
Caching needs an invalidation story
Next.js gives you several cache boundaries, and that is both useful and dangerous. Static generation, fetch caching, tag revalidation, route handlers, and CDN behavior can all help a Postgres-backed app. They can also make content wrong if nobody owns invalidation.
For CMS content, cache the public read path and revalidate when the document changes. For authenticated dashboards, cache stable reference data but keep permission-sensitive data fresh. For expensive AI-generated summaries, cache by input version so edits create a new result instead of serving old analysis.
The mistake is adding Redis before answering what should happen after a write. If a customer updates a project and the dashboard still shows old status, the cache is now part of the bug. A simple database query with the right index is often better than a clever cache nobody trusts.
Connection count is part of the app design
Serverless Next.js deployments can punish sloppy database connection handling. Each function instance may create connections. Traffic spikes can hit pool limits. Long-running queries can hold connections while users wait. A managed pooler helps, but it does not fix a route that does too much per request.
Keep database work short. Batch related reads when the screen needs them together. Avoid N+1 relationship loading. Move slow exports, reports, and AI processing into background jobs. Set timeouts so a bad query fails loudly instead of pinning the pool until the site degrades.
This is also a client-acquisition topic because performance problems are rarely isolated. A slow Postgres-backed Next.js site usually has unclear data ownership, oversized queries, missing indexes, weak cache boundaries, and no production measurement. Fixing the query is the entry point. Fixing the system around it is where the real value is.
The practical audit
Pick the three slowest pages that matter to revenue. Capture their database queries. Compare selected fields to rendered fields. Run the slow plans. Add one index only where the plan proves it. Trim payloads. Set pagination. Decide which route can cache and what invalidates it. Then measure again from the public URL, not only from a local dev server.
Postgres is usually not the weak part of this stack. It is very good at answering precise questions. The job of the Next.js app is to ask precise questions, keep cache promises honest, and avoid turning every page render into a full-content export.