73
votes

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.

6
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).code-jaff
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...dandavis

6 Answers

68
votes

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.

19
votes

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.

7
votes

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.

5
votes

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 ach.codes and discussed on this thread as well

1
votes

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 ;
        }
    }
}
0
votes

Yes in server side rendering if you need to update a small part of the page also you need to reload whole page. But in SPAs you update your stuffs using ajax, hence no need to reload the page. Seeing your problem I have some assumptions:

You see one of your component got updated but other components getting data from same API didn't update. Here comes Flux Architecture. where you have your data in store and your component listen to store's changes, whenever data in your store changes all your components listening to it's change will be updated (no scene of caching).

Or

You need to control your component to be updated automatically. For that

  1. You can request your server for data in specific intervals
  2. Websockets can help you updating component data from server.