After every successful production deployment, Clear cache with Hard reload
What is the problem that we will be solving?
Every time a production build is created and deployed, the user has to do a hard refresh of the webpage to clear the cache and to download the new javascript bundle to view the new changes done to the app. This is not the ideal way to ask the user to refresh every time once new thing is deployed to production.
OUR PROBLEM AND ITS SOLUTIONs:
Now, here comes the interesting part. Lets go into flashback and try to understand this problem once again and try to think what all can be possible solutions for this type of problem.
First Solution:
There should be a server that should be hosting your web app and whenever serve is going to detect the changes, with the help of socket.io middleware we can transfer our updates to the client via TWO way communication and let our server hosted frontend refreshed with updates.
But the challenge with type of problem is, you need to make sure that your web-app should be doing SSR somewhere down the line. otherwise for CSR based web-apps this solution won’t work. The other solution will come down once we start implementing that.
Our End Goal:
After every prod deployment, user should be either asked by pop up, saying some changes are detected, if you wanna refresh the page by reloading or wanna as is? or else giving user an indication mark over the page saying some changes has been deployed, whenever you feel feasible, you can refresh the page to get them adopted.
Idea in terms of code to get this resolved:
We will create a new app by using create-react-app. I will give the app name as clear-cache-web-app
npx create-react-app clear-cache-web-app
We will get into the folder and lets get started.
cd clear-cache-app
You will be able to see public folder in this. Create a meta.json file inside it.
touch public/meta.json
And put in the basic JSON
{"buildDateTime" : ""}
Once all the packages are installed, test run the app once
npm start
In package.json
file add the following code at the end of the file
"buildDateTime": ""
Create a new file updateBuildJSON.js
. It should reside in the main folder besides package.json
.updateBuildJSON.js
will have following code:
const fs = require("fs");
const filePath = "./package.json";
const packageJson = JSON.parse(fs.readFileSync(filePath).toString());
packageJson.buildDateTime = new Date().getTime(); // this will be current Time
fs.writeFileSync(filePath, JSON.stringify(packageJson, null, 2));
const jsonData = {
buildDateTime: packageJson.buildDateTime,
};
const jsonContent = JSON.stringify(jsonData);
fs.writeFile("./public/meta.json", jsonContent, "utf8", function (error) {
if (error) {
console.log("An error occured while saving build date and time to meta.json");
return console.log(error);
}
console.log("Latest build date and time updated in meta.json file");
});
Whenever a new build is generated we will call this file. What we are doing in updateBuildJSON.js
is two main things:
- We are generating a current date/time value in epoch.
- We are updating that value in meta.json file. This file will be automatically updated every time a new build is created.
Now update your build command in package.json
file as below:
"build": "node ./updateBuildJSON.js && react-scripts build",
Now, here comes the interesting part. Lets go into flashback and try to understand this problem once again and try to thing what all can be possible solutions for this type of problem.
First Solution:
There should be a server that should be hosting your web app and whenever serve is going to detect the changes, with the help of socket.io middleware we can transfer our updates to the client via TWO way communication and let our server hosted frontend refreshed with updates.
But the challenge with type of problem is, you need to make sure that your web-app should be doing SSR somewhere down the line. otherwise for CSR based web-apps this solution won’t work.
Second Solution:
If we straight away having a lack of server -client communication and web-app is client side rendered, then this solution is perfect for you and you are bang on. In order to achieve the goal, we will create a higher-order Component (HOC) called withClearCache. Our main App component will be passed as an argument to the withClearCache. The idea here is that before our content of App gets loaded into the browser we need to check whether the our content is latest or not.
We will create a new file into the src
folder named ClearCache.js
with the following code:
import React, { useState, useEffect } from "react";
import packageJson from "../package.json";
const buildDateGreaterThan = (latestDate, currentDate) => {
if (latestDate > currentDate)) {
return true;
} else {
return false;
}
};
function withClearCache(Component) {
function ClearCacheComponent(props) {
const [isLatestBuildDate, setIsLatestBuildDate] = useState(false);
useEffect(() => {
fetch("/meta.json")
.then((response) => response.json())
.then((meta) => {
const latestVersionDate = meta.buildDateTime;
const currentVersionDate = packageJson.buildDateTime;
const shouldForceRefresh = buildDateGreaterThan(
latestVersionDate,
currentVersionDate
);
if (shouldForceRefresh) {
setIsLatestBuildDate(false);
refreshCacheAndReload();
} else {
setIsLatestBuildDate(true);
}
});
}, []);
const refreshCacheAndReload = () => {
if (caches) {
// Service worker cache should be cleared with caches.delete()
caches.keys().then((names) => {
for (const name of names) {
caches.delete(name);
}
});
}
// delete browser cache and hard reload
window.location.reload(true);
};
return (
<React.Fragment>
{isLatestBuildDate ? <Component {...props} shouldRerender={true} /> : null}
</React.Fragment>
);
}
return ClearCacheComponent;
}
export default withClearCache;
As HOC is a wrapper over your entire react application, It will let the all inner children to get re-rendered, once your devops guy will build the production build.
After making above changes, you will be able to see your changes in the App.jsx will look something like this.
const ClearCacheComponent = withClearCache(MainApp);
function App() {
return <ClearCacheComponent />;
}
function MainApp(props) {
// THIS IS THE OPTION THAT YOU CAN OPT, BUT DIFFERENT BROWSERS WILL // HAVE THEIR DIFFERENT MESSAGES FOR PROMPTING BEFORE RELOAD. ELSE // IF YOU ARE USING BROWSERROUTER IN YOUR CODE. GIVE A SHOT FOR //THIS. https://v5.reactrouter.com/web/api/BrowserRouter(forceRefresh prop)// useEffect(() => {
// window.addEventListener("beforeunload", () => true);
// return () => window.removeEventListener("beforeunload", //() => false);
// }, []); return (
<div className="App">
// your actual app structure
</div>
);
}
export default App;
So that’s it. You only need to run npm run build
command and your build will be generated with new build date time.
AND YOU ARE BANG ON…HAPPY CODING!!!