Right now I've embedded my Backbone application in an html page ( a classic index.html ).
When I edit something and I go back to see the page, to see current changes I need to hit F5.
What would be the best practice in a case where the user is using an old version due to the page being cached by the browser?
I was thinking about using a jsp or a php page but I honestly was not able to find so much on Google.
Can you help me?
There is a difference between reloading a page automagically when a file changes (watching a folder) and making sure that new users have the right version of your javascript application.
In the first case, I am guessing this is for development purposes and you will most likely want to use something like Live.js to achieve this.
In the second case, each time a returning user enters the application ( loads the index.html ), the server should issue a 304 - Not changed for the javascript file if the application has not changed and the file is cached in the browser or a 200 - OK with the file if it has changed and serve the new version. This has nothing to do with Backbone and the caching strategies are implemented on the server side.
If however you would like the application to periodically check for new versions while the user is using it and reload if there is a change ( I personnally think this is a bad idea, but it is possible ), here is a solution that would work.
You will need to have an entry point on the server side that only returns the application's version. This could be a semantic versioning, a hash, a name, etc... As long as it is different for each version.
main.js
var currentVersion;
// Assuming that the '/api/app-version' access point returns a string or number.
$.ajax({ url : '/api/app-version'})
.done(function(data) { currentVersion = data });
// Poll the server every minute to watch for a new version.
// If the version is different, reload the application.
setInterval( function() {
$.ajax({ url : '/api/app-version'})
.done(function(data) { if ( data !== currentVersion ) window.location.reload() });
}, 60000);
Related
I have an angularjs/ui-router application. All the html templates, which are lazyloaded by the framework, are cachebusted so that they are named myTemplate.someHash.html, and the references to those templates are also updated compile-time.
One problem that sometimes occurs is when I deploy a new version while a user is using the application. The sequence of events is as following:
The user has opened a page, which has a link on it to a state called Summary. The Summary state uses an HTML template called summary.html. But since we're cachebustering the HTML templates, this file is actually named summary.12345.html in version of the application currently loaded in the user´s browser.
A new release is deployed, which contained some changes to summary.html, causing it to get a new hash, so it's now called summary.98765.html.
The user clicks on the link, and angular tries to fetch summary.12345.html which nolonger exists, causing a 404.
Is there a good pattern for solving this?
My initial thought is to append some HTTP header in all requests, something like Expected-Version: 999 (where 999 is the build number generated in CI), and if that is not the version running on the server, then the server will respond with something like "410 Gone", in which case the application will ask the user to refresh the browser.
But it would require some work on the server side, and I'm not sure how to inject this logic into the template loading on the client side either.
Also, since new versions are typically deployed a few times per week (sometimes many per day), and most of those releases don't even contain any changes that would break the SPA in the above way, I don't want to force the users to reload the page all the time.
Could one approach be to only show the "please refresh" message when a request results in a 404 AND the response contains a header that indicates that you're running a stale version of the SPA?
Or maybe there is a simpler way?
I solved it in the following way.
I decided I don't want to ask users to refresh their browser every time a new release has been deployed, because many of those releases only contain backend changes.
I only want to ask them to refresh when a release has been deployed (since the time they opened the app in their browser) which contains changes to either:
the javascript application (which is served as two bundles), or
any of the html templates that angular lazyloads.
So I had to introduce the notion of a UI version. I decided that the definition of this UI version should be the SHA256 hash of all the filenames (just the names, since those already contain hashes for cachebusting) of the html templates mentioned above, concatenated with the names of the two js bundles (which are also cachebusted).
Here's the solution.
Part 1
I added a step in my CI pipeline, after the js app has been compiled and cachebusted, but before the whole application is built into a Docker image. This new build step looks like this:
const glob = require("glob");
const crypto = require('crypto');
const fs = require('fs');
const jsPromise = new Promise(resolve => {
glob("./js/*.js", {}, function (err, files) {
if (!err) {
resolve(files);
}
});
});
const htmlPromise = new Promise(resolve => {
glob("./app/**/*.html", {}, function (err, files) {
if (!err) {
resolve(files);
}
});
});
Promise.all([jsPromise, htmlPromise]).then(([files1, files2]) => {
const allFiles = files1.concat(files2);
fs.writeFileSync(
'./ui-version.env',
`UI_VERSION=${crypto.createHash('sha256').update(allFiles.sort().join()).digest('hex')}`
);
});
As you can see, it writes it to a file called ui-version.env. I then refer to this file in the docker-compose.yml using the env_file command. This way, the backend application is now aware of the current UI version. And the backend now serves this data at the HTTP endpoint GET /ui-version.
Part 2
When the frontend app is loaded, it calls the above mentioned endpoint and stores the current UI version in memory.
When a new release has been deployed, a websocket message is sent to all connected frontend apps with a notification to check whether or not they are still running an up-to-date UI version. They do this by requesting the new endpoint mentioned above, and comparing the result to the version it saved on load.
What about users who´s computers were in sleep mode or without internet connection when the websocket message was sent out?
I already have event listeners setup in the SPA that goes off when the computer comes back from sleep mode, or when it recovers internet connection. So any of those events will now also trigger the UI version check.
To be honest, I'm not super excited about this solution. There are a lot of moving parts. It would be great if someone could offer a simpler solution. But unfortunately I'm skeptical that I will get any good answers at all.
I have a create react app SPA, and I have deployed it with a registered service-worker.js (Cache-Control: max-age=0)
Everything works totally fine: When I update my code, and redeploy, navigate away from my site (or close the page), and return to my site, the service-worker.js file is downloaded again. It caches my index.html file which contains the url for my app.HASH.js file. It notifies me that new content is available, I refresh the browser page, and now I am running my latest app version.
What doesn't work is: When I navigate to different parts inside my SPA, I use react-router to change the URL. The "url-changes" don't trigger a reload of my service-worker.js file (it's cached with max-age=0 - and I have confirmed this with curl -I). Therefore a new worker is never downloaded to eventually inform me that new content is available and that I need to do a refresh.
I am not sure what the expected behaviour is supposed to be. If react-router changes the URL - should this not trigger a reload of service-woker.js when it's set to not cache itself?
In order to be able to get a new version of the SW.js file while the user is using your app, you have to manually call ServiceWorkerRegistration.update().
You could do for instance:
navigator.serviceWorker.getRegistrations()
.then(registrationsArray => {
registrationsArray[0].update();
})
This code would then be called whenever you like! This forces the browser to check for updates to the SW.js file and then handle the situation in whatever way you've configured your script to do.
This update() call should be made in any place you want to check for updates. You could call it after every URL change / new route visit or once a minute or whatever. You decide.
Checkout the MDN documentation. They also show reference code for storing a reference to the registered SW which gives you the possibility of directly calling update.
https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration/update
Goal:
The goal is for the end user to see the updated website without having to manually clear the cache.
Setup :
Client side : Angular 1.4
Server Side : Azure Web Apps
Deployment method : Azure links to a branch on a bitbucket account and detects updates pushed to this account from git.
Issue :
Quality Assurance has reported that she could not see the updates. I mentioned that this is probably the cache and to clear the cache to see the updates as mentioned in the following website:
https://pixpa.zendesk.com/hc/en-us/articles/201555604-My-website-changes-are-not-showing-up-How-can-I-clear-my-browser-s-cache-
She pointed out that the users of this website would not know about clearing their cache to see an update (Even it is as easy as pressing ctrl+shift+r).
Research and steps taken to try and resolve the issue:
I researched this topic and added the following meta tags into the index.html to stop caching :
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
I tested this change by updating the title of the page and this seemed to work on her end in that the title of the page updated without her needing to clear her cache. However, today the QA has reported that the page was not displaying correctly and she needed to refresh her cache to see the page correctly. A lot of changes were made in the Model/view/whatever structure so I cannot determine where in the structure this breaking change would have occurred.
To me, this seems like a very common requirement for the users to see website updates. What is a best practice approach for the end user to automatically see website updates? Any helpful advice or steps I could follow would be greatly appreciated.
This should probably solve your issue. I found it here:
https://www.reddit.com/r/angularjs/comments/3j77a2/prevent_browser_template_caching/
$httpProvider.interceptors.push(['constants', function(constants){
return {
request: function(config){
if(config.url.indexOf('partials/') > -1){
var separator = config.url.indexOf('?') === -1 ? '?' : '&';
config.url = config.url + separator + 'c=' + constants.cache;
}
return config;
}
};
}]);
This will add a cache-busting parameter to your requests to get template files. The example looks for all calls to /partials, but I would suggest changing this to look for requests ending in .html.
The simple method of doing this is just using +new Date() instead of constants.cache. That way it will always pull the most current version of the template file. If you don't care about caching the template files when they haven't been changed, this is the easy fix.
$httpProvider.interceptors.push([function(){
function endsWith(str, suffix) {
return str.indexOf(suffix, str.length - suffix.length) !== -1;
}
return {
request: function(config){
if(!endsWith(config.url, '.html')) return config;
config.url = config.url + '?c=' +new Date();
return config;
}
};
}]);
Browsers should only cache partials if the .html file that was served had a cache control header that told them to cache it.
This answer shows the header required to prevent any caching: How to control web page caching, across all browsers?
This will only apply going forward of course. Users who have already cached a partial will need to refresh.
In the interests of reducing load on your server you may want to modify the header, for example to cache for one day, or 1 hour, or whatever meets your release cycle / user requirements.
I don't think your approach of using HTML meta tags will work in this case as partials aren't loaded/rendered as html pages by the browser as part of a normal page pipeline. They are loaded asynchronously and compiled by Angular. So you need to get the cache control in at a lower level (i.e. on the header). This may be browser specific, but if they aren't working I'd expect that is why.
If the view is not displayed correctly this is css and/or javascript issue (the browser cache the css and js). The easiest work around is to put random number or in my case I put my web version in my css and js file and change it when I'm updating my website for example
example.com/style.css?v=11009
with this, your client browser will always retrieve your website css if the version is changed (because browser will assume that they are two different file)
Good morning, I have a web application in production environement. The users are using it every day, when I publish an update and a user comes back to the web application he views the old version of the web application. He needs to refresh the browser to load the new version. How can I solve this problem? I cannot tell hundreds of users to refresh the page every time I publish an update (3-4 times a week).
The classic cache problem. Unless told otherwise (by the webserver) the browser is going to cache assets based on the files name. So when it sees the same script/style/image with the same name it will 304 and serve the cached version. There are several ways around this, you can:
Configure the webserver to tell browsers not to cache the file. This is obviously inefficient as the user will have to download the same file time and time again.
Append a query string parameter to the filename such as my-angular-code.js?x=123, again inefficient.
Fingerprint your assets. This is neatly explained in Rails' Asset Pipeline documentation. There are several ways to achieve this, there are grunt tasks and gulp tasks also available for this. Essentially it is the process of changing the file name only when the contents changes, forcing the browser to consider it a new file.
A simple solution would be to add query strings representing timestamp or session id to your files.
For e.g., in our spring applications, we simply use :
<script src="js/angular/lib/app.js?r=<%= session.getId()%>"></script>
You can implement the same solution in your server specific implementation too.
If you are using HTML5, then you can use the Application Cache. It will automatically check for updates and you can prompt the user to refresh the cache.
Here are some good tutorials on how to use it:
A Beginner's Guide to Using the Application Cache
Using the application cache
Let's take this offline (offline web applications)
The first step would be to understand what's happening server side right now. Understand and configure Cache-Control and Etag. While you can use query string parameters, file fingerprinting is a better technique since proxy servers will be allowed to cache the file.
The idea is that you set your documents that can change (if you have an Angular app this is most likely just your index.html) to no-cache and aggressively cache resources that that document references. When you release a new build, your resources are all renamed and when a request comes in everything works as expected.
I use grunt-rev in my Angular app. Works great.
The First step is get the timestamp and pass the timestamp as query Parameter in the URL for .html extensions.
While loading html content every time it will render with different routes since the timestamp is append to the URL.
EXAMPLE:
var date = new Date().getTime().toString();
app.config(['$stateProvider', '$urlRouterProvider',function($stateProvider, $urlRouterProvider){
}])
$stateProvider.state('app', {
abstract: true,
url: '/app',
cache: false,
templateUrl: 'views/app.html'**+'?t='+ date**
})
.state('app.dashboard', {
url: '/dashboard',
cache: false,
controller: 'DashboardCtrl as showCase',
templateUrl: 'components/dashboard/dashboard.view.html'**+'?t=' + date** ,
})
I have done a full Single Page Application (SPA) application using Angularjs.
So far so good.
As anyone knows, all javascript files are loaded in the first time access. Or, some file are loaded in lazy mode style when needed.
So far so good...
The situation is: the server updates all files (html partials, javascripts, css's) and the client remain with a lot of files out-dated.
This would be simply solved refreshing the browser, hit F5 key, control+f5, or refresh button in the browser. But this concept does not exists when working with SPA.
I'm not sure how to solve this problem.
I could detect somehow (doing a ping maybe) and just to re-load that specific file. With document.write strategy. But now rises another problem, I have a single javascript file with all javascript minified.
I could try to force a full reload in the browser or force to re-login (and reload because login are SPA part).
But reloading is an ugly solution, imagine the client lose all data in the form because he was unlucky the server have just updated. And worse, I must now create some "auto-save" feature just because of this.
I'm not sure how to handle this, if possible, doing in "angular way".
I wonder how google gmail handles this because I stay logged for many many hours without logging of.
As others have already suggested, keep the logged user on the old version of your webapp.
Not only what you ask is difficult to do in Angular, but it can also lead to a bad user experience and surprising behaviour, since there may not be a mapping between what the user is doing with the old version and what the new version provides. Views may be removed, renamed, split or merged. The behaviour of the same view may have changed, and doing so without notice for the user may cause mistakes.
You made an example with Gmail, but may have noticed that changes to the UI always happen after you logout, never while you're using it.
First of all, if your app is an intranet website used during office time, just update it while nobody is using it. This is a much simpler solution.
Otherwise, if you need to provide 24/24 availability, my suggestion is:
When you deploy the new version of your SPA, keep the old version in parallel with the new version, keep the current users on the old version, and log new users to the new version. This can be made in a number of ways depending on your setup, but it's not difficult to do.
Keep the old version around until you're confident that nobody is still using it or you're pretty sure that the new version is ok and you don't need to rollback to the old version.
The backend services should be backward-compatible with the old version of the frontend. If that's not possible you should keep multiple version of the backend services too.
As the rest of the guys said a solution can be to versioning your files. So every time that your browser check those files out the browser notice that the files are different to the ones that are in the server so the browser cache the new files.
I suggest to use some build tool like gulp, grunt or webpack, the last one is becoming more popular.
By the moment I use gulp for my projects. I´m moving to webpack though.
if you are interested in gulp you can have a look to gulp-rev and gulp-rev-replace plugins.
What do they do?
let´s say that we have the next file in your project app.js what you get after apply gulp-rev to your project is something like app-4j8888dp.js then your html file where the app.js is injected is still pointing to app.js so you need to replace it. To do that you can use gulp-rev-replace plugin.
eg. gulp task where
var gulp = require('gulp');
var rev = require('gulp-rev');
var revReplace = require('gulp-rev-replace');
var useref = require('gulp-useref');
var filter = require('gulp-filter');
var uglify = require('gulp-uglify');
var csso = require('gulp-csso');
gulp.task("index", function() {
var jsFilter = filter("**/*.js", { restore: true });
var cssFilter = filter("**/*.css", { restore: true });
var indexHtmlFilter = filter(['**/*', '!**/index.html'], { restore: true });
return gulp.src("src/index.html")
.pipe(useref()) // Concatenate with gulp-useref
.pipe(jsFilter)
.pipe(uglify()) // Minify any javascript sources
.pipe(jsFilter.restore)
.pipe(cssFilter)
.pipe(csso()) // Minify any CSS sources
.pipe(cssFilter.restore)
.pipe(indexHtmlFilter)
.pipe(rev()) // Rename the concatenated files (but not index.html)
.pipe(indexHtmlFilter.restore)
.pipe(revReplace()) // Substitute in new filenames
.pipe(gulp.dest('public'));
});
if you want to know further details see the links bellow.
https://github.com/sindresorhus/gulp-rev
https://github.com/jamesknelson/gulp-rev-replace
A single page application is that, a single stack that controls the client logic of your application. Thus, any navigation done through the application should be handled by your client, and not by the server. The goal is to have a one single "fat" HTTP request that loads everything you need, and then perform small HTTP requests.
That's why you can only have one ng-app in your apps. You are not suppose to have multiple and just load the modules you need (although the AngularJS team wants to move that way). In all cases, you should serve the same minified file and handle everything from your application.
It seems to me that you are more worried about the state of your application. As Tom Dale (EmberJS) described in the last Cage Match, we should aim to have applications that can reflect the same data between server and client at any point of time. You have many ways to do so, either by cookies, sessions or local storage.
Usually a SPA communicates with a REST based server, and hence perform idempotent operations to your data.
tl;dr You are not supposed to refresh anything from the server (styles or scripts, for instance), just the data that your application is handling. An initial single load is what SPA is all about.
separate your data and logic and reload the data using ajax whenever you want, for that i will suggest you use REST API to get the data from server.
SPA helps you to reduce the HTTP request again and again but its also require some http request to update a new data to view.
Well, you would have to unload the old existing code (i.e. the old AngularJS app, modules, controllers, services and so on). Theoretically, you could create a custom (randomized) app name (with all modules have this prefix for each unique build!) and then rebuild your app in the browser. But seriously.. that's a) very complex and b) will probably fail due memory leaks.
So, my answer is: Don't.
Caching issues
I would personally recommend to name/prefix all resources depended by a build with a unique id; either the build id, a scm hash, the timestamp or whatever like that. So, the url to the javascript is not domain.tld/resources/scripts.js but domain.tld/resources-1234567890/scripts.js which ensures that this path's content will never conflict with a (newer) version. You can choose your path/url like you want (depending on the underlaying structure: it is all virtually, can you remap urls, etcpp). It would be even not required that each version will exist forever (i.e. map all resources-(\d+)/ to resources/; however, this would be not nice for the concept of URLs.
Application state
Well, the question is how often will the application change that it would be important that such reloads are required. How long is the SPA open in a browser? Is it really impossible to support two versions at the same time? The client (the app in the browser) could even send its own version within the HTTP requests.
In the beginning of a new application, there are a lot of changes that would require a reload. But soon after your application has a solid state, the changes will be smaller and would not require a reload. The user itself will make more refreshs.. more than we ever expected :/
As with what everyone else is saying...
Don't, and while socket.io could work it's asking for trouble if you are VERY careful.
You have two options, upon server update invalidate any previous session (I would also give users a half hours notice or 5 minutes depending on application before maintenance would be done.
The second option is versioning. If they are on version 10, then they communicate with backend 10. If you release version 11 and they are still on 10 then they can still communicate with backend 10.
Remember Google wave? It failed for a reason. Too many people writing one source as the same time causes more problems then it solves.
use $state service. create state during loading page using ctor. after specified time re create state and load page.
function(state) {
state.stateCtor(action);
$state.transitionTo(action + '.detail', {}, {
notify: true
});
}
Versioning your files, so on every update increment version number and the browser will update it automaticallly.
My solution consists of several points.
While this is not important, but I send one JavaScript file to the client side, I use grunt to prepare my release. The same grunt file adds to the JavaScript tag a query with version number. Regardless of whether you have one file or lots of files, you need to add a version number to them. You may need to do this for resources as well. (check at the end for an example)
I send back in all my responses from the server (I use node) the version number of the app.
In Angular, when I receive any response, I check the version number against the version number loaded, if it has changed (this means the server code has been updated) then I alert the user that the app is going to reload. I use location.reload(true);
Once reloaded the browser will fetch all new files again because the version number in the script tag is different now, and so it will not get it from cache.
Hope this helps.
<script src="/scripts/spa.min.js?v=0.1.1-1011"></script>