Every Push Is Production
Any branch, any commit — the deck you just pushed is the deck at lossless-decks.vercel.app.
Every Push Is Production
Why Care?
There's a talk to give, and the deck is still moving. The gap that matters right before a presentation is the one between "I fixed that slide" and "the link I'm about to open shows the fix." Vercel's default closes that gap for exactly one branch and leaves every other branch on a preview URL nobody wrote down.
Now it closes for all of them. Commit on main, on fix-slide-7, on whatever branch the moment produced — push it, and https://lossless-decks.vercel.app becomes that commit. There is one URL to remember and no promote step to forget.
What's New?
A production deploy on every branch.
.github/workflows/deploy-production.ymlfires onbranches: ['**']and runsvercel deploy --prebuilt --prod.Newest push wins. A
concurrencygroup withcancel-in-progress: truekills an in-flight build when a newer commit lands, so production converges on the latest commit rather than on whichever build happened to finish last.Vercel's own Git auto-deploy is off.
vercel.jsonsetsgit.deploymentEnabled: false, leaving the workflow as the single deploy path.The site URL follows the renamed Vercel project. The project was
lossless-slides-siteand is nowlossless-decks, so its generated hostname moved with it.astro.config.mjsandpublic/robots.txtstill named the old one, which now 404s, so canonical URLs and the sitemap were both advertising a dead hostname.The README says how deployment works, including which secrets the workflow needs.
The Story
The ask was a vercel.json that always puts the latest push on production regardless of branch. The current Vercel docs say that file cannot do it. Its Git section offers git.deploymentEnabled — an on/off switch per branch glob — and the legacy github.* flags. There is no key that sets a deployment's target. Production branch is a single-branch project setting, and one branch is the most Vercel's own integration will ever promote.
So the target has to be set by whoever runs the deploy, which means running it yourself. The Vercel CLI takes --prod on any deploy from anywhere, and GitHub Actions will happily run it on a '**' branch filter.
That creates a second problem worth naming: two deploy paths on one repo. Left alone, Vercel's integration would build every push as a preview at the same time the workflow built it as production — double builds, and on the production branch, a race. Hence deploymentEnabled: false. The switch that cannot make every branch production can at least get out of the way of the thing that can.
Checking the live URL to confirm the deployment surfaced a stale hostname. astro.config.mjs carried lossless-slides-site.vercel.app behind a comment reading "Placeholder until the production domain is chosen" — which read as never-configured, but was actually right at the time: a Vercel project's generated hostname is its project name, and the project was called lossless-slides-site. Renaming it to lossless-decks moved the hostname and released the old subdomain, which now returns 404. So the config wasn't a placeholder that never resolved; it was a correct value that a rename invalidated, and nothing in the repo tracks that rename automatically.
How It Works
The workflow does what a Vercel build would do, on GitHub's runner:
vercel pull --environment=productionfetches the project's production environment variables, soSITE_URLand anything else configured in the dashboard reaches the build exactly as it would on Vercel's own infrastructure.vercel build --prodproduces.vercel/outputlocally.vercel deploy --prebuilt --produploads that output and aliases it to production.
Building on the runner rather than uploading source is what makes --prod honest here: the artifact that gets promoted is the one the workflow just built and can show you the logs for.
Three repo secrets carry the credentials — VERCEL_TOKEN, VERCEL_ORG_ID, VERCEL_PROJECT_ID. The org and project IDs come out of .vercel/project.json after pnpm dlx vercel@latest link; that directory stays gitignored.
What's Next
Set the three repo secrets. Until they exist the workflow fails and
git.deploymentEnabled: falsemeans nothing else deploys either. The deployment currently live stays up, but no new commit reaches it.Push once and watch the run, before relying on it the day of the talk.
Decide whether a scratch branch should be exempt. Every branch is production now, which is the point and also the whole risk; a
'!scratch'exclusion in the branch filter is the escape hatch if one is wanted.Point a custom domain at it, and update
astro.config.mjspluspublic/robots.txttogether when that happens.
Related
[[2026-09-15_01]]
