Skip to content
Chapter 36Lesson 3

Neon branching and scale-to-zero

How Neon separates storage from compute to give every preview deployment its own copy-on-write database branch that scales to zero when idle.

You’re shipping a web app in 2026, and every pull request your team opens gets its own preview URL: a full running copy of the app a reviewer can click through before anyone merges.

That setup raises two hard questions. First, each preview needs its own data, so two reviewers testing two PRs don’t corrupt each other’s results. How does every preview get an isolated copy? Second, if there’s a database behind every open PR, are you paying for each one, even those nobody has touched in a week? On a traditional Postgres server both are dealbreakers: a per-PR database is too slow to copy and too expensive to leave idle, so teams don’t bother. Neon removes that ceiling, and a single architectural choice answers both questions at once. The previous lesson pointed your DATABASE_URL at a Neon branch; this lesson explains what that branch is.

Branching, the database-per-preview trick, and the bill that drops to zero all follow from one fact about how Neon is built.

A traditional Postgres server is one process welded to its disk. The running program parses your SQL, plans queries, and reads and writes rows; the data files it owns sit on a disk bolted to the same machine. They boot together, live together, and die together.

That welding shows up as three specific pains:

  • Copying the database means physically copying the disk. To duplicate a 10 GB database you pg_dump and restore it elsewhere: minutes of work and a second, full 10 GB on disk.
  • An idle server still costs money. A database behind a preview nobody has touched in a week burns the same compute as one under load, because always-on is its only state.
  • You can’t add a read-only copy without replicating everything. A second process to spread read traffic needs its own disk, so all the data has to be copied and kept in sync.

Neon splits the process apart from the disk. Storage becomes a log of page versions in durable cloud storage, independent of any running program. Compute becomes a stateless Postgres process the platform starts on demand and points at that storage. Kill it and the data is untouched; start a new one against the same storage and it picks up where the last left off.

Traditional Postgres

Postgres process
disk (data files)
one inseparable unit

Neon

Compute stateless Postgres
Compute stateless Postgres
Storage log of page versions
started on demand, pointed at storage
Neon's storage is durable and shared; its compute is disposable. Every feature in this lesson is a consequence of that one split.

Each pain turns into its opposite:

  • Storage is shared, so a second process needs no copy of the data. That is what makes branching cheap.
  • Compute is disposable, so when nobody is querying, the platform throws the process away and you pay nothing. That is scale-to-zero.
  • Many computes can read one storage, so a read-only replica is just another process pointed at the same log. Neon supports this for scaling reads, though the course never needs it.

Branches share storage, so they’re nearly free

Section titled “Branches share storage, so they’re nearly free”

A branch is a new compute pointed at a snapshot of a parent’s storage at a moment in time. It’s copy-on-write : the branch shares every existing page with its parent and copies nothing up front. Only when you change a page, by inserting a row or updating a value, does that one page get written fresh and kept separate. Everything you haven’t touched is still, physically, the parent’s pages.

So copying a 10 GB database the old way takes pg_dump, a restore, and a fresh 10 GB on disk; branching it on Neon takes under a second and roughly zero added storage, because the branch shares all 10 GB and has changed nothing. You pay storage only for the pages your branch diverges on, so a branch you barely touch costs almost nothing to keep.

One thing about branches trips up nearly everyone. A branch captures its parent as it was at the moment you created it, and then the two go their separate ways. This is what point-in-time means, and it has real consequences:

production primary
Mon A B
Tue A B C
branch
Mon A B copied at fork
Tue A B Z
A branch is a snapshot, not a live mirror. After the fork, new rows on either side stay on their own side.

A branch is not a live mirror of its parent. Rows the parent gains after you branch never show up, so an old branch shows old data; if a new production row isn’t appearing in a branch, the branch was cut before that row existed. The independence runs the other way too, and that’s what makes branches safe: rows your branch writes never flow back to the parent. You can fill a branch with garbage or drop a table, and the parent doesn’t feel it.

That safety is what makes branching worth building a workflow around. Here’s what it unlocks, each expanded in the sections that follow:

  • A branch per preview deployment. Every open PR gets its own isolated, prod-shaped database.
  • Two long-lived branches as your baseline. A production branch that real users hit, and a staging branch forked off it that lives indefinitely. This pair is the course’s default setup.
  • Migration rehearsal. Before running a destructive migration against production, branch it, run the scary thing on the branch, inspect the result, and throw the branch away if it went wrong. The branch is your undo button; the mechanics come in the migrations chapter.

These branches are created by the platform and the integration we’re about to meet, not by a command you type.

This is the direct answer to the question we opened with: how does every preview get its own data? Follow one pull request from “opened” to “merged.”

Pull request opened
Preview deployment
Neon project
production primary · warm
staging long-lived
preview/feature-x copy-on-write · ~0 storage
shared storage · page log

PR opened. A developer pushes a Git branch and opens a pull request on GitHub. The database side is unchanged: production and staging exist, with no preview branch yet.

Pull request open
Preview deployment
Neon project
production primary · warm
staging long-lived
preview/feature-x copy-on-write · ~0 storage
shared storage · page log

Neon branch created. The Vercel–Neon integration sees the new deployment and creates a Neon branch off the parent, named something like preview/<git-branch>. It’s copy-on-write, so it shares the parent’s storage and costs almost nothing.

Pull request open
Preview deployment deployed
Neon project
production primary · warm
staging long-lived
preview/feature-x copy-on-write · ~0 storage
shared storage · page log

Preview deployed with the branch’s URL. Vercel builds the preview and injects that branch’s connection string as the deployment’s DATABASE_URL. The preview app now talks to its own private database, so seeding or mutating it can’t touch production or any other PR.

