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:
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.
Show It in the Footer
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.
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:
- Sentry Release Management — official Sentry documentation on release tracking
- git describe — Git documentation for version string generation