How to Cache bust web app

mnathani

Murtaza Nathani

Posted on June 5, 2022

How to Cache bust web app

Why do we need to do cache busting ?

Static file gets cached and can be stored for a long period of time before it ends up expiring. So in order to get the latest features and fixes we need to remove the cache so that browsers can get the latest updates.

Additionally, have you ever felt web application like soundcloud, facebook etc..., asking to reload or upgrade after deployment, no right ? how the hell are they doing the updates ?


Making sure we have cache invalidated in CDN

Cache busting solves the CDN caching issue by using a unique file version identifier.
As discussed in the previous article we used no-cache policies on some files to bust the cache using unique files names. Hence we are sure that cloudfront will always keep the updated files..


Lets bust the browser cache

So today will cover what is the one of the best approach to seamlessly bust the cache of the frontend application in the browsers when a deployment is done, without the user feeling the app was upgraded...

The trick

The trick is we keep updating the version of the application in meta file, which never gets cached... and to seamlessly upgrade, we perform a reload on the route change so that user would feel as if they are redirecting to a different view, but in our case we are actually cache busting our application to get the new update from the build we deployed.

Let's dig in to see how its possible.

How to check if a new build is generated

To know when we have a new build in browsers we keep two version of the application.

  • package.json version
  • meta.json version

What are these version and how we manage it

Here is the command prebuild that runs before every build to manage both versions as shown below:



    "release": "HUSKY=0 standard-version",
    "prebuild": "npm run release && node ./build-version",


Enter fullscreen mode Exit fullscreen mode
  • package.json version is kept and maintain using tools like Semantic versioning or Standard release which upgrades the package version after every deployment. Here we are using standard-version to always get our package.json upgraded.

  • meta.json version is created in public folder using a script build-version.js we wrote to make sure we always get a latest version after deployment.

build-version.js:



const fs = require('fs');
const { version } = require('./package.json');

fs.writeFile('./public/meta.json', JSON.stringify({ version }), 'utf8', (error) => {
  if (error) {
    console.error('Error occurred on generating meta.json:', error);
    return;
  }
  // eslint-disable-next-line no-console
  console.info(`meta.json updated with latest version: ${version}`);
});


Enter fullscreen mode Exit fullscreen mode

The above scripts takes the latest version from package.json which was upgraded using npm run release and save it to meta.json using fs.writeFile.

Here is how the output of the above script will look like:

meta.json:



{ "version": "108.0.0" }


Enter fullscreen mode Exit fullscreen mode

Before we proceed to the next step, let me inform you that we are using the following frameworks in our app:

Code to check application is upgraded

We created a hook that could be placed in a suitable position in your application, preferably on layouts/routes:



import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
import { version } from '../../../package.json';

const useCacheBuster = () => {
  const location = useLocation();
  const parseVersion = (str) => +str.replace(/\D/g, '');

  useEffect(() => {
    fetch(`/meta.json?v=${+new Date()}`, { cache: 'no cache' })
      .then((response) => response.json())
      .then((meta) => {
        if (meta?.version) {
          const metaVersion = parseVersion(meta.version);
          const packageVersion = parseVersion(version);
          if (packageVersion < metaVersion) {
            if (window?.location?.reload) {
              window.location.reload();
            }
          }
        }
      })
      .catch((error) => {
        console.error('something went wrong fetching meta.json', error);
      });
  }, [location]);

  return null;
};

export default useCacheBuster;


Enter fullscreen mode Exit fullscreen mode

The hooks above is doing the following:

  1. useEffect having a deps of location, which runs on every change of the route.
  2. parseVersion is a pure function that can format the version like "1.0.5" into a number 105, so we can compare the versions.
  3. On changing the app route, the hook fires and fetches /meta.json files from the root of the app, important thing to note here is we are passing a date param: and cache, to make sure this file never returns the cached content on fetching.
  4. Using the response of meta.json we are checking if packageVersion is less than metaVersion, which means the new build has deployed and the browser is using the old cached build, so the app needs to reload.
  5. If the above condition is true then reload it!.

NOTE: if you are using a CDN then you need to cache bust in CDN by adding the meta.json to behaviours as shown here

P.S: we can optimize the fetching of meta, by conditionalizing it on certain routes rather then all.

That's it folks..., all you need to perform cache bust in browser programmatically.


Conclusion

The solution above is useful for the scenarios when you have a frequent deployment to production.
Moreover, in my understanding for apps used in webview or apps saved on homepage could also be similarly busted with different reload methods...


Please feel free to comment on the approach, would love to hear your feedback on this.

Regards.

💖 💪 🙅 🚩
mnathani
Murtaza Nathani

Posted on June 5, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related