← Back to blog

Gating Vercel deploys on CI success

Why we route Vercel deploys through GitHub Actions instead of letting them fire on every push — and how to wire it up without losing the rest of Vercel's niceness.

· Updated

Vercel’s default behavior is “deploy on every push.” Connect the repo, and every commit to every branch triggers a build. Fast feedback, zero config — great for a single-app project.

In a monorepo with multiple deploy targets (a dashboard and a marketing site, say), a few things start to grate:

Our setup

success

failure

dashboard changed

landing changed

neither

git push to main

CI workflow

Deploy workflow

no deploy

per-project change check

deploy dashboard

deploy landing

skip

Two pieces make this work.

First, a disable switch in each Vercel project’s vercel.json:

{
  "git": { "deploymentEnabled": false }
}

This keeps the Git integration connected (env var sync, vercel pull auth) but stops the automatic deploy on push. Every future deploy now has to come from somewhere else.

Second, a Deploy workflow in GitHub Actions that fires via workflow_run after the CI workflow completes with success. It runs vercel pullvercel buildvercel deploy --prebuilt using the Vercel CLI. That’s the “somewhere else.”

Per-project change detection

The Deploy workflow includes a per-project check: it calls a small bash script that diffs the current commit against the previous deploy’s SHA and checks whether any files under that project’s watched paths changed. If nothing relevant changed, the job exits early.

if git diff --quiet "$BASE" HEAD -- "${PATHS[@]}"; then
  exit 0  # skip
fi

Simple, idempotent, and no extra infrastructure.

Trade-offs

The big one: preview deploys on PRs go away. If you want review links without CI, you either revert to Vercel’s default or add a second workflow that deploys previews after CI. We opted for the former: no preview deploys until tests pass. It shifts friction earlier in the loop.

A secondary gotcha: if a CI run fails and later commits don’t touch the project’s watched paths, the pending changes never auto-deploy. Adding a workflow_dispatch trigger with a force=true input covers this — a manual button to deploy whatever’s on main, bypassing the change check. We hit this within the first week.

When to adopt this

If your repo has one app, Vercel’s defaults are fine. Reach for CI-gated deploys once you have:

Each of these raises the cost of “deploy first, ask questions later.”