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 Answers 3

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);
        </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.

7
  • 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. Commented Mar 7, 2021 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
    Commented Mar 9, 2021 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. Commented Mar 10, 2021 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
    Commented Mar 10, 2021 at 17:42
  • 1
    This is a cool solution! Having tried the usual service workers route, I can tell you that fails miserably with Safari.
    – ffxsam
    Commented Feb 17, 2023 at 17:57
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. Commented Apr 4, 2021 at 0:49
  • 1
    BTW, I used websocket because my app already used it. Commented Apr 4, 2021 at 0:51
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.

3
  • We also used websocket but as I said in my question, an equally important question is "design a build process to update the SPA version automatically." because manually setting the updated version is error-prone. How did you solve this ? Commented Oct 7, 2022 at 8:05
  • The version inside the static resource is automatically updated by the build process of the CI, so no manual operation. As described in my reply, due to the possible miss of the push notification, we are sure that the version will be checked at the next login against the updated static resource. Commented Oct 10, 2022 at 6:54
  • Yes the version number needs to be automatically updated. But how do you design a version number schema that will update the number to show it is a newer version ? For example if the version number is based on unix time then it is always the latest version but how do you map unix time to the real version like the one we use for the Semantic Versioning (major.minor.patch) ? Commented Oct 10, 2022 at 7:02

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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