Pull request +commits pushed
Preview deployment iterating safely
Neon project
production primary · warm
staging long-lived
preview/feature-x copy-on-write · ~0 storage
shared storage · page log

Iterate safely. The developer pushes more commits, runs migrations against the preview branch, and QA clicks through real, isolated data.

Pull request merged / closed
Preview deployment torn down
Neon project
production primary · warm
staging long-lived
preview/feature-x deleted · storage reclaimed
shared storage · page log

PR merged or closed → branch deleted. With auto-delete enabled, the integration tears the preview branch down and reclaims the storage its diverged pages used. Back to just production and staging.

A database appears for the life of a PR and vanishes when it’s done. That teardown is what makes the pattern affordable.

It also replaces the single shared staging database, a well-known anti-pattern. With one staging database and two PRs in flight, reviewer A seeds test invoices, reviewer B’s destructive test drops a table A relied on, and a third migration leaves it half-broken, until nobody trusts what they see. Shared staging degrades because everyone writes to it and no one owns it. A branch per PR gives every preview a clean, production-shaped database that exactly one PR can touch, cheap enough to keep idle one per open pull request.

As of 2026 the Vercel–Neon integration comes in two flavors, differing only in who deletes the branch:

  • Neon-managed: you switch on “automatically delete obsolete branches,” and cleanup runs the next time a preview deploys.
  • Vercel-managed: the branch is deleted when Vercel removes the matching deployment, on Vercel’s retention policy, which defaults to roughly six months.

The deployment chapter owns the wiring; carry one watch-out from here: auto-delete is a setting you have to turn on. Leave it off and preview branches accumulate until your project holds the database of every PR you ever opened.

How the preview app opens a connection over that injected URL is the next lesson’s subject.

Because a Neon compute holds no permanent state, the platform can do something a welded-together server never could: after a stretch of no activity, it suspends the compute entirely and the process goes away. While it’s gone you pay nothing for compute, and your data is safe: it was never in the process, it sits in storage, untouched. The moment a query arrives, the platform spins up a fresh compute, points it at the same storage, and serves the query. The database fell asleep and woke up, and nothing was lost. The current numbers, as of mid-2026:

  • Idle window: after about five minutes with no activity, the compute suspends. On paid (Scale) plans this is a dial you can turn, from one minute up to never suspending. On the free tier the five-minute window is fixed.
  • Wake time (the cold start ): roughly 300–800 milliseconds to resume, with total time-to-first-query usually between half a second and a second.

An idle preview branch with no traffic bills you for storage only, because its compute has dropped to zero. That is what makes a branch per pull request sustainable. Ten open pull requests are not ten databases burning compute around the clock; they are ten cheap storage snapshots whose compute flickers on only when someone opens that preview, then drops back to zero. Branching makes the copy free, and scale-to-zero makes the idle free.

Scale-to-zero is a trade: the first query after a suspend pays the wake latency; every query after that, on the warm compute, is fast. The whole cost lands on that first query, and you decide per branch whether it’s acceptable there.

One honest limit. Neon’s free plan gives you roughly 100 compute-hours per project a month, half a gigabyte of storage, and around ten branches. The exact numbers drift, so check the pricing page in the resources below for current figures. That is plenty to learn this model on and run a side project, but not sized for live production traffic, where real load burns through the compute-hours and you will need a paid plan.

Pull the pieces together and a recognizable shape falls out, one you should be able to read off a real project at a glance.

Neon project
production primary DATABASE_URL points here · kept warm
staging long-lived
preview/* ephemeral one per PR · created & destroyed automatically · scale to zero
shared storage · page log
One project: two long-lived branches (production kept warm, staging), and a fan of ephemeral preview branches that the integration creates and destroys per pull request.
  • A Neon project holding a production primary branch, the one your DATABASE_URL points at in production, and a long-lived staging branch forked off it.
  • A Vercel–Neon integration that cuts an ephemeral branch per preview deployment, injects that branch’s connection string into the preview, and, with auto-delete on, removes the branch afterward.
  • Production compute tuned to stay warm so no real user waits through a cold start, while preview and dev branches scale to zero, where the savings outweigh the wake latency.

Look at production in that tree and notice what protects it: only a name. production is just a branch by convention. The platform won’t stop anyone from running a destructive migration straight against it, no “are you sure,” no technical guard of any kind. What protects it is discipline: branch it, test the change on the branch, then apply it for real.

Three things this section leaves for later: provisioning a project and wiring the integration (deployment chapter), the branch-run-promote migration discipline (migrations chapter), and how your application code connects to whichever branch URL it’s handed (the next lesson).

Now put the model to work: map a real situation to the right move, and clear up the two misconceptions that cause the most trouble.

What's the Neon move here?

Read the situation, then reach for the Neon feature that fits. The two questions below cover the misconceptions worth getting right.

On Monday you create a branch off production. On Tuesday, three new rows are inserted into production — nothing else touches the branch. When you query the branch on Wednesday, what does it return?

Monday’s data, without the three Tuesday rows.
All of Monday’s data plus the three Tuesday rows — the branch tracks production as it changes.
The three Tuesday rows, and any rows you wrote to the branch now also show up in production.
Nothing, until you manually pull the latest data from production into the branch.

A preview branch has sat with zero traffic for the last hour — nobody opened its preview. The branch still holds the few pages it diverged from production. Which part of it is Neon billing you for during that idle hour?

Neither part — once a branch goes idle it costs nothing until a query wakes it.
The diverged pages it’s holding, but not the process that serves queries.
The process that serves queries, kept running so the first query after idle is instant.
A fixed per-branch charge that’s the same whether the branch is busy or asleep.

These are the canonical pages worth opening, especially for the numbers in this lesson, which drift over time, so trust the docs over any figure printed here.