Skip to main content

Neon Setup

Neon provides serverless Postgres with auto-scaling and branching. The free tier is enough for development and small production deployments.

Create a project​

  1. neon.tech → Create project.
  2. Pick a region matching your Vercel + Supabase regions.
  3. Name your branch (default main).

Connection strings​

Neon gives you two connection strings per database:

  • Pooled (...-pooler.region.aws.neon.tech) — go-to for serverless functions. Use as DATABASE_URL.
  • Direct (...region.aws.neon.tech) — set as DATABASE_URL_UNPOOLED.

Both include ?sslmode=require.

Migrations currently use DATABASE_URL

The Prisma schema does not declare a directUrl, so Prisma migrations run against DATABASE_URL (the pooled URL), not DATABASE_URL_UNPOOLED. Keep DATABASE_URL_UNPOOLED set for forward-compatibility, but it isn't wired into the schema today. If you hit pooler issues during migration, point DATABASE_URL at the direct connection for the duration of the migration.

Schema initialization​

Run from a shell with both env vars set:

pnpm db:push # apply schema (no migration history needed)
pnpm db:seed # provision the demo project + admin user

db:push is idempotent. Re-run after schema changes. Production deployments that need migration history use db:migrate locally + db:deploy in CI.

Branching for environments​

Neon supports DB branches — copy-on-write forks of the main DB. Use them for:

  • Preview deploys — every Vercel preview deploy gets a Neon branch with a fresh copy of production data, automatically discarded on PR close.
  • Staging environments — a long-lived branch for the staging Krios instance. Promote to prod by merging the branch back.

Vercel + Neon native integration handles this automatically when both are connected. See Neon's "Vercel integration" docs for the setup.

Connection pooling​

Neon's pooler runs PgBouncer in transaction mode. This is the right choice for serverless — short-lived connections multiplexed onto the pool. Krios's Prisma config already uses it via the pooled URL.

For self-hosted Krios with high traffic, run your own PgBouncer in front of Postgres or use Neon's pooled URL even off-Vercel.

Migration sequence​

When you change prisma/schema.prisma:

# Local (db:migrate already expands to `prisma migrate dev`)
pnpm db:migrate --name describe_change

# Commit prisma/migrations/
git add prisma/migrations
git commit -m "schema: add CustomFieldType"

# CI / production
pnpm db:deploy

Or, for fast-iteration projects without migration history:

pnpm db:push # local
# In production: same command, but be aware that destructive changes (column drops) need --accept-data-loss

Backups + recovery​

Neon snapshots the database continuously. Point-in-time-restore is in the Neon dashboard:

  • Settings → PITR. Pick a timestamp; Neon creates a new branch from that point.

For larger deployments, schedule a nightly pg_dump to S3 as a belt-and-braces redundancy.

Tuning​

  • Increase connection_limit on the pooled URL via the query string (?connection_limit=20) for high-throughput admin operations.
  • Enable autoscaling in the Neon dashboard so the compute scales up under load and down when idle (free tier suspends after 5 min of inactivity).

Common pitfalls​

  • Using the unpooled URL in serverless. You'll exhaust connections under load. Pool URL only.
  • Forgetting sslmode=require. Neon rejects unencrypted connections. Both URLs include it by default.
  • Running db:push --accept-data-loss in production. It's destructive — only run with explicit migration intent.