How to force update Single Page Application (SPA) pages?

52,769

Solution 1

You can have a React component make an ajax request to your server, when the application loads, to fetch "interface version". In the server API, you can maintain an incremental value for the client version. The React component can store this value on the client (cookie/local storage/etc). When it detects a change, it can invoke window.location.reload(true); which should force the browser to discard client cache and reload the SPA. Or better still, inform the end-user that a new version will be loaded and ask them if they wish to save the work and then reload etc. Depends on what you wanna do.

Solution 2

Similar to Steve Taylor's answer but instead of versioning API endpoints I would version the client app, in the following way.

With each HTTP request send a custom header, such as:

X-Client-Version: 1.0.0

The server would then be able to intercept such header and respond accordingly.

If the server is aware that the client's version is stale, for example if the current version is 1.1.0, respond with an HTTP status code that will be appropriately handled by the client, such as:

418 - I'm a Teapot

The client can then be programmed to react to such a response by refreshing the app with:

window.location.reload(true)

The underlying premise is that the server is aware of the latest client version.

EDIT:

A similar answer is given here.

Solution 3

You can send app’s version with every response from any endpoint of your API. So that when the app makes any API request you can easily check there’s a new version and you need a hard reload. If the version in the API response is newer than the one stored in localStorage, set window.updateRequired = true. And you can have the following react component that wraps react-router's Link:

import React from 'react';
import { Link, browserHistory } from 'react-router';

const CustomLink = ({ to, onClick, ...otherProps }) => (
  <Link
    to={to}
    onClick={e => {
      e.preventDefault();
      if (window.updateRequired) return (window.location = to);
      return browserHistory.push(to);
    }}
    {...otherProps}
  />
);

export default CustomLink;

And use it instead of react-router's Link throughout the app. So whenever there's an update and the user navigates to another page, there will be a hard reload and the user will get the latest version of the app.

Also you can show a popup saying: "There's an update, click [here] to enable it." if you have only one page or your users navigate very rarely. Or just reload the app without asking. It depends on you app and users.

Solution 4

What techniques should be employed to make sure the old components versions are not used anymore by any clients?

today (2018), many front apps use service workers. With it, it's possible to manage your app lifecycle by several means.

Here is a first example, by using a ui notification, asking your visitors to refresh webpage in order to get latest application version.

import * as SnackBar from 'node-snackbar';

// ....

// Service Worker
// https://github.com/GoogleChrome/sw-precache/blob/master/demo/app/js/service-worker-registration.js

const offlineMsg = 'Vous êtes passé(e) en mode déconnecté.';
const onlineMsg = 'Vous êtes de nouveau connecté(e).';
const redundantMsg = 'SW : The installing service worker became redundant.';
const errorMsg = 'SW : Error during service worker registration : ';
const refreshMsg = 'Du nouveau contenu est disponible sur le site, vous pouvez y accéder en rafraichissant cette page.';
const availableMsg = 'SW : Content is now available offline.';
const close = 'Fermer';
const refresh = 'Rafraîchir';

if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    function updateOnlineStatus() {
      SnackBar.show({
        text: navigator.onLine ? onlineMsg : offlineMsg,
        backgroundColor: '#000000',
        actionText: close,
      });
    }
    window.addEventListener('online', updateOnlineStatus);
    window.addEventListener('offline', updateOnlineStatus);
    navigator.serviceWorker.register('sw.js').then((reg) => {
      reg.onupdatefound = () => {
        const installingWorker = reg.installing;
        installingWorker.onstatechange = () => {
          switch (installingWorker.state) {
            case 'installed':
              if (navigator.serviceWorker.controller) {
                SnackBar.show({
                  text: refreshMsg,
                  backgroundColor: '#000000',
                  actionText: refresh,
                  onActionClick: () => { location.reload(); },
                });
              } else {
                console.info(availableMsg);
              }
              break;
            case 'redundant':
              console.info(redundantMsg);
              break;
            default:
              break;
          }
        };
      };
    }).catch((e) => {
      console.error(errorMsg, e);
    });
  });
}

// ....

There's also an elegant way to check for upgrades in background and then silently upgrade app when user clicks an internal link. This method is presented on zach.codes and discussed on this thread as well.

Solution 5

I know this is an old thread, and service workers are probably the best answer. But I have a simple approach that appears to work:

I added a meta tag to my "index.html" file :

<meta name="version" content="0.0.3"/>

I then have a very simple php scrip in the same folder as the index.html that responds to a simple REST request. The PHP script parses the server copy of the index.html file, extracts the version number and returns it. In my SPA code, every time a new page is rendered I make an ajax call to the PHP script, extract the version from the local meta tag and compare the two. If different I trigger an alert to the user.

PHP script:

<?php
include_once('simplehtmldom_1_9/simple_html_dom.php');
header("Content-Type:application/json");
/*
    blantly stolen from: https://shareurcodes.com/blog/creating%20a%20simple%20rest%20api%20in%20php
*/

if(!empty($_GET['name']))
{
    $name=$_GET['name'];
    $price = get_meta($name);

    if(empty($price))
    {
        response(200,"META Not Found",NULL);
    }
    else
    {
        response(200,"META Found",$price);
    }   
}
else
{
    response(400,"Invalid Request",NULL);
}

function response($status,$status_message,$data)
{
    header("HTTP/1.1 ".$status);

    $response['status']=$status;
    $response['status_message']=$status_message;
    $response['content']=$data;

    $json_response = json_encode($response);
    echo $json_response;
}

function get_meta($name)
{
    $html = file_get_html('index.html');
    foreach($html->find('meta') as $e){
        if ( $e->name == $name){
            return $e->content ;
        }
    }
}
Share:
52,769

Related videos on Youtube

Pahlevi Fikri Auliya
Author by

Pahlevi Fikri Auliya

Think!

Updated on July 08, 2022

Comments

  • Pahlevi Fikri Auliya
    Pahlevi Fikri Auliya almost 2 years

    In fully server side based rendering (non Web 2.0), deploying server side code would directly update client side pages upon page reload. In contrast, in React based Single Page Application, even after React components were updated, there would be still some clients using old version of the components (they only get the new version upon browser reload, which should rarely happen) -> If the pages are fully SPA, it's possible that some clients only refresh the pages after a few hours.

    What techniques should be employed to make sure the old components versions are not used anymore by any clients?

    Update: the API doesn't changed, only React Component is updated with newer version.

    • code-jaff
      code-jaff over 8 years
      Good question. I can think of couple of ways. Either using SSE(server sent events) or websockets to notify the client which has new update, hence they could update it when ready(to make sure they're not in the middle of something where automatic update may possibly make some disappointments).
    • dandavis
      dandavis over 8 years
      leaving a socket open just for code updates is probably overkill, and not needed anyway, since a pure-client can run forever. It's server communication that would be at-risk. send a version stamp with each server request, and if your server gets something old, respond with an error message that will cause the page to reload (maybe after asking the user first). if you can support old and new at once, until old dries up, that's ideal...
  • HelloWorld
    HelloWorld over 5 years
    @dotslash in short: you need to send the api version with the request. The server can compare and sends back there has been an update. If you're using redux, it will be easy to save the state (one reason why it's important that redux values are serializable) in web storage (developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API). reload page. check if anything is stored for redux. hydrate redux if so. and load your components with the necessary data (if you aren't using redux/react-redux you'll have to do this manually for a component's state)
  • Grzegorz Brzęczyszczykiewicz
    Grzegorz Brzęczyszczykiewicz over 5 years
    I have tried this solution and it seems bit outdated with browser history. What do you think about it? stackoverflow.com/questions/52293750/…
  • Nikola Mihajlović
    Nikola Mihajlović almost 5 years
    Good answer. I think it's better just to send the latest version from the server back to the client as a header, instead of using (abusing) HTTP status. Then client can decide whatever they want to do.
  • David Dahan
    David Dahan almost 5 years
    I don't understand why the update of the client app should be tied to a server request. Let's say clients are running v.42 of the SPA, and a v.43 just became available. This means clients will be forced to keep using v.42 until they make an HTTP request. But you can use a SPA for a long time without making such request... This SPA update should happen just after the v.43 availability IMHO.
  • hazardous
    hazardous almost 5 years
    @DavidD. You'd need the client to initiate some communication with the server to know about the new version. It either comes to polling or push. Checkout Andy's response in this thread (stackoverflow.com/a/47608249/2790937) for tying a version check with every request. IMO a push approach just for keeping client version in sync would be an overkill.
  • Kangur
    Kangur over 4 years
    I prefer to serve manifest.json that is already packed with React application and use its description field to store version.
  • Hamid Mayeli
    Hamid Mayeli over 3 years
    Nowadays, reload with a boolean parameter is deprecated. Is a newer way to do so?
  • Qiulang
    Qiulang about 3 years
    5 yeas later I still can't find a satisfied answer. I asked my question here with some new information softwareengineering.stackexchange.com/questions/423068/…
  • saurabh_garg
    saurabh_garg over 2 years
    Isn't reloading the page a Bad user experience ? Imagine, you are browsing a site or filling up a form on the site and it reloads in the middle of it.
  • hazardous
    hazardous over 2 years
    That's correct, you should add more checks corresponding to your specific use cases.
  • Lonyui
    Lonyui over 2 years
    !!!!warn!!!! window.location.reload(true) is depreciated. !!!!don't try this solution!!!