
Connect Next.js to a database through an ORM (Prisma or Drizzle) paired with a connection pooler, not a direct connection string. Serverless deployments spin up a new function instance for every burst of traffic, and each instance opening its own database connection is exactly how a Postgres database with a default 100-connection limit gets exhausted during a normal traffic spike.
Why This Is Harder Than It Looks
A traditional server keeps a persistent connection pool alive: 10–20 connections serve thousands of requests because they’re reused. Next.js deployed to Vercel, AWS Lambda, or similar platforms doesn’t get that luxury — each function invocation is stateless, connection pools can’t be shared across invocations, and under load, hundreds of simultaneous invocations can each try to open a fresh connection at once. This is the single most common production incident for teams deploying Next.js with a traditional database driver.
Step 1: Pick an ORM
Prisma and Drizzle are the two dominant choices for a Next.js project in 2026, and the decision mostly comes down to deployment target and bundle size:
- Prisma generates a type-safe client from a schema definition language, manages migrations through its own CLI, and offers Prisma Accelerate for HTTP-based global connection pooling on serverless and edge deployments. It’s the gentler learning curve and the more batteries-included option, at the cost of a considerably larger client bundle.
- Drizzle writes SQL-like queries directly in TypeScript with no query-interpretation layer in between, ships a client roughly a tenth the size of Prisma’s, and has stronger native support for serverless-first databases like Neon, Turso, and Cloudflare D1. For teams deploying to the edge or optimizing cold-start time, it’s increasingly the lighter default.
Both support PostgreSQL, MySQL, and SQLite; Prisma adds native support for MongoDB and SQL Server, while Drizzle leans harder into edge-native databases. Neither choice is wrong — they optimize for different constraints.
Step 2: Use a Singleton Client
In development, Next.js’s hot-reload can re-instantiate your database client on every file save unless you guard against it, quietly leaking local connections. The standard fix is a global singleton:
const globalForPrisma = globalThis;
export const prisma = globalForPrisma.prisma ?? new PrismaClient();
if (process.env.NODE_ENV !== "production") {
globalForPrisma.prisma = prisma;
}This ensures the same client instance is reused across hot-reloads locally, and a fresh instance is created cleanly per cold start in production.
Step 3: Put a Pooler Between Next.js and the Database
This is the step that actually solves the connection exhaustion problem, and it’s not optional in a serverless deployment:
- PgBouncer is the traditional standalone connection pooler — it sits between your application and Postgres, queuing and reusing connections so hundreds of function invocations share a much smaller real connection count.
- Managed poolers from your database provider (Supabase’s session pooler, Neon’s built-in pooling) achieve the same result without self-hosting infrastructure — generally the simpler starting point.
- HTTP-based drivers like
@neondatabase/serverlesssidestep the problem differently, connecting over HTTP instead of a long-lived TCP connection, which removes the pooling requirement entirely for compatible databases.
For Prisma specifically, keep both a pooled connection string (for the running application) and a direct connection string (for running migrations) in your environment variables — migrations should bypass the pooler.
Step 4: Fetch Data From Server Components, Mutate With Server Actions
The App Router’s model changes where database code actually lives. Reads typically happen directly inside Server Components, which can query the database during render without an API layer in between. Writes go through Server Actions, which handle the mutation and revalidation in one function instead of a separate API route plus a client-side fetch.
Production Checklist
- Use
migrate deploy(Prisma) or generated SQL migrations (Drizzle) in production — neverdb pushor schema sync commands meant for local development. - Set an explicit, conservative connection limit for serverless (Prisma’s guidance starts at
connection_limit=1and scales up from measured need, not a guess). - Index for your actual query patterns, not preemptively for every column — unused indexes cost write performance for no read benefit.
- Monitor pool utilization in production; connection exhaustion under real traffic looks fine in every local test and only shows up under concurrent load.
The Bottom Line
Connecting Next.js to a database isn’t just an ORM choice — it’s a connection-management problem that only shows up under real traffic. Pick Prisma or Drizzle based on your deployment target and bundle constraints, put a pooler or an HTTP driver between your app and the database from day one, and keep reads in Server Components and writes in Server Actions so the App Router’s model works with you instead of around a bolted-on API layer.
Prisma vs Drizzle: Quick Comparison
| Prisma | Drizzle | |
|---|---|---|
| Client bundle size | Larger | Roughly a tenth the size |
| Query style | Generated type-safe client | SQL-like queries in TypeScript |
| Serverless/edge pooling | Prisma Accelerate (HTTP-based) | Strong native support for Neon, Turso, Cloudflare D1 |
| Database support | Postgres, MySQL, SQLite, MongoDB, SQL Server | Postgres, MySQL, SQLite, edge-native databases |
| Best fit | Teams wanting a batteries-included, gentler learning curve | Edge deployments and teams optimizing cold-start time |
Should I use Prisma or Drizzle for a new Next.js project?
Prisma if you want a gentler learning curve and batteries-included tooling. Drizzle if you are deploying to the edge or optimizing cold-start time, since its client is roughly a tenth the size of Prisma’s.
Why does my database run out of connections in production but not locally?
Local testing rarely produces the concurrent load that causes connection exhaustion. In production, serverless platforms spin up many function instances under real traffic, and each one opening its own database connection is what exhausts the limit — a connection pooler or HTTP-based driver fixes this.
Do I need a connection pooler if I use an HTTP-based driver?
No. HTTP-based drivers like @neondatabase/serverless connect over HTTP instead of a long-lived TCP connection, which removes the pooling requirement entirely for compatible databases.