Skip to content

Database

Next.js boilerplate with Neon

Serverless Postgres you can branch like git, with a driver built for cold starts.

Serverless Postgres with copy-on-write database branching. It ships an HTTP driver for one-shot queries and a pooled connection for route handlers that need a session. It also adds a read-only db-inspector agent, and a Bash guard that keeps migrations off the pooler.

What Neon adds to the agent layer: 2 rules · 2 skills · 1 subagent · 1 hook · 6 solution docs · 1 MCP server

Maintained by @raviMITNext.js on Vercel

From the manifest

Should you pick Neon?

Pick it if

Teams that want plain Postgres plus a throwaway database per pull request, running on Vercel or another serverless host where connections are scarce.

Watch out for

  • Standard Postgres. ORMs and psql work unchanged, with no custom query layer to learn.
  • Branching is copy-on-write, so a preview branch of a 20 GB database only adds storage for what the branch changes.
Show 3 more
  • Compute scales to zero after 5 idle minutes and wakes in a few hundred milliseconds. On low-traffic previews that shows up as a slow first request.
  • Database only. No auth, storage, realtime or generated API. Pair it with Better Auth or Clerk. Supabase bundles those.
  • Two connection strings to keep straight (pooled and direct). Point a migration at the pooled one and it fails in confusing ways.

What it costs

Free plan: up to 100 projects, each with 100 CU-hours a month and 0.5 GB of storage. Launch and Scale have no monthly minimum: you pay per CU-hour ($0.106 on Launch) plus $0.35 per GB-month of storage.

Prices change. Check with Neon before you commit.

registry/tested.yaml

Tested with Neon

Each pair was installed, typechecked, linted, built and booted together.

Admin panel
Admin panel
Error tracking
Sentry
Customer support
Crisp

Not tested yet: Supabase Auth and Supabase Storage.

What it adds

What Neon adds to the repo

Read straight from the neon manifest, so it is exactly what lands in your repo.

Environment variables

  • DATABASE_URLRequired

    Pooled connection string. Used by the app at runtime. The hostname contains "-pooler".

    Placeholder
    postgresql://user:password@ep-example-123456-pooler.us-east-2.aws.neon.tech/neondb?sslmode=require
  • DATABASE_URL_UNPOOLEDOptional

    Direct connection string, same host without "-pooler". Migrations, DDL and anything holding a session use this one. This is Neon's name for it and what the Vercel integration injects; an ORM that declares its own key for the same endpoint (Prisma reads DIRECT_URL) adds that key below - set every one of them to this exact string.

    Placeholder
    postgresql://user:password@ep-example-123456.us-east-2.aws.neon.tech/neondb?sslmode=require
  • NEON_LOCAL_PROXYOptional

    Local development without a Neon account. Set it to localhost:4444, point DATABASE_URL at a Postgres on this machine, and run bun run db:proxy in another terminal. The Neon driver then talks to that proxy instead of Neon's cloud. Leave it empty against a real Neon branch. The app refuses to start with it set on a Vercel production deployment.

    Where to get it
    See the "Develop against a local Postgres" step in docs/onboard.md.
    Placeholder
  • NEON_API_KEYOptional

    Personal or org API key. Only needed for the neonctl CLI and the db:branch script. The app never reads it, and the MCP server signs in with OAuth.

    Placeholder
    neon_api_key_xxxxxxxxxxxxxxxxxxxx

Dependencies

  • @neondatabase/serverless^1.0.0
  • @types/pg^8.23.0dev
  • pg^8.23.0dev

Scripts

  • bun run db:branch

    bun scripts/db-branch.ts

  • bun run db:proxy

    bunx tsx --env-file-if-exists=.env.local scripts/neon-local-proxy.ts

  • bun run db:psql

    psql "$(bunx neonctl connection-string)"

MCP server

  • neon

    URL
    https://mcp.neon.tech/mcp?readonly=true

Files it writes

5 files, at these exact paths.

  • scripts/2 files
    • db-branch.ts
    • neon-local-proxy.ts
  • src/3 files
    • db/3 files
      • client.ts
      • health.ts
      • neon-local.ts

Stack slots it fills

The stack declares these injection points; this battery supplies the fragment, so the provider tree, the env check and the schema stay one file each instead of many.

  • @slot db-client
  • @slot env-required
  • @slot hook-probes
  • @slot legal-processors
  • @slot verify-checks

The differentiator

What Neon teaches your agent

Other starter kits stop at the package. This is the part an agent reads: where it may work, what it must never do there, and the problems someone already solved.

Rules (2)

Loaded when the agent opens a matching file.

One connection surface, and the right driver for the job

