Frontend application - show correct version number

Semantic versioning in the frontend – the version number should be part of the build artifact and displayed in the frontend. I've had a hard time with this so far – here is my final approach including the necessary adjustments to solve the problem once and for all.

4 minutes
Hero image

Background

When versioning software, we at konzentrik use the concept of Semantic Versioning. The upcoming release is automatically given a version number based on the change history. We determine this based on the commits when we create the productive code.

In our case, this version number should always be available on the front end. On the one hand, "invisible" in the header of the index.html if the customer does not want it to be displayed, or on the other hand, e.g., in the sidebar, so that this identifier is visible to the customer. In bug reports, the customer must specify this version number in the report.

Problem with previous solutions

As part of our automation, the new version number is written as a GitHub action in the package.json in the version field. This is the last step in the automatic process in the event of a successful release. A commitment is made to the change to package.json.

We used a constant defined in the React context for the frontend display. This value returns the string from the version field of the package.json. Because we write the new version at the end of the workflow, the version number in the built package is not the current version number but the previous one.

const appVersion = VERSION

Adapting the workflow is the obvious conclusion—but it's not a good idea. Only after all steps have been completed successfully can we ensure the new version can be built. Accordingly, only then can we create the commit. So, how do we get out of this chicken-and-egg problem?

The new solution

The goal remains: The build artifact, whether in a Docker container, as a zip file, or directly uploaded to a server, should contain the correct version number. Using the constant to read from the package.json is not a failing approach.

Consequently, the version number must be determined and written into the HTML header and the sidebar before building the frontend code. Only then can the npm run build command be executed. The necessary adjustments in the GitHub Action YAML are as follows:

  1. Prepare a placeholder for the version number in the source code

Insert the following line in the HTML header in index.html

Additionally, insert the placeholder in the EnvVar service, which provides encapsulated access to environment variables on the front end. This allows the value to be accessed in the sidebar, for example. Or somewhere else in the front end, whatever the exact UI template looks like.

const appVersion = 'SEMANTIC_RELEASE_VERSION'

  1. Install necessary dependencies and update plugin list in .releaserc

npm install -D semantic-release-export-data

// in .releaserc
// ...
  "plugins": [
    "@semantic-release/commit-analyzer",
    "@semantic-release/release-notes-generator",
    "semantic-release-export-data",
    "@semantic-release/github",
// ...
  1. Execute all preconditions, such as check-out, installing dependencies, tests, linter, etc., within the GitHub Action YAML

  2. Dry-run of Semantic Release enables access to the next version via ${{ steps.get-next-version.outputs.new-release-version }}

# In ci.yaml
- name: Semantic release dry run to determine version
   run: npx semantic-release --dry-run
   id: get-next-version
   env:
     GITHUB_TOKEN: ${{ secrets.PAT_SEMANTIC_RELEASE }}
  1. Build step with Docker gets the new version number as BUILD_ARG
- name: Build Docker image
   uses: docker/build-push-action@v5
   with:
     context: .
     labels: ${{ steps.meta.outputs.labels }}
     push: true
     tags: ${{ steps.meta.outputs.tags }}
     build-args: |
       GITHUB_NPM_TOKEN=${{ secrets.PACKAGE_REGISTRY_TOKEN }}
       NODE_VERSION=${{ steps.nvm.outputs.NODE_VERSION }}
       SEMANTIC_RELEASE_VERSION=${{ steps.get-next-version.outputs.new-release-version }}
  1. Replace the placeholders in the Dockerfile with the new version number
FROM node:${NODE_VERSION}-alpine AS build
# ensure the build arg is accessible in the build stage
ARG SEMANTIC_RELEASE_VERSION

WORKDIR /app
ENV PATH=/app/node_modules/.bin:$PATH
COPY package.json ./
COPY package-lock.json ./
RUN npm install
COPY . ./

# replace SEMANTIC_RELEASE_VERSION placeholder with actual version
RUN echo "SEMANTIC_RELEASE_VERSION: ${SEMANTIC_RELEASE_VERSION}"
RUN sed -i "s/SEMANTIC_RELEASE_VERSION/${SEMANTIC_RELEASE_VERSION}/g" index.html
RUN sed -i "s/SEMANTIC_RELEASE_VERSION/${SEMANTIC_RELEASE_VERSION}/g" src/services/EnvVars.ts

RUN npm run build

Attention: it is important to name the ARG SEMANTIC_RELEASE_VERSION after the build stage, and otherwise the variable is empty → see Docker documentation for further details.

Conclusion

With the approach described above, the version number becomes part of the build artifact and thus guarantees the correct display in the application. The necessary interaction of semantic versioning and Docker Build in the GitHub action requires a handful of configurations, which can, however, be used universally for any frontend project. This means that once set up, the problem with the correct version number is solved.

You are welcome to write to me if you need access to the complete GitHub action YAML file, the Dockerfile or the other configuration files.

call to action background image

Subscribe to my newsletter

Receive once a month news from the areas of software development and communication peppered with book and link recommendations.