How to force client reload after deployment?
Asked Answered
C

6

23

I'm using the MEAN stack (mongo, express, angular and node). I'm deploying relatively frequently to production...every couple of days. My concern is that I'm changing the client side code and the API at times and I would rather not have to ensure backwards compatibility of the API with previous versions of the client code.

In such a scenario, what is the most effective way of ensuring that all clients reload when I push to production? I have seen that Evernote for example has a pop-up that says something along the lines of please reload your browser for the latest version of Evernote. I would like to do something similiar...do I need to go down the path of socket.io or sock.js or am I missing something simple and there is a simpler way to achieve this?

Char answered 27/3, 2015 at 1:56 Comment(0)
B
10

Update: AppCache was deprecated summer 2015 so the below is no longer the best solution. The new recommendation is to use Service Workers instead. However, Service Workers are currently still experimental with sketchy (read: probably no) support in IE and Safari.

Alternatively, many build tools now seamlessly incorporate cache-busting and file "versioning" techniques to address OPs question. WebPack is arguably the current leader in this space.


This might be a good use case for using HTML5's AppCache

You'd probably want to automate some of these steps into your deployment scripts, but here is some code you might find useful to get you started.

First, create your appcache manifest file. This will also allow you to cache resources in the client's browser until you explicitly modify the appcache manifest file's date.

/app.appcache:

CACHE MANIFEST

#v20150327.114142

CACHE:
/appcache.js
/an/image.jpg
/a/javascript/file.js
http://some.resource.com/a/css/file.css

NETWORK:
*
/

In app.appcache, the comment on line #v20150327.114142 is how we indicate to the browser that the manifest has changed and resources should be reloaded. It can be anything, really, as long as the file will look different to the browser from the previous version. During deployment of new code in your application, this line should be modified. Could also use a build ID instead.

Second, on any pages you want to use the appcache, modify the header tag as such:

<html manifest="/app.appcache"></html>

Finally, you'll need to add some Javascript to check the appcache for any changes, and if there are, do something about it. Here's an Angular module. For this answer, here's a vanilla example:

appcache.js:

window.applicationCache.addEventListener('updateready', function(e) {
    if (window.applicationCache.status == window.applicationCache.UPDATEREADY) {
        // Browser downloaded a new app cache.
        // Swap it in and reload the page to get the latest hotness.
        window.applicationCache.swapCache();
        if (confirm('A new version of the application is available. Would you like to load it?')) {
            window.location.reload();
        }
    }
    else {
        // Manifest didn't changed. Don't do anything.
    }
}, false);

Alternatively, if AppCache won't work for your situation, a more ghetto solution would be to create a simple API endpoint that returns the current build ID or last deployment date-time. Your Angular application occasionally hits this endpoint and compares the result to it's internal version, and if different, reloads itself.

Or, you may consider a live-reload script (example), but, while very helpful in development, I'm not sure how good of an idea it is to use live/in-place-reloading of assets in production.

Butta answered 27/3, 2015 at 2:56 Comment(5)
Wow! AppCache. I think it may be a bit much for what I'm after...especially as I see IE9 doesn't look to be supported and Firefox seems to require user consent. I really like the idea of the 'ghetto' solution though - checking an API endpoint at a set interval and calling window.location.reload( ) if necessary. Actually seems nice and simple to me...though I'm sure someone will tell me that I can do the same thing just as easily with socket.io....Char
yea; you could do it with socket.io.. but that's a lot of overhead just for "realtime" code updates. Pinging the server on a timeout seems sufficient (and cheaper resources-wise; you don't have to worry about loads of open socket connections or worry about load balancing them as you scale up..) Unless, of course, your app already uses socket.io. Then it makes more sense.Conventioner
Yep - I'm thinking the same thing...too much overhead. We will likely be adding socket.io in the next few months but I think will go with the API ping approach until we actually do. Thanks for your answer!Char
That probably shouldn't be the accepted answer. At the top of the link to app cache: Deprecated This feature has been removed from the Web standards. Though some browsers may still support it, it is in the process of being dropped. Do not use it in old or new projects. Pages or Web apps using it may break at any time. .... Use Service Workers instead.Terracotta
@pmont: I'd have to agree. I'll update my answer accordingly with an addendum.Conventioner
L
2

