2

I have a single page application and need the clients to be on the latest version. Caching is aggressive so waiting for them to invalidate isn't feasible.

Is there a way to tell the client to look at a resource (say something like build.txt) and if that resource contains a build number greater than the current one loaded, invalidate the cache and refresh?

David Alsh
  • 6,747
  • 6
  • 34
  • 60
  • I had a middleware that attached the current git commit ID as a header with all API responses. If it changed, I did a full reload. Perhaps something along those lines? – ceejayoz May 30 '19 at 00:42
  • Just using `location.reload()`? – David Alsh May 30 '19 at 00:49
  • In my case, yes, as we were using versioning on CSS/JS/image assets. – ceejayoz May 30 '19 at 00:51
  • @DavidAlsh `location.reload()` will reload the main page, but not invalidate the cache of all resources that it loads, like CSS and JS files. – Barmar May 30 '19 at 00:53
  • I don't think there's any good way to force clients to clear their caches automatically. You should reduce the cache lifetime in advance of the update. – Barmar May 30 '19 at 00:54
  • To do that in cloudfront, I just need to set a custom cache length - right? It will overwrite the cache headers coming from the source? – David Alsh May 30 '19 at 01:33

2 Answers2

5

As this question has been tagged with progressive-web-apps I'm going to assume that it's installing a service-worker, which is what is aggressively caching the resources.

This post runs though showing a "new version available" popup for PWAs - even if it's not the particular behaviour you want, it explains a lot about how service-workers get updated.

This question/answer also goes over how often the service-worker is checked for updates.

This question/answer goes over pros/cons of always using skipWaiting to keep the client immediately up to date.

Edit: If you're just dealing with regular HTTP Cache, try using location.reload(true) (reload with the forcedReload flag set) when you detect that there's a newer version on the server. In the past I've done this by putting the release number into the js code at build/release time, and having the server add its release number to every response as a header. A simple compare of the values after an ajax call can confirm whether the ui and server release numbers match and take action when they don't.

Jono Job
  • 2,732
  • 1
  • 23
  • 23
  • It does have a serviceworker, I forgot to mention in the above. The serviceworker only has an empty fetch handler at this stage - would that still modify caching behaviour? – David Alsh May 30 '19 at 08:33
  • 1
    Hmm, I wouldn't think so - you'd usually need some code in there to enable [precaching](https://bendyworks.com/blog/precache-for-performance), which is what I thought you'd be dealing with. If it's just regular http cache you're dealing with,you might get by with [`Location.reload(true)`](https://developer.mozilla.org/en-US/docs/Web/API/Location/reload#Parameters) - note the `forceReload` parameter set. I'll update my answer a little. – Jono Job May 30 '19 at 23:02
  • 1
    I wrote the following Angular service. Essentially it polls a `build.txt` file that lives in the `/assets` folder and checks to see if it's different to the build in its own environment. https://gist.github.com/alshdavid/032ea535f222646dc74420e20b28faa1 – David Alsh Jun 15 '19 at 01:31
2

Credit to Jono Job for helping me achieve this solution.

What I ended up doing was the following:

In my build pipeline I added the following two lines before the client build

- sed -i -e "s/{{build-number}}/${CI_COMMIT_SHORT_SHA}/g" ./src/environments/environment.prod.ts
- echo $CI_COMMIT_SHORT_SHA > ./src/assets/build.txt

The first line pushes the commit hash into the compiled javascript bundle, the second creates a text file with the commit hash as the content.

I then set up an Angular service which uses the http client to poll the text file which contains the commit hash. It checks to see if the hash in the text file is different to the hash loaded from the javascript bundle.

The theory here is that the Javascript bundle can be cached, but the http request to fetch the build.txt file will not be. Allowing me to inspect a difference between the build hashes stored in the two.

If the service detects a difference in builds, it would project a prompt to the user notifying them of an update which, upon clicking, would refresh the page.

Refreshing is done using window.location.reload(true). TypeScript tells me the parameter is deprecated, though it still works.

Here's a gist of the aforementioned Angular service: https://gist.github.com/alshdavid/032ea535f222646dc74420e20b28faa1

In my APM, I can see that within a couple of hours of pushing an update, everyone client has been updated to the latest version.

So it probably works.

David Alsh
  • 6,747
  • 6
  • 34
  • 60