How to know which version of your frontend is deployed
Nowadays when you deploy a new version of your frontend application, you don’t push your code to production directly. Instead, you run (typically automatically) a deployment pipeline in the CI that builds your application, runs tests, and finally deploys the built assets to a hosting service.
This process takes some time, and when multiple developers are working in the same code base, it can happen that while one developer is deploying a new version, another developer pushes new changes to the main branch and triggers a new deployment pipeline. This creates a deployment race condition: if multiple pipelines run concurrently, the last one to finish determines which code is deployed, regardless of which was triggered first. This means you don’t really know which version of the code is currently deployed unless you check the deployment logs or the CI status page, which is slow and a lot of work.
Knowing exactly which version of your frontend is deployed is crucial for debugging issues, providing user support, and ensuring that hotfixes have been successfully rolled out.
We need a better (simpler and faster) way to know which version of our frontend application is currently deployed.
The Browser Cache Challenge
This also applies to the browser cache. Even if the deployed version is the correct one, the browser might still be serving an older version of the assets from its cache, leading to confusion, so we also need to know which version of the assets the browser is using.
Solution
The most reliable solution is to embed version information directly in the HTML entrypoint (index.html), since this file is the first thing the browser loads and helps bypass most caching issues.
While you could include the version in a separate text or JSON file, this approach has drawbacks. If the file wasn’t accessed before and you ask the user to retrieve it to get the version, you will get the correct one, but the browser can still be serving a cached version of the entrypoint. Since in a SPA the entrypoint is typically index.html, this file is the best candidate to include the version information.
Version ID
We need to define how we will get the version of our code. It should be a number or string that identifies the build uniquely and you can use it in other places to relate it with the code base. That will depend on your deployment strategy. For example:
- Git tag:
v1.2.3- if you are using git tags to mark releases - Commit hash:
a1b2c3d- for tracking exact code snapshots - PR number:
pr-1234- useful for preview deployments - CI build number:
build-5678- if you are using a CI/CD tool that provides build numbers
You can also combine it with the deployment environment (e.g., production, staging, development) to make it more informative, like production-v1.2.3 or staging-pr-1234.
Implementation
I will assume you are using Vite as your build tool, but you can find a way to apply this approach to other build tools like webpack or rollup.
We will create a simple Vite plugin that will replace placeholders in the index.html file with the version information.
// htmlEnvPlugin.ts
export const htmlEnvPlugin = (
env: Record<string, string>
): {
name: string
transformIndexHtml: (html: string) => string
} => {
return {
name: 'html-transform',
transformIndexHtml: (html: string): string =>
// Replace placeholders like %PLACEHOLDER% with values from env object
// The regex /%(.*?)%/g matches any text between % symbols
// p1 captures the placeholder name, which is used as a key to lookup the value
html.replace(/%(.*?)%/g, (match, p1) => env[p1] ?? '')
}
}
This plugin will look for placeholders in the format %PLACEHOLDER% and replace them with the corresponding value from the env object. This placeholders approach will allow you to add more information from the env vars if needed, for example the build date/time.
Now, we need to use this plugin in the Vite config file and pass the version information from the environment variables.
// vite.config.ts
import { defineConfig } from 'vite'
import { htmlEnvPlugin } from './htmlEnvPlugin'
export default defineConfig({
plugins: [
htmlEnvPlugin({
BUILD_VERSION: process.env.BUILD_VERSION || 'dev',
BUILD_DATE: new Date().toISOString()
})
]
})
}
We need to add the placeholder in the index.html file.
<!DOCTYPE html>
<html lang="en">
// ....
</html>
<!-- Build version: %BUILD_VERSION% -->
<!-- Build date: %BUILD_DATE% -->
Alternatively, if you need programmatic access to the version information from JavaScript, you can use <meta> tags in the <head> section:
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="build-version" content="%BUILD_VERSION%" />
<meta name="build-date" content="%BUILD_DATE%" />
<!-- ... other head elements ... -->
</head>
<body>
// ....
</body>
</html>
This allows you to read the version in your application code:
const version = document.querySelector('meta[name="build-version"]')?.content;
const buildDate = document.querySelector('meta[name="build-date"]')?.content;
Finally, we need to set the BUILD_VERSION environment variable in the CI pipeline before running the build command. How you do this will depend on your CI tool, but let’s assume you are using GitHub Actions. You can use the following step to set the variable using the PR number.
- name: Set build version
run: echo "BUILD_VERSION=pr-${{ github.event.pull_request.number }}" >> $GITHUB_ENV
With this setup, every time you deploy a new version of your frontend application, the index.html file will include the build version and date, allowing you to easily check which version is currently deployed by simply inspecting the HTML source in the browser.
You can use this approach to include other interesting information, for example, the backend API version your frontend is using.
Conclusion
By embedding version information directly in your index.html file, you eliminate the uncertainty around which code is running in production. This simple approach provides immediate visibility into your deployments, helps debug issues faster, and ensures your team can quickly verify that critical hotfixes have been rolled out successfully.
Sergio Carracedo