How to Cache bust web app
Murtaza Nathani
Posted on June 5, 2022
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",
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 usingstandard-version
to always get ourpackage.json
upgraded.meta.json
version is created inpublic
folder using a scriptbuild-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}`);
});
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" }
Before we proceed to the next step, let me inform you that we are using the following frameworks in our app:
- Reactjs: react
- Routing: react-router-dom
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;
The hooks above is doing the following:
-
useEffect having a
deps
oflocation
, which runs on every change of the route. -
parseVersion
is a pure function that can format the version like"1.0.5"
into a number105
, so we can compare the versions. - 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. - Using the response of
meta.json
we are checking ifpackageVersion
is less thanmetaVersion
, which means the new build has deployed and the browser is using the old cached build, so the app needs to reload. - 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.
Posted on June 5, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.