# Prisma in dev: "too many clients already" after a few saves

> Next.js hot reload re-runs your module and builds a new PrismaClient every time. Cache one instance on globalThis and the connection leak stops.

You start the dev server, everything works. You edit a file, save, edit, save.
Ten minutes later the page throws:

```
PrismaClientInitializationError:
Error querying the database: FATAL: sorry, too many clients already
```

Or, on a hosted Postgres:

```
Error: Can't reach database server at db.example.com:5432
```

Restarting the dev server fixes it. For about ten minutes. Then it comes back.

Nothing about your query changed. What changed is how many database
connections your laptop is holding open.

## Why it happens

Next.js dev mode does hot module replacement. When you save a file, the module
graph that depends on it is invalidated and re-evaluated. That is the whole
point: it is why your change appears without a full restart.

Now look at the code almost every tutorial shows:

```ts
// src/db/prisma.ts, the version that leaks
import { PrismaPg } from "@prisma/adapter-pg";
import { PrismaClient } from "@/generated/prisma/client";

export const prisma = new PrismaClient({
  adapter: new PrismaPg({ connectionString: process.env.DATABASE_URL }),
});
```

`new PrismaClient()` is a module-level side effect, and a PrismaClient is not a
thin object. Since Prisma 7 every client runs on a driver adapter, and the
adapter owns a connection pool: node-postgres opens up to 10 connections by
default.

Every re-evaluation of this module constructs another one. The old client is no
longer referenced by your code, but its sockets are still open: the pool is
lazy about closing, the garbage collector has no idea it is holding an
expensive external resource, and nothing calls `$disconnect()`. So the
connections accumulate.

On a laptop Postgres with the default `max_connections = 100`, a pool of 10
connections means you get roughly ten saves before the server refuses new
ones. On a free hosted tier with a limit of 20 or 30, you get two or three.

This is also why the bug is so confusing: it is time-and-edit-dependent, not
input-dependent. It never reproduces in a test, never happens in production
(where the module is evaluated once per instance), and disappears the moment
you restart to investigate.

## The fix

Cache the client on `globalThis`. Hot reload replaces modules; it does not
replace the global object. So the second evaluation finds the client the first
one made and reuses it.

```ts
// src/db/prisma.ts
import { PrismaClient } from "@/generated/prisma/client";
import { createAdapter } from "./driver"; // PrismaPg or PrismaNeon, one file

const globalForPrisma = globalThis as unknown as {
  prismaClient?: PrismaClient;
};

function createClient(): PrismaClient {
  return new PrismaClient({
    adapter: createAdapter(),
    log: process.env.NODE_ENV === "production" ? ["error"] : ["warn", "error"],
  });
}

export const prisma: PrismaClient = globalForPrisma.prismaClient ?? createClient();

if (process.env.NODE_ENV !== "production") {
  globalForPrisma.prismaClient = prisma;
}
```

Three details worth understanding rather than copying:

**The cast exists because `globalThis` is typed as having no such property.**
Casting through `unknown` to a one-property shape keeps the rest of the global
object honest, instead of reaching for `any`.

**`??` not `||`.** A falsy-but-present client is not a thing here, but the
nullish operator states the intent exactly: reuse if defined.

**The write is guarded by `NODE_ENV !== "production"`.** In production the
module is evaluated once per serverless instance or once per server process,
so the cache buys nothing, and parking a client on the global object outlives
the code that wants it. The read is unguarded on purpose: it is harmless, and
keeping it symmetrical invites someone to "simplify" the guard away.

## Import it, and only it

The singleton only helps if it is the only client. One `new PrismaClient()`
hidden in a route handler, a test helper or a script that the dev server also
loads brings the leak straight back.

```ts
// anywhere in the app
import { prisma } from "@/db/prisma";

const user = await prisma.user.findUnique({ where: { id } });
```

Worth grepping for before you close the issue:

```bash
grep -rn "new PrismaClient" src/ scripts/ prisma/
```

The only legitimate hit is inside `src/db/prisma.ts`.

## What about `$disconnect()`?

You will find advice to call `prisma.$disconnect()` after each request. Do not
do that in a long-lived server or a serverless function. Disconnecting drops
the pool, so the next request pays full connection setup (including the TLS
handshake) before it can run a query. Prisma's own guidance is to let the
client live as long as the process does.

`$disconnect()` belongs in one-shot scripts: a seed, a backfill, a verify
script. Those need it, otherwise the process hangs with an open pool and never
exits.

```ts
// prisma/seed.ts
import { prisma } from "../src/db/prisma";

async function seed() {
  // ...
}

seed()
  .then(() => prisma.$disconnect())
  .catch(async (error) => {
    console.error(error);
    await prisma.$disconnect();
    process.exit(1);
  });
```

## Confirming it worked

Ask the database how many connections you are holding, then save a file five
times and ask again. The number should not move:

```sql
select count(*), application_name
from pg_stat_activity
where datname = current_database()
group by application_name;
```

If it climbs by a pool's worth per save, something is still constructing
clients. If it climbs by 1 per save, you probably have a second module doing
the same thing with a different variable name.

## The same shape, elsewhere

Any expensive client with a connection pool or a background timer has this
problem in Next.js dev: Redis, a Kafka producer, a Postgres `Pool` from `pg`, a
websocket client, a metrics agent. The `globalThis` cache is the standard fix
for all of them, and it is worth recognising the pattern rather than
remembering it once per library.

---

Agentic Boilerplate: A Next.js repo your agent already knows. Free during launch, then $99 once.

- Site map for agents: https://agenticboilerplate.com/llms.txt
- Public API: https://agenticboilerplate.com/openapi.json
- Contact: agenticstudio@gmail.com
