← Back to blog

Frontend App Versioning: From Git Tag to Page Footer

“Which version is this?” is a question that should never require detective work. Yet on most frontend projects, it does. A user reports a bug, attaches a screenshot, and you’re left guessing whether they’re on yesterday’s deploy or last week’s. A staging environment looks broken, but nobody knows if the latest push actually made it there.

One git command fixes this.

git describe --tags --always

This outputs something like v1.4.2-3-ga1b2c3d — the nearest tag, how many commits since it, and the short commit hash. If there are no tags, you get just the hash. Either way, you have a unique identifier for the exact code that’s running.

The Full Pipeline

Here’s the flow from tagging a release to seeing the version in production:

git tag v1.5.0
build script
environment.ts
footer + Sentry

Every step is automated. Tag once, and the version appears everywhere it needs to be.

Generate the Version at Build Time

You need a script that runs before the Angular build. It reads the version from git and writes it into a file your app can import.

import { execSync } from "child_process";
import { writeFileSync } from "fs";

const version = execSync("git describe --tags --always").toString().trim();
const branch = execSync("git rev-parse --abbrev-ref HEAD").toString().trim();
const timestamp = new Date().toISOString();

const content = `export const APP_VERSION = "${version}";
export const APP_BRANCH = "${branch}";
export const BUILD_TIME = "${timestamp}";
`;

writeFileSync("src/environments/version.ts", content);

Run this script as a prebuild step in your package.json:

{
  "scripts": {
    "prebuild": "ts-node scripts/write-version.ts",
    "build": "ng build --configuration production"
  }
}

Every build now captures the exact git state. No manual version bumping. No forgetting to update a constant.

Inject Into Environment

Your Angular environment file imports the generated version:

import { APP_VERSION, APP_BRANCH, BUILD_TIME } from "./version";

export const environment = {
  production: true,
  version: APP_VERSION,
  branch: APP_BRANCH,
  buildTime: BUILD_TIME,
};

The version.ts file is generated fresh on every build, so it always matches what’s in git. Add it to .gitignore — it’s a build artifact, not source code.

This is the part users and QA actually see. A small version string in the page footer. Nothing fancy, but incredibly useful.

import { Component } from "@angular/core";
import { environment } from "../../environments/environment";

@Component({
  selector: "app-footer",
  template: `
    <footer>
      <span class="version">{{ version }}</span>
    </footer>
  `,
})
export class FooterComponent {
  version = environment.version;
}

Now when someone reports a bug, you can say “what version number is in the bottom right?” and get an exact answer. No more back-and-forth. No more “I think it was updated recently.”

Wire Up Sentry

This is where versioning really pays off. Sentry can group errors by release, show you which deploy introduced a regression, and link source maps to the exact commit.

import * as Sentry from "@sentry/angular";
import { environment } from "./environments/environment";

Sentry.init({
  dsn: "https://your-dsn@sentry.io/project-id",
  release: environment.version,
  environment: environment.production ? "production" : "development",
});

With this in place, every error Sentry captures is tagged with the version that caused it. You can filter errors by release, see when a specific error first appeared, and know exactly which commit to investigate.

Key insight: Without release tracking, Sentry shows you what broke. With it, Sentry shows you when and where it broke — which deploy, which commit, which change.

What This Gets You

The difference between a project with versioning and one without is night and day:

Scenario Without versioning With versioning
User reports a bug "I think I saw this yesterday" "I'm on v1.4.2-3-ga1b2c3d"
QA finds a regression Check deploy logs, guess which build Compare footer version to git tags
Sentry error spike All errors in one pile Filter by release, see which deploy caused it
Staging looks wrong "Did the deploy go through?" Check footer, compare to expected version
Rollback needed Which commit was the last good deploy? Sentry shows the last stable release

The Tagging Workflow

Keep it simple. When you’re ready to release, tag and push:

git tag v1.5.0
git push origin v1.5.0

Your CI pipeline picks up the tag, runs the build (which generates the version file), deploys it, and the version shows up in the footer and Sentry automatically.

For feature branches or staging deploys, git describe still works. You’ll get something like v1.4.2-7-g3e8f1a9, which tells you “7 commits ahead of v1.4.2, at commit 3e8f1a9.” That’s more than enough to track down any issue.

The Takeaway

Set this up once and forget about it. The build script writes the version, the footer displays it, Sentry tracks it. Every deploy is identifiable. Every bug report includes context. Every error in Sentry links back to a specific release.

It takes about 30 minutes to wire up. You’ll save more than that the first time someone asks “which version is deployed?” and you have an instant answer.


Sources: