Tracking Frontend Vite Changes

Always Know What Version of Your Frontend Is Running (Vite + Git)

Ever deploy a frontend and wonder “wait, is this actually the latest version?” You squint at the UI, try to remember what changed, and eventually crack open the network tab or SSH into a server.

There’s a simpler way. Inject the git commit hash at build time and display it right in the UI. Three lines of config, zero runtime cost.

The Technique

Vite has a define option that performs global string replacement at build time. You give it a constant name and a value, and Vite swaps every occurrence in your source code with that value — before the browser ever sees it.

We combine this with git rev-parse --short HEAD, which gives you the 7-character abbreviated commit hash (e.g., ff9c56d).

Step 1: Update vite.config.ts

1
2
3
4
5
6
7
8
9
10
11
import { execSync } from "child_process"
import { defineConfig } from "vite"

const commitHash = execSync("git rev-parse --short HEAD").toString().trim()

export default defineConfig({
// ...your existing config
define: {
__COMMIT_HASH__: JSON.stringify(commitHash),
},
})

execSync runs at build time (when you run npm run dev or npm run build), not in the browser. JSON.stringify wraps the value in quotes so Vite injects it as a string literal.

Step 2: Declare the Type (TypeScript)

If you’re using TypeScript, add a declaration so the compiler knows about the global:

1
2
3
4
// src/vite-env.d.ts
/// <reference types="vite/client" />

declare const __COMMIT_HASH__: string

If you’re using plain JavaScript, skip this step.

Step 3: Use It Anywhere

1
2
3
4
5
6
7
function Footer() {
return (
<footer>
<p>Build: {__COMMIT_HASH__}</p>
</footer>
)
}

That’s it. When Vite builds your app, every reference to __COMMIT_HASH__ is replaced with the literal string "ff9c56d" (or whatever your current commit is). There’s no runtime lookup, no API call, no environment variable to manage.

What You Get

  • In development: The hash updates every time you restart the dev server. You always know which commit you’re working from.
  • In production: The hash is baked into the built JavaScript. You can glance at the footer and immediately know what’s deployed.
  • In debugging: A user reports a bug and sends a screenshot. The commit hash is right there — you know exactly what code they’re running.

Going Further

You can inject more than just the commit hash:

1
2
3
4
5
6
7
8
9
const commitHash = execSync("git rev-parse --short HEAD").toString().trim()
const buildTime = new Date().toISOString()

export default defineConfig({
define: {
__COMMIT_HASH__: JSON.stringify(commitHash),
__BUILD_TIME__: JSON.stringify(buildTime),
},
})

Or include the branch name, the tag, or whether the working tree is dirty:

1
2
3
4
const branch = execSync("git rev-parse --abbrev-ref HEAD").toString().trim()
const isDirty = execSync("git status --porcelain").toString().trim() !== ""
const commitHash = execSync("git rev-parse --short HEAD").toString().trim()
+ (isDirty ? "-dirty" : "")

The define option is powerful because it runs arbitrary Node.js at build time and collapses the result into a static string. No runtime overhead, no secrets leaking, no extra dependencies.

The Catch

The hash is captured when the build starts. If you’re running npm run dev and make new commits without restarting the dev server, the displayed hash will be stale. In practice this rarely matters — you restart the dev server often enough — but it’s worth knowing.

For production builds (npm run build), this is a non-issue. The hash is captured at build time, which is exactly when you want it frozen.