DEV Community

Cover image for Clear Cache on build for React Apps.
Ammar Tinwala
Ammar Tinwala

Posted on • Edited on

Clear Cache on build for React Apps.

What problem are we trying to solve?

Every time a build is created and deployed, the user has to do a hard refresh of the webpage to clear the cache to view the new changes done to the app. This is not a practical solution to ask the user to do it

Our Goal:

I wanted a solution wherein every time I create a new build and deploy it to production, on a basic refresh of the page the user should be able to view the new changes.

We will create a new app by using create-react-app. I will give the app name as clear-cache-app



npx create-react-app clear-cache-app


Enter fullscreen mode Exit fullscreen mode

We will install moment library as well. You will understand its importance at a later stage.



cd clear-cache-app
npm i moment


Enter fullscreen mode Exit fullscreen mode

Once all the packages are installed, test run the app once



npm start


Enter fullscreen mode Exit fullscreen mode

In package.json file add the following code at the end of the file



"buildDate": ""


Enter fullscreen mode Exit fullscreen mode

Create a new file update-build.js. It should reside in the main folder besides package.json.
update-build.js will have following code:



const fs = require("fs");
const filePath = "./package.json";

const packageJson = JSON.parse(fs.readFileSync(filePath).toString());
packageJson.buildDate = new Date().getTime();

fs.writeFileSync(filePath, JSON.stringify(packageJson, null, 2));

const jsonData = {
  buildDate: packageJson.buildDate,
};

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");
});



Enter fullscreen mode Exit fullscreen mode

Whenever a new build is generated we will call this file. What we are doing in update-build.js is two main things:

  1. We are generating a current date/time value in epoch.
  2. We are updating that value in meta.json file. This file will be automatically generated every time a new build is created.

Now update your build command in package.json file as below:



"build": "node ./update-build.js && react-scripts build",


Enter fullscreen mode Exit fullscreen mode

Next, 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";
import moment from "moment";

const buildDateGreaterThan = (latestDate, currentDate) => {
  const momLatestDateTime = moment(latestDate);
  const momCurrentDateTime = moment(currentDate);

  if (momLatestDateTime.isAfter(momCurrentDateTime)) {
    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.buildDate;
          const currentVersionDate = packageJson.buildDate;

          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} /> : null}
      </React.Fragment>
    );
  }

  return ClearCacheComponent;
}

export default withClearCache;



Enter fullscreen mode Exit fullscreen mode

Let's go through the code in the above file to see what exactly we are doing here:

  • We are making an api call to meta.json file to access it's content. Browsers don't cache the api calls, so even if our files are cached we will always get the latest response from it. ```javascript

useEffect(() => {
fetch("/meta.json")
.then((response) => response.json())
.then((meta) => {
const latestVersionDate = meta.buildDate;
const currentVersionDate = packageJson.buildDate;

      const shouldForceRefresh = buildDateGreaterThan(
        latestVersionDate,
        currentVersionDate
      );
      if (shouldForceRefresh) {
        setIsLatestBuildDate(false);
        refreshCacheAndReload();
      } else {
        setIsLatestBuildDate(true);
      }
    });
}, []);
Enter fullscreen mode Exit fullscreen mode
* As you can see above, in the response we get the build date from `meta.json` file. We pass that value to a function `buildDateGreaterThan` which accepts two arguments the latest build date generated by `meta.json` file and the cached build date which we took from `package.json` file. This function compares the two date/time values, it returns true if the latest build date is greater than the cached build date else false. We are using the `moment` library for date/time comparison.

```javascript


const buildDateGreaterThan = (latestDate, currentDate) => {
  const momLatestDateTime = moment(latestDate);
  const momCurrentDateTime = moment(currentDate);

  if (momLatestDateTime.isAfter(momCurrentDateTime)) {
    return true;
  } else {
    return false;
  }
};



Enter fullscreen mode Exit fullscreen mode
  • If the lastest build date is greater than the cached build date we will delete the service worker cache, browser cache and do a hard reload.


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);
    };


Enter fullscreen mode Exit fullscreen mode
  • If the latest build date and the cached build date are same we don't clear the cache and we load the component. ```javascript

return (

{isLatestBuildDate ? : null}

);


Now I want to display my build date and time. Since the build date generated is in epoch, I have created two utility functions that will help me format the date in *dd-mm-yyyy hh:mm*

