tessl install github:jezweb/claude-skills --skill neon-vercel-postgresgithub.com/jezweb/claude-skills
Set up serverless Postgres with Neon or Vercel Postgres for Cloudflare Workers/Edge. Includes connection pooling, git-like branching, and Drizzle ORM integration. Use when: setting up edge Postgres, troubleshooting "TCP not supported", connection pool exhausted, SSL config errors, or Node v20 transaction issues.
Review Score
90%
Validation Score
12/16
Implementation Score
85%
Activation Score
100%
Status: Production Ready
Last Updated: 2026-01-21
Dependencies: None
Latest Versions: @neondatabase/serverless@1.0.2, @vercel/postgres@0.10.0, drizzle-orm@0.45.1, drizzle-kit@0.31.8, neonctl@2.19.0
Option A: Neon Direct (multi-cloud, Cloudflare Workers, any serverless)
npm install @neondatabase/serverlessOption B: Vercel Postgres (Vercel-only, zero-config on Vercel)
npm install @vercel/postgresNote: Both use the same Neon backend. Vercel Postgres is Neon with Vercel-specific environment setup.
Why this matters:
For Neon Direct:
# Sign up at https://neon.tech
# Create a project → Get connection string
# Format: postgresql://user:password@ep-xyz.region.aws.neon.tech/dbname?sslmode=requireFor Vercel Postgres:
# In your Vercel project
vercel postgres create
vercel env pull .env.local # Automatically creates POSTGRES_URL and other varsCRITICAL:
-pooler.region.aws.neon.tech)?sslmode=require parameterNeon Direct (Cloudflare Workers, Vercel Edge, Node.js):
import { neon } from '@neondatabase/serverless';
const sql = neon(process.env.DATABASE_URL!);
// Simple query
const users = await sql`SELECT * FROM users WHERE id = ${userId}`;
// Transactions
const result = await sql.transaction([
sql`INSERT INTO users (name) VALUES (${name})`,
sql`SELECT * FROM users WHERE name = ${name}`
]);Vercel Postgres (Next.js Server Actions, API Routes):
import { sql } from '@vercel/postgres';
// Simple query
const { rows } = await sql`SELECT * FROM users WHERE id = ${userId}`;
// Transactions
const client = await sql.connect();
try {
await client.sql`BEGIN`;
await client.sql`INSERT INTO users (name) VALUES (${name})`;
await client.sql`COMMIT`;
} finally {
client.release();
}CRITICAL:
sql`...`) for automatic SQL injection protectionsql('SELECT * FROM users WHERE id = ' + id) ❌Choose based on your deployment platform:
Neon Direct (Cloudflare Workers, multi-cloud, direct Neon access):
npm install @neondatabase/serverlessVercel Postgres (Vercel-specific, zero-config):
npm install @vercel/postgresWith ORM:
# Drizzle ORM (recommended for edge compatibility)
npm install drizzle-orm@0.45.1 @neondatabase/serverless@1.0.2
npm install -D drizzle-kit@0.31.8
# Prisma (Node.js only)
npm install prisma @prisma/client @prisma/adapter-neon @neondatabase/serverlessKey Points:
Option A: Neon Dashboard
postgresql://user:pass@ep-xyz-pooler.region.aws.neon.tech/db?sslmode=requireOption B: Vercel Dashboard
vercel env pull to get environment variables locallyOption C: Neon CLI (neonctl@2.19.0)
# Install CLI
npm install -g neonctl@2.19.0
# Authenticate
neonctl auth
# Create project and get connection string
neonctl projects create --name my-app
neonctl connection-string mainCRITICAL:
-pooler.region.aws.neon.tech)?sslmode=require in connection stringFor Neon Direct:
# .env or .env.local
DATABASE_URL="postgresql://user:password@ep-xyz-pooler.us-east-1.aws.neon.tech/neondb?sslmode=require"For Vercel Postgres:
# Automatically created by `vercel env pull`
POSTGRES_URL="..." # Pooled connection (use this for queries)
POSTGRES_PRISMA_URL="..." # For Prisma migrations
POSTGRES_URL_NON_POOLING="..." # Direct connection (avoid in serverless)
POSTGRES_USER="..."
POSTGRES_HOST="..."
POSTGRES_PASSWORD="..."
POSTGRES_DATABASE="..."For Cloudflare Workers (wrangler.jsonc):
{
"vars": {
"DATABASE_URL": "postgresql://user:password@ep-xyz-pooler.us-east-1.aws.neon.tech/neondb?sslmode=require"
}
}Key Points:
POSTGRES_URL (pooled) for queriesPOSTGRES_PRISMA_URL for Prisma migrationsPOSTGRES_URL_NON_POOLING in serverless functionsOption A: Raw SQL
// scripts/migrate.ts
import { neon } from '@neondatabase/serverless';
const sql = neon(process.env.DATABASE_URL!);
await sql`
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
)
`;Option B: Drizzle ORM (recommended)
// db/schema.ts
import { pgTable, serial, text, timestamp } from 'drizzle-orm/pg-core';
export const users = pgTable('users', {
id: serial('id').primaryKey(),
name: text('name').notNull(),
email: text('email').notNull().unique(),
createdAt: timestamp('created_at').defaultNow()
});// db/index.ts
import { drizzle } from 'drizzle-orm/neon-http';
import { neon } from '@neondatabase/serverless';
import * as schema from './schema';
const sql = neon(process.env.DATABASE_URL!);
export const db = drizzle(sql, { schema });# Run migrations
npx drizzle-kit generate
npx drizzle-kit migrateOption C: Prisma
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("POSTGRES_PRISMA_URL")
}
model User {
id Int @id @default(autoincrement())
name String
email String @unique
createdAt DateTime @default(now()) @map("created_at")
@@map("users")
}npx prisma migrate dev --name initCRITICAL:
CRITICAL - Template Tag Syntax Required:
// ✅ Correct: Template tag syntax (prevents SQL injection)
const users = await sql`SELECT * FROM users WHERE email = ${email}`;
// ❌ Wrong: String concatenation (SQL injection risk)
const users = await sql('SELECT * FROM users WHERE email = ' + email);Neon Transaction API (Unique Features):
// Automatic transaction (array of queries)
const results = await sql.transaction([
sql`INSERT INTO users (name) VALUES (${name})`,
sql`UPDATE accounts SET balance = balance - ${amount} WHERE id = ${accountId}`
]);
// Manual transaction with callback (for complex logic)
const result = await sql.transaction(async (sql) => {
const [user] = await sql`INSERT INTO users (name) VALUES (${name}) RETURNING id`;
await sql`INSERT INTO profiles (user_id) VALUES (${user.id})`;
return user;
});Vercel Postgres Transactions:
sql.connect() + manual BEGIN/COMMIT/ROLLBACKclient.release() in finally block (prevents connection leaks)Drizzle Transactions:
await db.transaction(async (tx) => {
await tx.insert(users).values({ name, email });
await tx.insert(profiles).values({ userId: user.id });
});Connection String Format:
Pooled (serverless): postgresql://user:pass@ep-xyz-pooler.region.aws.neon.tech/db
Non-pooled (direct): postgresql://user:pass@ep-xyz.region.aws.neon.tech/dbWhen to Use Each:
-pooler.): Serverless functions, edge functions, high-concurrencyAutomatic Pooling (Neon/Vercel):
// Both packages handle pooling automatically when using pooled connection string
import { neon } from '@neondatabase/serverless';
const sql = neon(process.env.DATABASE_URL!); // Pooling is automaticConnection Limits:
CRITICAL:
Cloudflare Workers:
// src/index.ts
import { neon } from '@neondatabase/serverless';
export default {
async fetch(request: Request, env: Env) {
const sql = neon(env.DATABASE_URL);
const users = await sql`SELECT * FROM users`;
return Response.json(users);
}
};# Deploy
npx wrangler deployVercel (Next.js API Route):
// app/api/users/route.ts
import { sql } from '@vercel/postgres';
export async function GET() {
const { rows } = await sql`SELECT * FROM users`;
return Response.json(rows);
}# Deploy
vercel deploy --prodTest Queries:
# Local test
curl http://localhost:8787/api/users
# Production test
curl https://your-app.workers.dev/api/usersKey Points:
✅ MUST DO:
-pooler. in hostname) for serverless?sslmode=require in connection stringssql`...`) to prevent SQL injectionclient.release() in finally block (Vercel Postgres transactions only)POSTGRES_URL for queries, POSTGRES_PRISMA_URL for Prisma migrations❌ NEVER DO:
POSTGRES_URL_NON_POOLING in serverlesssslmode=require (connections will fail)This skill prevents 19 documented issues:
Error: Error: connection pool exhausted or too many connections for role
Source: https://github.com/neondatabase/serverless/issues/12
Why It Happens: Using non-pooled connection string in high-concurrency serverless environment
Prevention: Always use pooled connection string (with -pooler. in hostname). Check your connection string format.
Error: Error: TCP connections are not supported in this environment
Source: Cloudflare Workers documentation
Why It Happens: Traditional Postgres clients use TCP sockets, which aren't available in edge runtimes
Prevention: Use @neondatabase/serverless (HTTP/WebSocket-based) instead of pg or postgres.js packages.
Error: Successful SQL injection attack or unexpected query results
Source: OWASP SQL Injection Guide
Why It Happens: Concatenating user input into SQL strings: sql('SELECT * FROM users WHERE id = ' + id)
Prevention: Always use template tag syntax: sql`SELECT * FROM users WHERE id = ${id}`. Template tags automatically escape values.
Error: Error: connection requires SSL or FATAL: no pg_hba.conf entry
Source: https://neon.tech/docs/connect/connect-securely
Why It Happens: Connection string missing ?sslmode=require parameter
Prevention: Always append ?sslmode=require to connection string.
Error: Gradually increasing memory usage, eventual timeout errors
Source: https://github.com/vercel/storage/issues/45
Why It Happens: Forgetting to call client.release() after manual transactions
Prevention: Always use try/finally block and call client.release() in finally block.
Error: Error: Connection string is undefined or connect ECONNREFUSED
Source: https://vercel.com/docs/storage/vercel-postgres/using-an-orm
Why It Happens: Using DATABASE_URL instead of POSTGRES_URL, or vice versa
Prevention: Use POSTGRES_URL for queries, POSTGRES_PRISMA_URL for Prisma migrations.
Error: Error: Query timeout or Error: transaction timeout
Source: https://neon.tech/docs/introduction/limits
Why It Happens: Long-running transactions exceed edge function timeout (typically 30s)
Prevention: Keep transactions short (<5s), batch operations, or move complex transactions to background workers.
Error: Error: PrismaClient is unable to be run in the browser or module resolution errors
Source: https://github.com/prisma/prisma/issues/18765
Why It Happens: Prisma requires Node.js runtime with filesystem access
Prevention: Use Drizzle ORM for Cloudflare Workers. Prisma works in Vercel Edge/Node.js runtimes only.
Error: Error: Unauthorized when calling Neon API
Source: https://neon.tech/docs/api/authentication
Why It Happens: Missing or invalid NEON_API_KEY environment variable
Prevention: Create API key in Neon dashboard → Account Settings → API Keys, set as environment variable.
Error: Error: database "xyz" does not exist after deleting a branch
Source: https://neon.tech/docs/guides/branching
Why It Happens: Application still using connection string from deleted branch
Prevention: Update DATABASE_URL when switching branches, restart application after branch changes.
Error: Error: Query timeout on first request after idle period, or Connection terminated unexpectedly
Source: Neon Docs - Auto-suspend | GitHub Issue #168 | Changelog Dec 2025
Why It Happens: Neon auto-suspends compute after ~5 minutes of inactivity (free tier), causing ~1-2s wake-up delay or connection termination
Prevention:
neon()) which handles auto-suspend transparentlyimport { Pool } from '@neondatabase/serverless';
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
// CRITICAL: Handle connection termination errors
pool.on('error', (err) => {
console.error('Unexpected database error:', err);
// Implement reconnection logic or alerting
});Error: TypeScript errors like Property 'x' does not exist on type 'User'
Source: https://orm.drizzle.team/docs/generate
Why It Happens: Database schema changed but Drizzle types not regenerated
Prevention: Run npx drizzle-kit generate after schema changes, commit generated files.
Error: Error: relation "xyz" already exists or migration version conflicts
Source: https://neon.tech/docs/guides/branching#schema-migrations
Why It Happens: Multiple branches with different migration histories
Prevention: Create branches AFTER running migrations on main, or reset branch schema before merging.
Error: Error: timestamp is outside retention window
Source: https://neon.tech/docs/introduction/point-in-time-restore
Why It Happens: Trying to restore from a timestamp older than retention period (7 days on free tier)
Prevention: Check retention period for your plan, restore within allowed window.
Error: Error: Invalid connection string or slow query performance
Source: https://www.prisma.io/docs/orm/overview/databases/neon
Why It Happens: Not using @prisma/adapter-neon for serverless environments
Prevention: Install @prisma/adapter-neon and @neondatabase/serverless, configure Prisma to use HTTP-based connection.
Error: WebSocket is not defined or timeout during Next.js 15 prerender with use cache
Source: Neon Docs - Prisma Guide | GitHub Issue #181
Why It Happens: Edge runtimes like Cloudflare Workers require HTTP instead of WebSocket for Pool queries. Next.js 15's use cache directive can timeout when using Pool with poolQueryViaFetch.
Prevention: Set neonConfig.poolQueryViaFetch = true before using Pool in edge environments.
import { Pool, neonConfig } from '@neondatabase/serverless';
// Enable Pool queries over HTTP fetch (required for edge)
neonConfig.poolQueryViaFetch = true;
const pool = new Pool({ connectionString: env.DATABASE_URL });
export default {
async fetch(request: Request, env: Env) {
// Pool.query() now uses HTTP instead of WebSocket
const result = await pool.query('SELECT * FROM users');
return Response.json(result.rows);
}
};Caveat - Next.js 15 use cache: Avoid poolQueryViaFetch = true with use cache directive - use neon() HTTP client instead:
// ❌ Can timeout during prerender
import { Pool, neonConfig } from '@neondatabase/serverless';
neonConfig.poolQueryViaFetch = true;
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
async function getData() {
'use cache';
return await pool.query('SELECT * FROM data');
}
// ✅ Works with prerender
import { neon } from '@neondatabase/serverless';
const sql = neon(process.env.DATABASE_URL!);
async function getData() {
'use cache';
return await sql`SELECT * FROM data`;
}Error: Foreign key constraint violations in transactions when using Promise.all(): insert or update on table violates foreign key constraint
Source: Drizzle Issue #2200
Why It Happens: When using Node.js v20+ with Neon serverless driver and Drizzle ORM, parallel database operations within a transaction using Promise.all() lose transaction context. Sequential operations work correctly. This is a transaction context management issue specific to Neon driver's session handling in Node v20.
Prevention: Use sequential operations or switch to postgres-js driver (not edge-compatible) for Node.js environments.
// ❌ FAILS in Node v20 with Neon driver
await db.transaction(async (tx) => {
const [user] = await tx.insert(users).values({ name: 'Alice' }).returning();
// Parallel inserts lose transaction context
await Promise.all([
tx.insert(userSettings).values({ userId: user.id, theme: 'dark' }),
tx.insert(userSettings).values({ userId: user.id, locale: 'en' })
]);
// Error: Foreign key constraint violation (user.id not visible)
});
// ✅ WORKS - Sequential execution
await db.transaction(async (tx) => {
const [user] = await tx.insert(users).values({ name: 'Alice' }).returning();
await tx.insert(userSettings).values({ userId: user.id, theme: 'dark' });
await tx.insert(userSettings).values({ userId: user.id, locale: 'en' });
});
// ✅ ALTERNATIVE - Use postgres-js driver for Node.js (not edge-compatible)
import { drizzle } from 'drizzle-orm/postgres-js';
import postgres from 'postgres';
const client = postgres(connectionString);
const db = drizzle(client);
// Promise.all() works correctly with this driverAffected Configuration:
Promise.all() for parallel inserts in transactionError: ReferenceError: process is not defined
Source: GitHub Issue #179
Why It Happens: The Neon serverless driver unconditionally accesses process.env.* at the top level, which causes errors in sandboxed runtimes like Slack's Deno runtime that don't provide process.env.
Affected Environments: Slack Deno runtime, sandboxed JavaScript environments without Node.js process global
Prevention: No workaround available yet. Users must either:
process.env in their runtimepg with Deno compatibility)@neondatabase/serverless and pg (which also accesses process.env)Official Status: Open issue with no fix timeline provided. Affects both Neon driver and underlying pg library.
Breaking Change: v1.0.0 requires tagged-template syntax for all SQL queries. Source: Neon Blog Post | GitHub Issue #3678
Before (v0.x):
const result = await sql("SELECT * FROM users WHERE id = $1", [userId]);After (v1.0+):
// Option 1: Tagged template (recommended)
const result = await sql`SELECT * FROM users WHERE id = ${userId}`;
// Option 2: .query() method for parameterized queries
const result = await sql.query("SELECT * FROM users WHERE id = $1", [userId]);
// Option 3: .unsafe() for trusted raw SQL (dynamic identifiers)
const column = 'name';
const result = await sql`SELECT ${sql.unsafe(column)} FROM users`;Why this change: The v1.0.0 release enforces tagged-template syntax to prevent SQL injection vulnerabilities. Function-call syntax sql("...", [params]) now throws a runtime error.
Error Message:
This function can now be called only as a tagged-template function:
sql`SELECT ${value}`, not sql("SELECT $1", [value], options)Migration Checklist:
sql("...", [params]) calls with tagged templatesbetter-auth Users: If using better-auth v1.3.4+ with Neon v1.0.0+, upgrade drizzle-orm to v0.40.1 or later to resolve compatibility:
{
"dependencies": {
"@neondatabase/serverless": "^1.0.2",
"better-auth": "^1.3.4",
"drizzle-orm": "^0.40.1"
}
}Alternative Workaround: Use Kysely instead of Drizzle with better-auth (works without drizzle-orm updates):
import { Kysely } from 'kysely';
import { Pool } from '@neondatabase/serverless';
const db = new Kysely({
dialect: new PostgresDialect({
pool: new Pool({ connectionString: process.env.DATABASE_URL })
})
});Source: Neon Blog - HTTP vs WebSockets
Performance characteristics differ significantly between HTTP and WebSocket protocols. The choice affects latency, throughput, and what Postgres features are available.
| Use Case | Recommended | Reason |
|---|---|---|
| Single query per request | HTTP (neon()) | Lower initial latency (~37ms) |
| 2+ sequential queries | WebSocket (Pool/Client) | Lower per-query latency (~5ms) |
| Parallel independent queries | HTTP | Better parallelization |
| Interactive transactions | WebSocket (required) | Required for transaction context |
| Edge Functions (single-shot) | HTTP | No connection overhead |
| Long-running workers | WebSocket | Amortize connection cost |
// HTTP: Best for single queries
import { neon } from '@neondatabase/serverless';
const sql = neon(env.DATABASE_URL);
const users = await sql`SELECT * FROM users`; // ~37ms
// WebSocket: Best for multiple sequential queries
import { Pool } from '@neondatabase/serverless';
const pool = new Pool({ connectionString: env.DATABASE_URL });
const client = await pool.connect(); // ~15ms setup
try {
const user = await client.query('SELECT * FROM users WHERE id = $1', [1]); // ~5ms
const posts = await client.query('SELECT * FROM posts WHERE user_id = $1', [1]); // ~5ms
const comments = await client.query('SELECT * FROM comments WHERE user_id = $1', [1]); // ~5ms
// Total: ~30ms (vs ~111ms with HTTP)
} finally {
client.release();
}HTTP does NOT support:
WebSocket limitations in edge:
{
"dependencies": {
"@neondatabase/serverless": "^1.0.2"
}
}{
"dependencies": {
"@vercel/postgres": "^0.10.0"
}
}{
"dependencies": {
"@neondatabase/serverless": "^1.0.2",
"drizzle-orm": "^0.44.7"
},
"devDependencies": {
"drizzle-kit": "^0.31.7"
},
"scripts": {
"db:generate": "drizzle-kit generate",
"db:migrate": "drizzle-kit migrate",
"db:studio": "drizzle-kit studio"
}
}import { defineConfig } from 'drizzle-kit';
export default defineConfig({
schema: './db/schema.ts',
out: './db/migrations',
dialect: 'postgresql',
dbCredentials: {
url: process.env.DATABASE_URL!
}
});Why these settings:
@neondatabase/serverless is edge-compatible (HTTP/WebSocket-based)@vercel/postgres provides zero-config on Verceldrizzle-orm works in all runtimes (Cloudflare Workers, Vercel Edge, Node.js)drizzle-kit handles migrations and schema generationimport { neon } from '@neondatabase/serverless';
interface Env { DATABASE_URL: string; }
export default {
async fetch(request: Request, env: Env) {
const sql = neon(env.DATABASE_URL);
const users = await sql`SELECT * FROM users`;
return Response.json(users);
}
};'use server';
import { sql } from '@vercel/postgres';
export async function getUsers() {
const { rows } = await sql`SELECT * FROM users`;
return rows;
}// db/index.ts
import { drizzle } from 'drizzle-orm/neon-http';
import { neon } from '@neondatabase/serverless';
import * as schema from './schema';
const sql = neon(process.env.DATABASE_URL!);
export const db = drizzle(sql, { schema });
// Usage: Type-safe queries with JOINs
const postsWithAuthors = await db
.select({ postId: posts.id, authorName: users.name })
.from(posts)
.leftJoin(users, eq(posts.userId, users.id));See Step 5 for Neon's unique transaction API (array syntax or callback syntax)
# Create branch for PR
neonctl branches create --project-id my-project --name pr-123 --parent main
# Get connection string for branch
BRANCH_URL=$(neonctl connection-string pr-123)
# Use in Vercel preview deployment
vercel env add DATABASE_URL preview
# Paste $BRANCH_URL
# Delete branch when PR is merged
neonctl branches delete pr-123# .github/workflows/preview.yml
name: Create Preview Database
on:
pull_request:
types: [opened, synchronize]
jobs:
preview:
runs-on: ubuntu-latest
steps:
- name: Create Neon Branch
run: |
BRANCH_NAME="pr-${{ github.event.pull_request.number }}"
neonctl branches create --project-id ${{ secrets.NEON_PROJECT_ID }} --name $BRANCH_NAME
BRANCH_URL=$(neonctl connection-string $BRANCH_NAME)
- name: Deploy to Vercel
env:
DATABASE_URL: ${{ steps.branch.outputs.url }}
run: vercel deploy --env DATABASE_URL=$DATABASE_URLWhen to use: Want isolated database for each PR/preview deployment
setup-neon.sh - Creates Neon database and outputs connection string
chmod +x scripts/setup-neon.sh
./scripts/setup-neon.sh my-project-nametest-connection.ts - Verifies database connection and runs test query
npx tsx scripts/test-connection.tsreferences/connection-strings.md - Complete guide to connection string formats, pooled vs non-pooledreferences/drizzle-setup.md - Step-by-step Drizzle ORM setup with Neonreferences/prisma-setup.md - Prisma setup with Neon adapterreferences/branching-guide.md - Comprehensive guide to Neon database branchingreferences/migration-strategies.md - Migration patterns for different ORMs and toolsreferences/common-errors.md - Extended troubleshooting guideWhen Claude should load these:
connection-strings.md when debugging connection issuesdrizzle-setup.md when user wants to use Drizzle ORMprisma-setup.md when user wants to use Prismabranching-guide.md when user asks about preview environments or database branchingcommon-errors.md when encountering specific error messagesassets/schema-example.sql - Example database schema with users, posts, commentsassets/drizzle-schema.ts - Complete Drizzle schema templateassets/prisma-schema.prisma - Complete Prisma schema templateNeon provides git-like database branching:
# Create branch from main
neonctl branches create --name dev --parent main
# Create from point-in-time (PITR restore)
neonctl branches create --name restore --parent main --timestamp "2025-10-28T10:00:00Z"
# Get connection string for branch
neonctl connection-string dev
# Delete branch
neonctl branches delete featureKey Features:
Connection Pool Monitoring:
Query Optimization:
Security:
Required:
@neondatabase/serverless@^1.0.2 - Neon serverless Postgres client (HTTP/WebSocket-based)@vercel/postgres@^0.10.0 - Vercel Postgres client (alternative to Neon direct, Vercel-specific)Optional:
drizzle-orm@^0.44.7 - TypeScript ORM (edge-compatible, recommended)drizzle-kit@^0.31.7 - Drizzle schema migrations and introspection@prisma/client@^6.10.0 - Prisma ORM (Node.js only, not edge-compatible)@prisma/adapter-neon@^6.10.0 - Prisma adapter for Neon serverlessneonctl@^2.19.0 - Neon CLI for database managementzod@^3.24.0 - Schema validation for input sanitization/github/neondatabase/serverless, /github/vercel/storage{
"dependencies": {
"@neondatabase/serverless": "^1.0.2",
"@vercel/postgres": "^0.10.0",
"drizzle-orm": "^0.45.1"
},
"devDependencies": {
"drizzle-kit": "^0.31.8",
"neonctl": "^2.19.0"
}
}Latest Prisma (if needed):
{
"dependencies": {
"@prisma/client": "^6.10.0",
"@prisma/adapter-neon": "^6.10.0"
},
"devDependencies": {
"prisma": "^6.10.0"
}
}This skill is based on production deployments of Neon and Vercel Postgres:
Error: connection pool exhaustedSolution:
-pooler.region.aws.neon.tech)Error: TCP connections are not supportedSolution:
@neondatabase/serverless instead of pg or postgres.jsError: database "xyz" does not existSolution:
DATABASE_URL points to correct databaseSolution:
PrismaClient is unable to be run in the browserSolution:
@prisma/adapter-neonSolution:
neonctl branches reset feature --parent mainError: Warning: @neondatabase/serverless can only connect to remote Neon/Vercel Postgres/Supabase instances through a websocket
Source: GitHub Discussion #12508
Solution: This warning is informational and can be safely ignored. Migrations work correctly despite the warning. The warning exists to inform users that WebSocket protocol is being used, which helps with debugging if something goes wrong. Adding pg as a dev dependency eliminates the warning but is unnecessary.
Error: NeonDbError: Error connecting to database: fetch failed [cause]: SocketError: other side closed
Source: GitHub Issue #146 comment
Solution: Some VPNs block WebSocket or fetch connections to Neon's endpoints. This occurs primarily during development (localhost) with Next.js 14+ server actions. Disable VPN and test, or whitelist Neon domains in VPN configuration:
# Whitelist these domains in VPN:
*.neon.tech
*.aws.neon.techUse this checklist to verify your setup:
@neondatabase/serverless or @vercel/postgres)-pooler.)?sslmode=requireDATABASE_URL or POSTGRES_URL)sql`...`)Questions? Issues?
references/common-errors.md for extended troubleshootingsslmode=require is in connection stringscripts/test-connection.tsLast verified: 2026-01-21 | Skill version: 2.0.0 | Changes: Added 4 new issues (#16-#19: poolQueryViaFetch, Node v20 transactions, process.env sandboxing); added Migration Guide (v0.x→v1.0+); added Performance & Protocol Selection section; expanded Issue #11 with auto-suspend handling and production config; added TIER 2 community-sourced troubleshooting (WebSocket warning, VPN blocking)