·4 min read·Mounaji Studio

Give your site an endpoint that says which commit it came from

Thirty lines in a Next.js route, and "which version is live?" is answered with a curl and a git rev-parse. How we built it for vendear, where the commit comes from, and why the answer also says how the deploy was made.

engineeringdeploywebvendear

Also in: Español

You fix something, merge it, and someone asks whether it is in production yet. If the site deploys on every push to main, nobody ran the deploy while watching the screen, so the answer usually comes from comparing the time of the last deploy with the time of the last commit. That is an approximation: two close timestamps do not prove the build came from that commit.

On vendear, our storefront site with an AI salesperson, the question is now answered like this:

curl -s https://www.vendear.com/api/version
{"commit":"ef66746e2df30970b7968c6e68d0d7066ec92153","branch":"main","source":"vercel-git","env":"production","builtAt":null}

And compared against the repo:

git rev-parse origin/main
# ef66746e2df30970b7968c6e68d0d7066ec92153

They match: what is live is main. That reading is from October 5, 2026, and there is nothing left to interpret.

The whole route

It is a Next.js (App Router) route handler at src/app/api/version/route.js:

export const dynamic = "force-dynamic";

export function GET() {
  const fromGit = process.env.VERCEL_GIT_COMMIT_SHA || null;
  const fromCli = process.env.COMMIT_SHA || null;

  return Response.json(
    {
      commit: fromGit || fromCli,
      branch: process.env.VERCEL_GIT_COMMIT_REF || process.env.COMMIT_BRANCH || null,
      source: fromGit ? "vercel-git" : fromCli ? "local-cli" : null,
      env: process.env.VERCEL_ENV || null,
      builtAt: process.env.BUILT_AT || null,
    },
    { headers: { "cache-control": "no-store" } }
  );
}

That is all of it. The interesting part is three small decisions.

1. The commit comes from two places, and the answer says which

Where the commit comes from, in order: if VERCEL_GIT_COMMIT_SHA is set, source is vercel-git; otherwise, if COMMIT_SHA is set, source is local-cli; if neither is set, commit and source are null

Vercel sets VERCEL_GIT_COMMIT_SHA on every deploy that comes from the connected repository. That is the normal path and needs no configuration.

Sometimes, though, a deploy is made by hand from a machine (to redeploy without a new commit, or after a build that failed for a transient reason), and what gets uploaded is the working folder, not a commit. Vercel builds from those files without .git, so inside the build there is no way to find the hash: it has to be passed in. Our deploy script does that with vercel --prod --build-env COMMIT_SHA=<hash>, and also sets COMMIT_BRANCH and BUILT_AT.

Both give you a hash, but they do not mean the same thing. vercel-git means "this was built from this commit of the repo". local-cli means "this was built from someone's disk, which was at this commit at the time". That is why source is in the response: with the hash alone, the two cases would look identical.

The script refuses to deploy if the folder has uncommitted changes. Otherwise the stamped hash would describe different code from what was uploaded.

If neither variable is set, commit and source come back null. A version endpoint that makes up a value when it does not know is worse than none.

2. Keep every cache out of it

dynamic = "force-dynamic" makes Next.js resolve the route on every request instead of freezing it at build time, and cache-control: no-store asks the CDN and the browser not to keep the response. Without them you risk the endpoint answering with the commit of a previous deploy, which is exactly the mistake it exists to prevent.

3. A check you can read with one command

The obvious path was to read the commit from the deploy metadata with vercel inspect. The value is stored (our script puts it there too, with --meta), but the JSON output of the CLI version we were using did not return it. A check you cannot read with a command is not much of a check: nobody will run it in a script or in CI.

curl plus git rev-parse can be automated. If the two hashes differ after a merge, the deploy did not pick up your change, whether it failed, got blocked or is still building. And you find out before a customer tells you.

Before you copy it

  • The hash is public. Anyone can see which commit the site came from. Without access to the repo, that does not give them the code. If the repo is public, it gives them a direct link to the exact code that is running, which is sometimes an advantage and sometimes not. Decide on purpose.
  • builtAt is null on deploys from git: only the manual deploy script sets it. If you want the build time on both paths, set it in your build too; if not, drop it from the contract.
  • Outside Vercel, swap the variable names for the ones your provider exposes, or have your pipeline write the commit into a variable of your own. The idea does not depend on the platform.

Thirty lines, and "which version is live?" went from investigation to a command. If any of this does not fit your stack, write to us.

Comments and corrections

Chat with us