Learn how to configure PgBouncer and Supabase connection pools to prevent database timeouts in serverless Next.js apps.
Every PostgreSQL connection consumes roughly 5 to 10 MB of RAM on your database server. In a traditional Node.js deployment running on a single long-lived container, your server opens 10 to 20 persistent connections and shares them across requests. But modern platforms like Vercel and AWS Lambda spin up new container instances for every concurrent request.
If 400 users hit your web app simultaneously, 400 isolated serverless functions launch. Each tries to open a direct PostgreSQL connection. If your database instance has a max_connections limit set to 100, 300 requests instantly fail with connection rejected or fatal: remaining connection slots are reserved errors. We resolved this exact issue when building [Aura Estate](/projects/15), a high-traffic real estate platform where property searches and valuation queries execute under heavy concurrent traffic.
A connection pooler acts as a lightweight proxy between your application and your PostgreSQL database. Instead of your serverless workers talking directly to port 5432, they connect to the pooler on port 6543. The pooler keeps a fixed, healthy set of open database connections (for example, 15 to 25 connections) and queues or reallocates them instantly as incoming web requests arrive.
When using an ORM like Prisma or Drizzle in serverless production, you need two distinct connection strings in your environment variables: one for migration runs and one for daily application traffic.
import { drizzle } from 'drizzle-orm/postgres-js';
import postgres from 'postgres';
// Transaction pooler connection string (Port 6543)
// Add ?pgbouncer=true or prepare=false to prevent prepared statement errors
const connectionString = process.env.DATABASE_URL!;
// Configure client with max 1 connection per serverless function instance
const client = postgres(connectionString, {
max: 1,
idle_timeout: 20,
connect_timeout: 10,
prepare: false, // Required for PgBouncer / Supabase transaction mode
});
export const db = drizzle(client);Notice the prepare: false flag. Because PgBouncer in transaction mode assigns queries to different backend connections dynamically, prepared statements cached on connection A will fail if a follow-up query runs on connection B. Turning off client-side statement preparation ensures zero runtime errors.
To keep your database fast and avoid timeouts, use these practical settings in your database configuration:
If your application is experiencing database crashes, connection timeouts, or slow query spikes under high traffic, I can help audit and optimize your database tier. Check out my [Full-Stack Web Development Services](/services) or [get in touch with Aafaque](/contact) to build a stable, scalable infrastructure.
Each serverless function instance opens its own TCP connection. Under sudden traffic spikes, hundreds of concurrent instances hit the database at once, exceeding PostgreSQL max_connections and crashing the instance with out-of-memory errors.
Session mode holds a database connection for the full lifetime of a client connection, supporting prepared statements and session variables. Transaction mode returns the connection to the pool as soon as a single transaction completes, allowing hundreds of serverless requests to share a tiny pool of connections.
Use port 5432 for running database schema migrations and long batch jobs via Prisma or Drizzle. Use port 6543 (transaction mode) for production serverless routes, API endpoints, and web traffic.