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

JavascriptReactjsSingle Page-Application

Javascript Problem Overview


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.

Javascript Solutions


Solution 1 - Javascript

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 - Javascript

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 - Javascript

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 - Javascript

> 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 - Javascript

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

Solution 6 - Javascript

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionPahlevi Fikri AuliyaView Question on Stackoverflow
Solution 1 - JavascripthazardousView Answer on Stackoverflow
Solution 2 - JavascriptAndyView Answer on Stackoverflow
Solution 3 - JavascriptYan TakushevichView Answer on Stackoverflow
Solution 4 - JavascriptLIITView Answer on Stackoverflow
Solution 5 - JavascriptmagicView Answer on Stackoverflow
Solution 6 - JavascriptKishore BarikView Answer on Stackoverflow