I will tell you my problem first then I will recommend a tentative solution. I wanted to force my user to log out and then log in when a production build is been deployed. At any point in time, there will be two versions of software deployed on production. A version which software which FE knows and a version which Backend knows. Most of the time they would be the same. At any point in time if they go out of sync then we need to reload the client to let the client know that a new production build has been pushed.

I am assuming 99.99% of the time the backend would have the knowledge of the latest version of the deployed software on production.

following are the two approaches which I would love to recommend:-

  1. The backend API should always return the latest version of the software in the response header. On the frontend, we should have a common piece of code that would check if the versions returned by the API and that present on the FE are the same. if not then reload.

  2. Whenever a user logs in. the BE should encode the latest software version in the JWT. And the FE should keep sending this as a bearer token along with every API request. The BE should also write a common interceptor for every API request. which would compare the software version in the JWT received from the API request and the

Lotz answered 9/7, 2021 at 13:56 Comment(0)
F
1

Maybe you can add hash to your client code file name. eg app-abcd23.js.
So the browser will reload the file instead of get it from cache. or you can just add the hash to url.eg app.js?hash=abcd23 but some browser may still use the cached version.

i know rails has assets-pipline to handle it, but i am not familiar with MEAN stack. there should be some package in npm for that purpose.

And i dont think it is really necessary to use socket.io if you want to notify the user their client code is out of date. you can define your version in both html meta tag and js file,if mismatch, show a popup and tell the user to refresh.

Fortis answered 27/3, 2015 at 2:21 Comment(2)
Thanks! It's an SPA though...so I'm not sure that the html meta tag and js file version approach will work. Conceivably the user could leave the browser window open for a week, come back and start using the app without reloading and the meta tag and js file version would be unchanged. The file naming point is a good one, but same issue - fixes cache on reload but not if user doesn't reload. Right?Char
yes, add hash to file name is for browser cache and cdn. so i think make ajax call every hour or day to see if there if there is a new version and then refresh the page using js may work for you. socket.io is still too much for that. And if you want to add realtime things to your app, i suggest using firebase or parse instead of using socket.io.Fortis
S
0
  1. Try to limit your js/files to expire within smaller periodic time, ie: 1 days.
  2. But in case you want something that pop-out and tell your user to reload (ctrl+f5) their browser, then simply make a script that popup that news if you just changed some of your files, mark the ip/session who have just reload/told to reload, so they will not be annoyed with multiple popup.
Showpiece answered 27/3, 2015 at 2:7 Comment(1)
Point 1 is a fair one. I have changed the time to expiry on the CDN for static assets to 1 day from 7 days.Char
D
0

I was facing the same problem recently. I fixed this by appending my app's build number with my js/css files. All my script and style tags were included by a script in a common include files so it was trivial to add a 'build number' at the end of the js/css file path like this

/foo/bar/main.js?123

This 123 is a number that I keep track of in my same header file. I increment it whenever I want the client to force download all the js files of the app. This gives me control over when new versions are downloaded but still allows the browser to leverage cache for every request after the first one. That is until I push another update by increment the build number.

This also means I can have a cache expiry header of however long I want.

Destrier answered 15/7, 2016 at 18:16 Comment(0)
P
0

Set a unique key to local storage during the build process I am using react static and loading up my own data file, in there i set the ID each time my content changes

Then the frontend client reads the key with from local storage (if the key does not exist it must be the first visit of the browser) if the key from local storage does not match it means the content has changed fire line below to force reload

window.replace(window.location.href + '?' + key)

in my case i had to run this same line again a second latter like

setTimeout( (window.replace(window.location.href + '?' + key))=> {} , 1000)

full code below:

const reloadIfFilesChanged = (cnt: number = 0, manifest: IManifest) => {
    try {
        // will fail if window does not exist
        if (cnt > 10) {
            return;
        }
        const id = localStorage.getItem('id');
        if (!id) {
            localStorage.setItem('id', manifest.id);
        } else {
            if (id !== manifest.id) {
                // manifest has changed fire reload
                // and set new id
                localStorage.setItem('id', manifest.id);
                location.replace(window.location.href + '?' + manifest.id);
                setTimeout(() => {
                    location.replace(window.location.href + '?' + manifest.id + '1');
                }, 1000);
            }
        }
    } catch (e) {
        // tslint:disable-next-line:no-parameter-reassignment
        cnt++;
        setTimeout(() => reloadIfFilesChanged(cnt, manifest), 1000);
    }
};
Pseudo answered 14/9, 2020 at 14:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.