Skip to content

Next.js

Tested with: Next.js 15.5 · React 19 · pg 8.13 · ioredis 5.4 · @libsql/client 0.14

lib/db.ts
import "server-only";
import { Pool } from "pg";
const globalForPg = globalThis as unknown as { pool?: Pool };
export const pool =
globalForPg.pool ??
new Pool({
connectionString: process.env.DATABASE_URL, // pooled endpoint on serverless, direct on a long-running server
max: process.env.VERCEL ? 3 : 10,
ssl: { rejectUnauthorized: true }, // verify-full: chain + hostname via the OS trust store
});
if (process.env.NODE_ENV !== "production") globalForPg.pool = pool;

import "server-only" guarantees the module (and the credential) never reaches a client bundle. Use it from Server Components, Route Handlers and Server Actions:

app/page.tsx
import { pool } from "@/lib/db";
export default async function Page() {
const { rows } = await pool.query<{ now: string }>("select now()");
return <p>Server time: {rows[0]?.now}</p>;
}

Database access needs the Node runtime. Add export const runtime = "nodejs" to routes that touch Postgres, Valkey or FerretDB; only libSQL (@libsql/client/web) works on the Edge runtime.

lib/cache.ts
import "server-only";
import Redis from "ioredis";
const g = globalThis as unknown as { redis?: Redis };
export const redis =
g.redis ??
new Redis(process.env.VALKEY_URL!, { tls: { rejectUnauthorized: true }, maxRetriesPerRequest: 2 });
if (process.env.NODE_ENV !== "production") g.redis = redis;

VALKEY_URL is the rediss:// URI from the instance’s Connect tab.

VariableValueWhere
DATABASE_URLpooled URI with sslmode=verify-fullruntime
DIRECT_URLdirect URIprisma migrate / drizzle-kit in CI
VALKEY_URLrediss://app:[email protected]:6379/0runtime