Skip to main content
Watch: Claude Code Just Killed After Effects

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.

neonserverlesspostgresdrizzlenextjs

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.

Install
SKILL.md

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 WebSocket Pool driver.

Pitfalls

  • A non-null assertion on process.env.DATABASE_URL at module top level is the single most common cause of a build that passes locally (where .env.local exists) 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.
AI Development Stack

Get Smarter About AI Dev

New tutorials, open-source projects, and deep dives on coding agents - delivered weekly.

One email per weekReal code, not theoryFree forever