3

I need to design a way to notify my user the SPA has updated if they don't refresh their browsers, i.e. if no requests to get index.html is made after the initial load, how do I notify users the javascript or css files have updated on the server? Note, it is not the REST api that my SPA communicate with updates but the SPA's static resources.

I think the options are limited:

  1. Start a time to keep querying the latest version number
  2. Add a customer header field about the latest version in responses for most if not all XHR requests SPA send
  3. using websocket to notify SPA
  4. I am not familiar with service worker but I read articles about it may work too.

There is another problem I need to deal with, my SPA is deployed on its own server, which is separated from the REST server it communicates with(although I suspect it is common) and there are cases that the SPA has new versions while REST server doesn't. The version number I design should be able to tell these 2 cases.

I notice folks asked this kind of questions on stackoverflow many times. I can find more 10 questions, with the earliest one I found in 2013 and the latest one in 2019. I list some here.

  1. How to handle expired files without refreshing the browser when using Single Page Application (SPA)?
  2. How to force update Single Page Application (SPA) pages?
  3. How can I force SPA clients to hard refresh if there is a new build?
  4. Refreshing a cached Angular SPA
  5. Proper way to refresh Single Page Application

The reasons I asked here are:

  1. I like to design a build process to update the SPA version automatically. Manually update the version number is error prone. None of answers seem to address this.
  2. All those Q&A on SO seemed to failed to mention the case that the SPA server and REST server is separated and what I need here is an update for SPA files. For example if I use the option 2, adding a customer header field in response I need to differentiate the SPA version and REST api version. None of the answers I saw address this. Some answers just focus on REST api version update.
  3. I believe this question should be address by a build process, a communication process and maybe a deploy model. So this is a question about SPA architecture.
  4. The mere facts that this question has been asked so many times for 8 years probably has said it clearly that there is no easy answer for it, probably even without some best practices. Some answers failed to realize is the core issue here is to retrieve index.html so focus on cache busting, file naming or setting Cache-Control, like this one How to force the browser to reload cached CSS and JavaScript files or the question answered here Dealing with browser cache in single-page apps. That was not my problem because my SPA is hosted on nginx server with etag on. As long as users hit F5 my problem is solved.

3 Answers3

4

Basically you want to trigger an event when the version changes to inform the user that their UI is outdated and they should refresh. If you want to keep it simple this can be done file-based. You basically need these components:

  • A JavaScript file (currentversion.js) that dispatches a event about the current version
  • Logic in the SPA that periodically reloads currentversion.js file and defines listeners for the "currentversion" event.

The generation of that currentversion.js and currentVersion can be automatically generated to contain the correct version on every build.

The idea is to (re)load the currentversion.js as needed to dispatch the current version of the UI. The logic can listen for that event and act when the version has deviated from last known version.

In theory you can have something like:

currentversion.js

window.dispatchEvent(new CustomEvent('currentversion', { detail: '1.1' }));

index.html

<html>
    <body>
        <script>
            const currentVersion = '1.1';
        function startVersionCheck() {
            const versionJs = document.getElementById('version-js');
            const versionJsUrl = versionJs.src;
            let versionJsReloadCount = 0;
            const versionCheck = function () {
                versionJs.src = [versionJsUrl, '?rc=', ++versionJsReloadCount].join('');
            };
            setInterval(versionCheck, 60000);
        }

        window.addEventListener('currentversion', function (e) {
            const version = e.detail;
            if (currentVersion !== version) {
                window.dispatchEvent(new CustomEvent('versionchanged', { detail: version }));
            }
        });

        window.addEventListener('versionchanged', function (e) {
            alert('New UI version available!');
        });

        window.addEventListener('load', startVersionCheck);
    &lt;/script&gt;
    &lt;script id=&quot;version-js&quot; src=&quot;./currentversion.js&quot;&gt;&lt;/script&gt;
&lt;/body&gt;

</html>

Both the UI and currentversion.js expose the same version initially. When the value changes in currentversion.js it should eventually trigger the versionchanged event on the next reload.

Once the user reloads both version are in sync again.

Bart
  • 214
1

From my post in 2014 we ended up sticking with the proxy service up until the end. I left that project a long time ago but our approach was clean and centralized but, like I said at the time, it only works for your own services, which in my opinion is ok; if an external provider changes their contract you'll probably need to change your implementation too.

So basically:

  • Include the app version on every service call response header of your services; This can be easily done in one place intercepting your backend response pipeline.
  • Intercept all http calls centrally on the SPA and compare the versions
  • If different, warn the user or auto refresh the whole site, depending on your needs

Keep it simple and reliable, going fancy on this will most likely cause you more problems than not.


As a side note,
since then, I had fun with SPAs and many fancy client-side frameworks but nothing is as stable as the good old server-side MVC applications; especially for a big enterprise environment with hundreds of devs and hundreds of applications.

AlexCode
  • 141
1

I came across this topic because we have a similar issue on our application.

We have already a WebSocket flow so we used it to send an event to the application when an update is available. The user is warned regarding the available update and is asked to save the work and reload the page; we do not force the reload if the user do not do any action on the notification after a certain time (imho, bad decision by design team).

Ok, the starting use case was solved, but then the QA department pointed another flow that causes issues:

Our customers usually open our webapp (an Angular SPA) and leave it open for long time, also when they leave their station. As you may imagine, the user can miss the notification during this period of absence. When they execute the login again, we have issues, because the app is not updated and does not work anymore.

So we though a possible solution: after every login, check a static resource on the server, which will be deployed with every pipeline and will contain info about the latest version available. The app will then compare its version with the one specified in the static resource and decide what to do.

Glorfindel
  • 3,167