```javascript


/**
 * Function returning the build date(as per provided epoch)
 * @param epoch Time in milliseconds
 */
export const getBuildDate = (epoch) => {
  const buildDate = moment(epoch).format("DD-MM-YYY HH:MM");
  return buildDate;
};


Enter fullscreen mode Exit fullscreen mode

The final step is to call the Clear Cache Component in App.js and to display the build date in my UI. I have updated my App.js as following:



import React from "react";
import logo from "./logo.svg";
import "./App.css";
import packageJson from "../package.json";
import { getBuildDate } from "./utils/utils";
import withClearCache from "./ClearCache";

const ClearCacheComponent = withClearCache(MainApp);

function App() {
  return <ClearCacheComponent />;
}

function MainApp(props) {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>Build date: {getBuildDate(packageJson.buildDate)}</p>
      </header>
    </div>
  );
}

export default App;



Enter fullscreen mode Exit fullscreen mode

So that's it. You only need to run npm run build command and your build will be generated with new build date time.

The complete source code can be found on my github repo

Credit:

I want to give a shout out to Dinesh for his article on clearing the cache in react apps.

I have taken the core idea of clearing the cache from his article. Do check it out.

Top comments (29)

Collapse
 
eddiemonge profile image
Eddie Monge Jr

I don't understand the point of this article. This is done automatically already. The build system outputs the built files using a hash. The index.html uses those to load it. When you push new code up, the index.html is replaced with updated references. Set the index.html to never be cached and add the new build files alongside the old ones and people going to the site initially get the new version and people already on the page will continue to work and then when they refresh they get the new version as well.

Collapse
 
arturogallegos profile image
Arturo Gallegos

When your project requires to implement strict security policies in some cases it is necessary to disable hashes, for this reason we need another option to clear all caches.

however I agree that this method could be shorter, in my opinion only need attach in the serviceWorker.js a console.log with current date

Collapse
 
ammartinwala52 profile image
Ammar Tinwala

Hi Arturo

I'm extremely sorry for such a late reply. Can you help me out how I can make this code shorter ?

Thread Thread
 
arturogallegos profile image
Arturo Gallegos

Of course, as I commented only need attach a console log in your service worker, for example:

Create a file updateBuildTime.js

const fs = require('fs');

const sw_file = './src/forceUpdateCache.js';

const buildDate = `console.log(${new Date().getTime()})`

fs.writeFile(sw_file, buildDate, "utf8", (error) => {
  if(error){
    return console.warn(error);
  }
  return console.warn('All right')
});
Enter fullscreen mode Exit fullscreen mode

then update your package file with

"scripts": {
   ....
    "build": "node ./updateBuildTime.js && node scripts/build.js && cat src/forceUpdateCache.js >> build/service-worker.js",
    ....
  },
Enter fullscreen mode Exit fullscreen mode

With these lines you can add a console.log, I have used the current date to ensure a new value is different to the last version, but you can use another value

The reason of this is very simple, own service worker update all cache our app but need at least one change to download the new version of this file, the console.log with a date is perfect, it will be diferent in each build.

Thread Thread
 
ammartinwala52 profile image
Ammar Tinwala

Thanks Arturo, I will check this out.

Collapse
 
ammartinwala52 profile image
Ammar Tinwala

Hi Eddie

I'm very sorry for such a late reply. The point of this article was, whenever I was generating the build, I was getting the same hash files through create-react-app. It seems to have changed now. I'm getting different has files generation during the build.
Can you share me some example on how to "Set the index.html to never be cached" only on new builds ? It will help us all

Collapse
 
jukim1234 profile image
jwkim

Awesome! Finally, I've found this solution. I wonder how every SaaS company could manage to work without this solution. I'm very curious why there has been no simple solution for this important problem in the React eco-system.

Collapse
 
pragziet profile image
Pragz

Hi Ammar great article, I need you help here, I tried Dinesh approach, but my app is indefinitely reloading, pls guide here, happy to share more info.

Collapse
 
ammartinwala52 profile image
Ammar Tinwala

Hi Pragz, can you create a small poc with issue share it me via github. I will have a look at it and get back to you.

Collapse
 
pragziet profile image
Pragz • Edited

One thing to check is that my app is not build using create-react-app, so do not have any Service worker at all and hence not being registered, still the below code will be able to work, also my app uses react-router.

    if (caches) {          
      caches.keys().then(async function(names) {
        await Promise.all(names.map(name => caches.delete(name)))
        })
    }

    // delete browser cache and hard reload
    window.location.reload()
Enter fullscreen mode Exit fullscreen mode

Also is it possible to share your email / twitter , for further 1-1 discussion, thanks for the support.

Thread Thread
 
ammartinwala52 profile image
Ammar Tinwala

@Pragz : Please email me at ammartinwala52@gmail.com. We can connect over there

Thread Thread
 
ammartinwala52 profile image
Ammar Tinwala

I ran the above code and it is working perfectly fine at my end.

Thread Thread
 
pragziet profile image
Pragz

Sent the referral mail!

Collapse
 
amanchaudhary1998 profile image
AmanChaudhary1998

I am also facing the same issue as it goes under the infinite loop please guide me how fix this issue
window.location.reload()

Collapse
 
ammartinwala52 profile image
Ammar Tinwala

Hi Aman

I'm extremely sorry for such a late reply. If you are still facing the issue, please email to me your code at ammartinwala52@gmail.com

Collapse
 
vishalvishalgupta profile image
vishalvishalgupta

Can you please check once again your implementation? The react web app gets reload only because of withClearCache's useEffect. The code is never going in refreshCacheAndReload. And also, if intentionally, I wanna get the code fall in refreshCacheAndReload function, It lets the UI into never ending rerendering. Please check your implementation and revert me back.
FYI, your buildDate in package.json and meta.json are always going to be same and if they are going to be same, then if (momLatestDateTime.isAfter(momCurrentDateTime)) {
is always going to give you false.

Collapse
 
ammartinwala52 profile image
Ammar Tinwala

Hi Vishal

I'm very sorry for such a late reply. Can you check the answer to Khorengrig that I just mentioned. It explained what exactly happens.

If you are still not clear then please email me ammartinwala52@gmail.com

Collapse
 
alexisyepes profile image
Alexis Yepes Sanabria

Thanks again for this article. I implemented this approach, but the first time my page loads, I can still see the old version. After closing the tab and reopening it I can see the new version. Is this what is suppose to be happening?

Collapse
 
alexisyepes profile image
Alexis Yepes Sanabria

Never mind! That happened the first time after the first change. Then every time a change something I can see the most recent version live!!! Thanks a million sir

Collapse
 
ammartinwala52 profile image
Ammar Tinwala

Hi Alexis

I'm very sorry for such a late reply. I'm glad that I could be of help. Keep writing. Cheers.

Collapse
 
davidcastillo4 profile image
DavidCastillo4

Hi Ammar, Thank you so much for the post. I'm trying to get it to work for me but I'm getting the errors in the pic I provided. Can you please help me if you don't mind. Thanks again for the post!!!

Collapse
 
khorengrig profile image
khorengrig

Who can explain me what is going in this file. update-build.js. Every time you run build command your package.json buildDate and meta.json buildDate will be the same(You are inserting the same value). Which way this code will work?

Collapse
 
ammartinwala52 profile image
Ammar Tinwala

Hi Khorengrig

Im very sorry for this very very late reply.
Both dates are generated at the same time, and when you generate your build and deploy, at that point of time the api call from meta.json will fetch the latest build date and it will be compared with package.json date. If package.json date is cached it will be a mismatch and then only the refresh will happen. If your server/deployment configuration is such that it always takes the latest build then your package.json buildDate and meta.json buildDate will always be the same.
I hope you got the point.

Collapse
 
briang123 profile image
Brian Gaines

Looks like there is a react-cache-buster npm package that is basically the same as this. It can be found here: npmjs.com/package/react-cache-buster.

Collapse
 
ammartinwala52 profile image
Ammar Tinwala

Hi Brian

I'm very very sorry for such a late reply. Thanks for sharing I will check that out.

Collapse
 
jasimur profile image
Jasim Uddin

It's calling API without login. How I can solve this problem

Collapse
 
ammartinwala52 profile image
Ammar Tinwala

Hi Jasim

I'm very very sorry for late reply. You would have to place some kind of authentication check first if you want to call the api. If auth is successful then load the ClearCache component which in turns call the API.

Collapse
 
alexisyepes profile image
Alexis Yepes Sanabria

How would you integrate the ClearCache component if App.js is a class Based component? Thanks in advance

Collapse
 
ammartinwala52 profile image
Ammar Tinwala

Hi Alexis, you can refer to the App.js code and CacheBuster code in this article by Dinesh
dev.to/flexdinesh/cache-busting-a-...

Here the implementation is class-based component. Let me know if you have any queries.