1

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.
2

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);
        </script>
        <script id="version-js" src="./currentversion.js"></script>
    </body>
</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.

6
  • Actually I use websocket because my SPA already uses websocket. But I think this is a general problem that I would like to see other opinions. What you said was on option #1. Mar 7 at 1:51
  • I must say that doing it file based makes more sense instead of websockets. The deployment will be very simple. Also backend and front end systems stay decoupled. It doesn't make any real sense that a backend system tells the UI it's not the correct version. It doesn't know about the UI as it should. Couple them and expect unwanted complexity when you scale to multiple frontend technologies/use-cases.
    – Bart
    Mar 9 at 17:36
  • Not necessary the case. I have a place to store the frontend version and backend version(separately). The backend does not know the frontend version. It just reads the version number to sends to frontend. The frontend version is updated by the build script automatically. And I don't like the idea of frontend keeps polling the version every n minutes. That 's why I use websocket. Mar 10 at 2:59
  • Reading a version number is knowing. But I'm more curious to why you dislike polling. What are your main concerns?
    – Bart
    Mar 10 at 17:42
  • For starters the performance! Check this, for example stackoverflow.com/questions/9397528/… but it is no brainer. Mar 11 at 4:38
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.

2
  • 1
    Thanks for answering my question. I actually read your question before I posted mine here. Some other post also mentioned adding a customer field in response as I said in my question. I chose not to go with this solution for couple of reasons, first I need to tell the difference between SPA update and Rest server update. I don't see a clean way to do it in response header. Second I need to automatic process to update my version number every time I have a new release. Your question didn't mention that but like I said that is important for me. Apr 4 at 0:49
  • 1
    BTW, I used websocket because my app already used it. Apr 4 at 0:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.