Preview deployments and rollbacks
A preview deployment is a complete copy of the site at its own address, built from a branch or a commit. A rollback moves the production domain back to an earlier copy. Together they make a release reversible: you look at the change before it is public, and you can retreat quickly if the look was not enough. This piece uses Vercel's documentation as the concrete system and checks each behaviour against it. Other hosts differ in the details, but the questions are the same on any of them.
What creates a preview
According to Vercel's environments page, a preview deployment is created by default when you push a commit to a branch that is not the production branch, open a pull request, or deploy from the command line without the --prod flag. One exception: the first deployment of a new project is always a production deployment, whichever branch it came from, and the preview rules apply after that.
Each deployment gets a generated URL, and there are two kinds. A branch URL always points at the latest deployment on that branch and stays the same as you push, which makes it the one to send to a reviewer. A commit URL points at the exact deployment of one commit. Generated URLs show the shapes: <project>-git-<branch>-<scope>.vercel.app for a branch, and a version with a unique hash for a commit.
Environment variables come with the environment. Previews use the Preview variables, and production deployments use the Production ones. The promotion page makes the consequence explicit: if you promote a preview to production and the variables differ, the deployment will use the production values, and a preview's variables cannot be used in production.
What a preview can prove
A preview shows that the change builds, renders and links together. It is the right place for the checks that need a URL rather than a developer's machine.
| A preview can show | A preview cannot show |
|---|---|
| The build completes and the pages render | Behaviour that depends on Production variables |
| Content, layout at several widths and keyboard paths | Anything bound to the production domain, such as cookie scope or sign-in redirect addresses |
| Internal links and redirects resolve | How real traffic and warm caches behave |
| Headers and metadata the project sets | A canonical address that points at the final host, unless you set it |
Where you want a rehearsal with production configuration, Vercel documents a staged production deployment. You disable "Auto-assign Custom Production Domains" under the Production environment's branch tracking. Deployments from the production branch then wait, and you verify them at their generated URL and promote them, with no rebuild. The same page warns that such a deployment uses production variables, so tests can reach production services and data. Where that is unacceptable, it points to a custom environment or a staging preview branch with its own credentials. It lists three staging patterns and their availability, which varies by plan for custom environments and not for the other two.
Who can open it
Whether a preview URL is public depends on project settings. Vercel's Deployment Protection page describes methods including Vercel Authentication and password protection, and scopes including Standard Protection, which it recommends for most projects and which protects all deployments except production domains. It states that protection requires authentication for all requests. Check what your project is set to before you share a link.
Keeping previews out of search
The response headers page says Vercel adds x-robots-tag: noindex to preview deployments and to outdated production deployments, to stop search engines crawling duplicates. It also says how to avoid the header: assign a production domain to the deployment, or turn it off in vercel.json. Do neither for an ordinary preview. For how the directive works in Google's systems, see sitemaps, robots.txt and Search Console.
Asking for a useful review
A link alone gets a glance. Send the reviewer the branch URL together with the list of routes that changed, the widths and input methods that matter, and one specific question, such as whether the new layout of the contact page still reads clearly at phone width. Ask for findings on the pull request so the evidence stays with the change.
The branch URL moves as you push, which is why it suits an ongoing review and does not suit a sign-off. When someone approves, record the commit URL, because that address always shows the build they looked at. If the commit you promote is a different one, the approval no longer applies to it.
A smoke test for a preview
Looking at a few pages is not the same as checking the ones that matter. The script below takes the preview origin, the production origin and a list of paths. For each path it reports the status, whether a title exists, whether the canonical link names the production host, and the x-robots-tag header. The canonical rule is a house convention here: a preview that canonicalises to itself is easy to promote with the wrong host baked in.
// preview-smoke.mjs usage: node preview-smoke.mjs <preview-origin> <production-origin> /path /other/path
const [previewOrigin, prodOrigin, ...paths] = process.argv.slice(2);
const prodHost = new URL(prodOrigin).host;
let failed = 0;
for (const path of paths) {
const res = await fetch(previewOrigin + path, { redirect: 'manual' });
const html = await res.text();
const title = (html.match(/<title>([\s\S]*?)<\/title>/i) || [])[1];
const canonical = (html.match(/<link[^>]*rel="canonical"[^>]*href="([^"]*)"/i) || [])[1];
const problems = [];
if (res.status !== 200) problems.push('status ' + res.status);
if (!title || !title.trim()) problems.push('no title');
if (!canonical) problems.push('no canonical');
else if (new URL(canonical).host !== prodHost) problems.push('canonical host is ' + new URL(canonical).host);
const robots = res.headers.get('x-robots-tag') || 'none';
console.log([problems.length ? 'FAIL' : 'ok ', path, 'x-robots-tag: ' + robots, problems.join('; ')].join('\t'));
if (problems.length) failed++;
}
process.exit(failed ? 1 : 0);
The script was tested against a small local server standing in for a preview, serving three fictional pages and adding the noindex header to every response. It is a test of the script's logic, not of Vercel. One page had a canonical on the wrong host, one had an empty title and one path did not exist:
ok / x-robots-tag: noindex
FAIL /about/ x-robots-tag: noindex canonical host is preview.test
FAIL /journal/ x-robots-tag: noindex no title
FAIL /missing/ x-robots-tag: noindex status 404; no title; no canonical
The exit code was 1, so the script can sit in a CI step. It shows only that pages are present and well-formed, so a person still reads the changed pages. For pages with heavy graphics, a performance budget for a site with WebGL covers what to measure. If the preview is protected, the script sees the authentication response instead of your page, and the docs describe Protection Bypass for Automation for that case.
What a rollback does
Vercel's Instant Rollback page describes it as a way to quickly revert to a previous production deployment. It says the rollback happens instantaneously, and describes the mechanism separately: Vercel points your domains back to the selected deployment. The page lists what to keep in mind:
- The rolled-back deployment is treated as a restored version of a previous deployment, and the configuration it used may become stale.
- Environment variables changed in project settings are not applied, because the rollback goes to a previous build.
- Cron jobs revert to the state of the rolled-back deployment.
- Custom aliases are only included if they were present on the previous production deployment.
- Only one rollback can run at a time on a project.
- Deployments previously aliased to a production domain are eligible. Deployments that were never aliased to production, such as most previews, are not.
- Which earlier deployments you can choose depends on the plan: some are limited to the immediately previous deployment.
The confirmation step also reminds you that external APIs, databases and CMSes used by the current or previous deployment may behave differently. A rollback is a change of code and static output, and it does not reverse data.
From the command line
The same move is available as vercel rollback <deployment-url>, and vercel rollback status shows any rollback still pending. The command waits for the rollback to complete. Its --timeout option sets how long it waits, and the page says a timeout of 0 exits straight after requesting the rollback, without affecting the rollback itself. The commands here come from the CLI documentation and were not run for this piece, since a rollback on a live project is an action to rehearse on one you can afford to disturb.
The trap after a rollback
The documentation is blunt about the second half. After a rollback, Vercel turns off auto-assignment of production domains. New pushes to the production branch will not replace the rolled-back deployment, and they do not go live automatically. You can fix the bug, merge it, watch the build succeed, and find the site still serving the old version.
To return to normal, promote a deployment: use the Undo Rollback button on the production tile, or run vercel promote with a deployment ID or URL. Promotion re-enables auto-assignment. The practical rule is that a rollback is not finished until the undo is done, so write "promote the fix" into the incident steps.
Choosing between the four moves
| Situation | Move | Why |
|---|---|---|
| Production is broken, an earlier version was good, the cause is unknown | Instant Rollback | Fast, with no rebuild |
| A tested fix exists on a branch | Merge, or promote the verified deployment | Promotion assigns production domains without a rebuild |
| A configuration or variable change must take effect | Redeploy, or a new deployment | A rolled-back deployment keeps its earlier build |
| The bad change is in the code history and should not return | Revert the commit in Git | Keeps the repository and production in agreement |
| A migration changed data | Handle outside the rollback | Data is not restored by a domain change |
Promotion and redeploy differ: promotion moves domains to a deployment that already exists, while redeploy re-runs the build for a commit or configuration. The promotion page adds a limit: a deployment that was promoted once cannot be promoted again, and to use it again you roll back to it.
An incident sequence
- Decide by time, not by diagnosis. If production is broken and the previous deployment was good, roll back first.
- Confirm on the real domain that the previous version is live, with a request for a page and a check for a marker you know belongs to the old version.
- Open a branch for the fix and let its preview build.
- Run the smoke script and read the changed pages on the preview.
- Promote the verified deployment or merge, which re-enables automatic assignment.
- Confirm production again and write down what failed and which check would have caught it.
Checklist
- Every change gets a preview, and its branch URL goes to the reviewer.
- Previews keep their
noindexheader, and protection settings are checked before links are shared. - The smoke script passes on the preview before merge.
- Rollback is known to leave data, external services and edited variables alone.
- Auto-assignment is re-enabled by promoting a deployment after every rollback.
Questions and answers
Why did my fix not go live after I rolled back?
Vercel's documentation says that after a rollback it turns off auto-assignment of production domains, so new pushes to the production branch do not replace the rolled-back deployment. You restore normal behaviour by promoting a deployment, from the dashboard's Undo Rollback button or with vercel promote.
Does a rollback undo a database change?
No. A rollback points your domains back to an earlier deployment. The rollback dialog reminds you that the behaviour of external APIs, databases and CMSes used by the current or previous deployments may differ, so schema and data changes need their own plan.
Can search engines index my preview URLs?
Vercel says it adds an x-robots-tag header with the value noindex to preview deployments and outdated production deployments to stop search engines crawling them. The header is not added if you assign a production domain to the deployment or disable it in vercel.json.
Can I roll back to a preview deployment?
Not directly. The documentation says only deployments previously aliased to a production domain are eligible for Instant Rollback, and most previews were never aliased. A preview can be promoted to production instead.
Sources
- Environments (Vercel documentation) vercel.com
- Instant Rollback (Vercel documentation) vercel.com
- Promoting deployments (Vercel documentation) vercel.com
- Response headers: x-robots-tag (Vercel documentation) vercel.com
- Deployment Protection (Vercel documentation) vercel.com