Neon Serverless Postgres for Web App
Neon is serverless PostgreSQL with separated storage and compute, database branching, and automatic scale-to-zero. Compute instance starts in ~500ms on first request and stops after idle period. Pay only for active time, not 24/7 running server.
Key Features
Scale-to-zero: useful for dev/staging — don't pay for idle instances. Production with constant traffic won't benefit — cold start latency unacceptable for users.
Branching: instant database copies via copy-on-write. Each branch is a full PostgreSQL endpoint:
# Branch for PR preview
neon branches create --name preview/pr-123 --parent main
# Branch for development
neon branches create --name dev/feature-payments --parent main
Serverless Driver: HTTP transport instead of TCP. Required for edge runtimes (Cloudflare Workers, Vercel Edge Functions) with no persistent TCP connections:
import { neon } from '@neondatabase/serverless';
const sql = neon(process.env.DATABASE_URL!);
// Top-level await in edge function
export default async function handler(req: Request) {
const posts = await sql`SELECT id, title FROM posts WHERE published = true LIMIT 10`;
return Response.json(posts);
}
Prisma Connection
npm install @prisma/client @neondatabase/serverless prisma
// schema.prisma
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
// Neon uses ?sslmode=require in URL
}
// lib/prisma.ts — Edge Runtime uses HTTP adapter
import { PrismaClient } from '@prisma/client';
import { Pool, neonConfig } from '@neondatabase/serverless';
import { PrismaNeon } from '@prisma/adapter-neon';
import ws from 'ws';
neonConfig.webSocketConstructor = ws; // for Node.js
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const adapter = new PrismaNeon(pool);
export const prisma = new PrismaClient({ adapter });
Connection Pooling
Serverless functions don't hold long-lived connections — each invocation opens new. Neon has built-in PgBouncer:
# Standard connection (direct to PostgreSQL)
postgresql://user:[email protected]/mydb
# Via pooler (for serverless)
postgresql://user:[email protected]/mydb?pgbouncer=true
For Next.js serverless functions use pooler URL. For long-lived processes (cron, workers) — direct connection.
Branching in CI/CD
# GitHub Actions — create branch per PR
- name: Create Neon branch
uses: neondatabase/create-branch-action@v5
id: create-branch
with:
project_id: ${{ vars.NEON_PROJECT_ID }}
api_key: ${{ secrets.NEON_API_KEY }}
branch_name: preview/pr-${{ github.event.pull_request.number }}
parent: main
- name: Run migrations on branch
env:
DATABASE_URL: ${{ steps.create-branch.outputs.db_url }}
run: npx prisma migrate deploy
- name: Deploy Preview
env:
DATABASE_URL: ${{ steps.create-branch.outputs.db_url }}
run: vercel deploy --env DATABASE_URL=$DATABASE_URL
# On PR close — delete branch
- name: Delete Neon branch
if: github.event.action == 'closed'
uses: neondatabase/delete-branch-action@v3
with:
project_id: ${{ vars.NEON_PROJECT_ID }}
branch: preview/pr-${{ github.event.pull_request.number }}
api_key: ${{ secrets.NEON_API_KEY }}
Limitations
- Max row size 8 KB (standard PostgreSQL)
- Doesn't support some PostgreSQL extensions (PostGIS supported, btree_gist — yes, pgaudit — no)
- Scale-to-zero cold start ~500ms — unacceptable for production with SLA < 1s
- Cost on high loads may exceed dedicated PostgreSQL
When to Choose Neon
Suitable:
- Next.js / Vercel apps with variable load
- Startups with low initial traffic
- Preview environments per PR
- Serverless functions on edge
Not suitable:
- High-load production (>1000 req/sec to DB)
- Apps with strict latency requirements
- Long-running connections (PostgreSQL LISTEN/NOTIFY, logical replication)
Timeline
Neon project setup, Prisma + HTTP adapter, CI pipeline with branching per PR, serverless pooler: 1–2 days.







