Auth & Data
Neon Serverless Client Patterns
Use when wiring the Neon serverless driver into a Next.js app with Drizzle, especially when the build must succeed in environments with no DATABASE_URL. Covers lazy client construction, the HTTP versus pooled tradeoff, and edge versus node runtime. Also use when a build fails at import time on a missing connection string, or a migration needs the direct rather than the pooled URL. If a database client is constructed at module scope, use this skill.
1 file
Description
Use when wiring the Neon serverless driver into a Next.js app with Drizzle, especially when the build must succeed in environments with no DATABASE_URL. Covers lazy client construction, the HTTP versus pooled tradeoff, and edge versus node runtime. Also use when a build fails at import time on a missing connection string, or a migration needs the direct rather than the pooled URL. If a database client is constructed at module scope, use this skill.
name: neon-serverless-patterns description: Use when wiring the Neon serverless driver (@neondatabase/serverless) into a Next.js app with Drizzle, especially when the build must succeed in environments without DATABASE_URL. Covers lazy client construction, the HTTP vs pooled tradeoff, and edge vs node runtime.
Neon Serverless Client Patterns
When to trigger
Standing up the first DB connection in a Next.js app deployed where the build step may not have secrets (Vercel build, a CI without env), or debugging a build that crashes on a missing DATABASE_URL.
The build must not need the database
A top-level neon(process.env.DATABASE_URL!) throws at import time when the var is absent, which crashes next build. Defer construction until the first query with a lazy proxy so import is always safe:
import { neon } from "@neondatabase/serverless";
import { drizzle, type NeonHttpDatabase } from "drizzle-orm/neon-http";
import * as schema from "./schema";
let cached: NeonHttpDatabase<typeof schema> | null = null;
function getDb() {
if (cached) return cached;
const sql = neon(process.env.DATABASE_URL ?? "postgresql://placeholder@localhost/placeholder");
cached = drizzle(sql, { schema });
return cached;
}
export const db = new Proxy({} as NeonHttpDatabase<typeof schema>, {
get: (_t, prop) => (getDb() as Record<string | symbol, unknown>)[prop as string],
});
The placeholder URL keeps import from throwing; queries lazy-fail at runtime instead, which is the correct place for a missing-secret error.
HTTP vs pooled vs direct
- The
neon()HTTP driver is one round trip per query, ideal for serverless and edge functions with no persistent connection to leak. - Use the pooled connection string for app queries. Use the direct (non-pooled) string for migrations that take advisory locks.
- The HTTP driver does not support interactive transactions across multiple statements. For a multi-statement transaction use
neon's transaction helper or the WebSocketPooldriver.
Pitfalls
- A non-null assertion on
process.env.DATABASE_URLat module top level is the single most common cause of a build that passes locally (where.env.localexists) and fails in CI. - The HTTP driver's
transaction()batches statements but is not a general interactive transaction. Do not rely on running arbitrary logic between statements inside it. - Edge runtime cannot use the node
Pool(TCP) driver. Keep the HTTP driver for edge routes.
Related
Added 2026-07-01. Back to the Skill Library.

Get Smarter About AI Dev
New tutorials, open-source projects, and deep dives on coding agents - delivered weekly.