# A Neon branch per preview deployment

> Preview deploys that share the production database corrupt it or lie to you. Give every preview its own copy-on-write Neon branch, wired to the deployment's environment variables.

You open a pull request, Vercel builds a preview, and the preview talks to the
same database as production. Everyone knows this is bad and everyone does it
anyway, because the alternative (a second database that someone has to keep
migrated and seeded) is worse.

The symptoms arrive in a predictable order. First a migration on a preview
branch adds a column that production's code does not know about, which is
harmless. Then one drops a column, and production starts throwing. Then a
reviewer clicks around a preview of a "delete account" feature and deletes a
real account. Somewhere in between, a preview of a branch whose migration has
not run yet queries a table that does not exist yet, and the reviewer reports a
bug that is not real.

## The wrong fixes

**A single shared staging database.** It drifts. Whichever preview branch
migrated last owns the schema, so every other open pull request is broken.
Reviewers learn to ignore preview errors, which defeats the point of previews.

**Seeding a fresh empty database per preview.** Correct in principle,
impractical in practice: your seed script is a fiction, so the preview never
reproduces the data shape that causes real bugs, the account with 40,000 rows,
the row with a null in the column you assumed was populated.

**Pointing previews at production read-only.** Half the features under review
are writes.

## The right fix: one branch per preview

A Neon branch is a copy-on-write clone of another branch. It shares its
parent's storage pages until it writes; only the differences cost anything. A
branch of a 20 GB database that inserts a hundred rows costs the storage of a
hundred rows. Creation takes seconds because nothing is copied.

That changes the economics. A per-preview database stops being an infrastructure
project and becomes a line in a deploy script.

### Option 1: the Vercel integration

Install the Neon integration from the Vercel marketplace and connect it to your
project. From then on, every preview deployment gets a branch created from
`main`, and the deployment's environment gets `DATABASE_URL` and
`DATABASE_URL_UNPOOLED` pointing at it. Merging or closing the pull request
deletes the branch.

For most teams this is the whole answer, and the rest of this page is
background.

### Option 2: own the script

You want this when your branch naming, retention or parent branch needs to
differ from the integration's defaults.

```bash
#!/usr/bin/env bash
# scripts/preview-branch.sh: create a Neon branch for the current git branch
# and print both connection strings.
set -euo pipefail

git_ref="$(git rev-parse --abbrev-ref HEAD)"
branch="preview/${git_ref}"

# Idempotent: re-running on the same git branch reuses the branch it made.
if ! neonctl branches get "$branch" >/dev/null 2>&1; then
  neonctl branches create --name "$branch" --parent main >/dev/null
fi

pooled="$(neonctl connection-string --branch "$branch" --pooled)"
direct="$(neonctl connection-string --branch "$branch")"

echo "DATABASE_URL=$pooled"
echo "DATABASE_URL_UNPOOLED=$direct"
```

Feed those into the deployment's environment (`vercel env add ... preview`, or
your host's equivalent), then apply migrations against the **direct** string as
part of the build. And add the other half:

```bash
neonctl branches delete "preview/${git_ref}"
```

on merge or close. Skip this and you will find two hundred stale branches in six
months.

## Migrations on a preview branch

The branch inherits the parent's schema, so a preview only needs the migrations
your pull request adds. Run them on the unpooled endpoint:

```
DATABASE_URL="$DATABASE_URL_UNPOOLED" <your migrate command> && <your build command>
```

Because the branch is copy-on-write, this is a rehearsal against production-sized
data: the `NOT NULL` that fails on existing rows fails *here*, on a throwaway
database, in front of the person who wrote it. That is the single biggest
practical win of the whole arrangement, bigger than the isolation.

## Things that bite

**Personal data is still personal data.** A branch of production contains
production rows. Preview URLs are often less protected than production, and a
branch is subject to the same GDPR/CCPA obligations as its parent. If your
production data is sensitive, branch from a sanitised parent branch: keep a
`staging` branch you periodically reset from `main` and scrub, and parent your
previews off that instead.

**Branch limits and storage.** Every plan caps branches. Copy-on-write storage
is cheap but not zero, and a preview that runs a big backfill writes real pages.
Delete on merge, and check `neonctl branches list` monthly.

**Cold starts.** A preview branch nobody has touched for five minutes has scaled
its compute to zero and takes roughly half a second to wake. Reviewers will
report the first click as slow. It is not a bug: see the cookbook page on cold
starts if you want to keep specific branches warm.

**The pooled/unpooled pair travels together.** A preview that inherits only
`DATABASE_URL` will fail its first migration with a lock error that does not
mention the missing variable. Set both, every time.

**Do not branch from a branch, by default.** Nested branches are supported and
are occasionally exactly right, but a preview parented on another preview
inherits that preview's half-finished migrations. Parent from `main` unless you
mean otherwise.

## What good looks like

- Opening a pull request produces a preview URL with its own database.
- The build applies pending migrations to that database on the direct URL.
- The preview's data resembles production because it started as production.
- Merging deletes the branch.
- Nobody has ever had to ask "is it safe to click delete on the preview?"

---

Agentic Boilerplate: A Next.js repo your agent already knows. Free during launch, then $99 once.

- Site map for agents: https://agenticboilerplate.com/llms.txt
- Public API: https://agenticboilerplate.com/openapi.json
- Contact: agenticstudio@gmail.com