Loads onsrc/db/**src/app/api/**.claude/rules/neon-connections.md

Database access goes through src/db - getSql(), getPool(), withTransaction(), batchTransaction(). Nothing else in the repo constructs a client.

Required

  • Import from @/db (or the ORM client bound to getPool()). Never call neon() or new Pool() in a route handler, a server action, a component or a script; every extra instance is another set of sockets against the same pooler.
  • Default to getSql(). The HTTP driver has no connection to establish, which is why it wins on a cold serverless instance. One statement per call.
  • Use getPool() / withTransaction() only when a single request genuinely needs a shared session: an interactive transaction, set local, an advisory lock, a cursor. Keep the transaction body free of HTTP calls - it holds a connection for its whole duration.
  • Use batchTransaction() when several statements must commit together but none of them reads a previous statement's result. It is one round trip and holds nothing.
  • Parameterise everything. The tagged template (sql`select * from users where id = ${id}`) sends values out of band; string concatenation into SQL is an injection bug even when the value "comes from our own code".

Forbidden

  • DATABASE_URL in client components, NEXT_PUBLIC_* anything database-shaped, or a connection string in a log line, an error message returned to the browser, or a commit. The env-leak hook catches the obvious shapes; do not rely on it.
  • Long-lived state assumed across requests. Neon branches scale to zero and serverless instances are recycled, so treat every request as if the connection is new. Nothing may be cached on the pool object.
  • select * in a hot path, and unbounded select without limit. Egress and row-parse time are the two costs you notice on a serverless database.
  • Swallowing connection errors. A Connection terminated or too many connections must surface - it is the signal that the pool policy is wrong, and hiding it turns a 30-second incident into a week of mystery latency.

Reads that can be stale

Wrap them in unstable_cache or a route segment revalidate before reaching for a read replica. Most "we need a replica" traffic is the same three queries running on every request.

Local Postgres

  • Local development runs the same Neon driver through bun run db:proxy and NEON_LOCAL_PROXY. databaseUrl() in src/db/client.ts applies it before the first query, so no code path forks. Never swap in pg, postgres or drizzle-orm/node-postgres "just for local": that client can run db.transaction() and the HTTP driver in production cannot, so local runs would pass code that breaks on deploy.
  • NEON_LOCAL_PROXY never goes into a Vercel environment. The app throws on a production deployment that has it set.

Schema changes go through ORM migration files on the direct URL

Loads onsrc/db/**.claude/rules/neon-migrations.md

Every schema change in this repo is a migration file produced by the ORM, applied with the ORM's own command, against DATABASE_URL_UNPOOLED.

Required

  • Generate the migration (bun run db:generate for Drizzle, prisma migrate dev --name <change> for Prisma), read the emitted SQL, then apply it. The generated file is the reviewable artefact - a schema change with no file in the diff did not happen.
  • Apply migrations with bun run db:migrate. It has to reach the direct endpoint, never the -pooler one. How it gets there is the ORM's business - Prisma's prisma.config.ts reads DIRECT_URL, then DATABASE_URL_UNPOOLED; Drizzle derives the direct host from DATABASE_URL - so set every direct-connection key .env.example lists to the same string and let the ORM pick.
  • In a script you write, resolve the direct string with directDatabaseUrl() from src/db/client.ts rather than re-reading process.env: it refuses to hand back a pooler host instead of letting the migration discover that itself.
  • Edit table definitions only in the ORM schema file - src/db/schema.ts under Drizzle, prisma/schema.prisma under Prisma. That file is owned by the ORM battery.
  • Test destructive migrations on a Neon branch first: bun run db:branch, apply there, check the row counts, then apply to main.

Forbidden

  • Raw DDL from application code, a script, psql -c, neonctl or the Neon SQL editor. create table, alter table, drop, truncate and create index outside a migration file put the database and the schema file out of sync, and the next generated migration will try to "fix" the drift by dropping your column.
  • Pointing any migration at the pooled URL. PgBouncer in transaction mode hands each statement to a different backend, so create index concurrently fails, advisory locks silently do nothing, and multi-statement DDL half-applies. The guard-neon-sql hook blocks the obvious cases; do not work around it by inlining the string.
  • drizzle-kit push or prisma db push against anything but your own throwaway branch. Both skip the migration history, so the change exists in the database and nowhere in git.
  • Hand-editing a migration that has already been applied to main. Write a new one.

Data migrations

Keep DDL and backfills in separate migrations. Add the nullable column, deploy, backfill in batches, then a second migration adds the not null. A single migration that adds a column and rewrites ten million rows holds a lock for the length of the rewrite, and on a serverless host the request that triggered it times out first.

Skills (2)

Invoked by name.

  • /db-branch

    Create, use, reset and delete Neon database branches, for a feature branch, a preview deploy, a migration rehearsal or a point-in-time investigation.

    .claude/skills/db-branch/SKILL.md

  • /migrate-on-neon

    Run a schema migration against Neon safely, on the direct URL, rehearsed on a branch first, with a recovery path when it fails halfway.

    .claude/skills/migrate-on-neon/SKILL.md

Subagents (1)

  • db-inspector

    Read-only Neon Postgres inspector. Explains schema, data shape and query plans. Runs SELECT and EXPLAIN only, and refuses every statement that writes or changes schema.

    ToolsReadGrepGlobBashmcp__neon

Hooks (1)

The repo wires hooks for Claude Code only.

  • guard-neon-sql

    Blocks raw DDL through psql, migrations that would really run through the Neon pooler, and schema pushes that skip migration files. The repo's own db:migrate scripts pass.

    PreToolUseBash

Solution docs (6)

Written before you hit the problem. Each one ships in the repo at docs/solutions/ and is published here as a cookbook page.

Show all 6

How it fits

What Neon needs, and what it goes well with

The resolver enforces this before it generates anything, and names every addition it makes.

Requires

  • An ORM battery. The resolver adds the default one for you and tells you why.

Pairs well with

Nothing extra. Add any tested battery alongside Neon.

Compared with the alternatives

Build a repo with Neon

Free and MIT. The builder opens with Neon picked. You download the zip right away, and we email you the link too.

Presets

Presets that already include Neon

A tested selection with its own file tree and its own generated CLAUDE.md. Start from one instead of from blank.