Most Next.js SaaS founders ship their first feature, watch it work fast, and move on. Then at some point -- maybe 500 users, maybe 50 -- dashboard pages start taking 800ms. You add a few Drizzle relations, the query count triples. Now it is 1.5 seconds per load.
The standard answer is Redis. But Redis on Upstash or Railway adds $20-50 per month, another service to monitor, and cache invalidation bugs that are subtle and hard to debug. For most SaaS products at the early growth stage, you do not need it.
Here is the caching strategy that covers the most common bottlenecks in a Next.js SaaS without adding a single external service.
Before you cache anything, know where the time actually goes:
Each one has a different fix. Caching is only one of them.
Before you add any caching, check whether your server components are waiting for each query to finish before starting the next one. This is the most common cause of slow pages and the easiest fix.
// Slow -- each query waits for the previous one
const user = await getUser(userId)
const posts = await getPostsByUser(userId)
const stats = await getStats(userId)
// Fast -- all three run at the same time
const [user, posts, stats] = await Promise.all([
getUser(userId),
getPostsByUser(userId),
getStats(userId),
])
If your page makes three independent database calls, parallelizing them can cut load time by two thirds with no infrastructure change at all.
unstable_cache for Expensive QueriesMost Drizzle queries bypass the built-in Next.js fetch cache because they use db.select() directly instead of fetch(). The fix is unstable_cache, which wraps any async function in a server-side cache with a TTL you control:
import { unstable_cache } from 'next/cache'
export const getDashboardStats = unstable_cache(
async (userId: string) => {
return db.select({ count: sql<number>`count(*)` })
.from(postTable)
.where(eq(postTable.authorId, userId))
},
['dashboard-stats'], // cache key prefix
{ revalidate: 60 } // seconds until stale
)
The first request is slow. Every request after is instant until the TTL expires or you call revalidateTag('dashboard-stats') on a write.
Use it for: aggregates, counts, dashboard summaries, anything expensive that does not change on every user action.
Skip it for: user-specific data that must always be fresh -- payment status, settings, unread notifications.
Serverless functions on Vercel open a new database connection on every invocation. With 20 concurrent users, that is 20 simultaneous connection attempts to Neon. Cold connections add 50-200ms per request, and they compound on every page that runs multiple queries.
Neon ships a built-in connection pooler at a separate connection string (the URL ends in -pooler.neon.tech). Switching your DATABASE_URL to the pooled endpoint is a one-line environment variable change that removes cold-connection overhead across your entire app. The deployment guide at /docs/deployment walks through this alongside the environment variable setup on Vercel.
If you want to go deeper on the Vercel deployment flow itself, the post on deploying your Next.js SaaS to Vercel covers migrations and environment variables in detail.
When a page is slow, work through this in order:
Promise.all first.unstable_cache with a short TTL.revalidateTag if the data lives in a Next.js route.Most SaaS products never need Redis until they are generating meaningful revenue. The stack in the Next.js SaaS Boilerplate -- Neon DB with pooling, Drizzle ORM, and Next.js server components -- gives you enough headroom to grow without it.
When you do hit the ceiling, the signals are specific:
None of those are day-one problems. When you reach them, the module architecture in the boilerplate makes adding a Redis layer to a single service straightforward -- you do not have to touch your routes or UI components.
Pick the slowest page in your app. Open your browser's network tab and look at the time-to-first-byte. If it is over 500ms, you almost certainly have either sequential queries you can parallelize or an aggregate you can cache for 30 seconds.
Fix that first. For most early-stage SaaS products, those two changes get you to sub-200ms on pages that were taking over a second.
The Next.js SaaS Boilerplate is built so you can make both changes without refactoring your data layer -- get it and ship faster.