Not long ago, history was one of the more useless and certainly unused Javascript objects available. Reading length, sending a user backwards and forwards – that was all you could do. But with every dynamic website using AJAX to update contents without reloading the complete page it became obvious that we needed more control. The HTML5 extensions of the JavaScript History API is the way out of this dilema.

Browser History and AJAX

There had been a lot of different scripts and various concepts to handle the browser history. And still, there is a huge amount of AJAX sites which are poorly scripted and do not handle or better do not care about proper navigation. The consequence – users can not bookmark or navigate via the browser, one single reload and your complete setup is gone. To name two major concepts:

The hidden iframe

Many websites make use of a hidden iframe to build the browser history. For each AJAX request, there is a second one, a “real” browser request from a hidden iframe in order to push this url to your browser history. This works as expected, well kind of at least. The huge downside on this, you double the traffic, for your server and for client which is especially annoying in a mobile enviroment.

The location hash, or hashbang

Manipulating the hash (#) of an url was a huge step in building a proper history, and until the majority of users are on a modern browser with support for the history API, location.hash is going to remain the best answer to this problem. Disadvantages are:

  • If your page uses hash-urls the content that is available through these ‘fake’ links is not available to end-users without javascript and for bots / crawlers which scan your page – in case you don’t provide a fallback solution for the contents
  • Servers don’t have access to hash tags data so you don’t see it in your server logs so it helps some with analytics.
  • You disable the default hash functionality the deep linking to sections of long pages.

The new approach – the history API

The API is quite simple: Whenever you want to add something to the history, e.g. you load further news to the page, history.pushState() has to be executed. You can run history.pushState() (or modify the current state using history.replaceState()) as many times as necessary.


// Ajax request
...
// display result
...
// modify history
history.pushState(obj, title, url);
// obj - state information, e.g. the data of your ajax request;
// or the data of your ajax response
// title - optional, can be used as document title
// The URL can be anything — the browser won’t jump to that page, 
// but could - depending on your popmethod

When the user clicks back (or forward), the window.onpopstate event is fired. A handler function can retrieve the associated state and display what you define:


// a theoretic popstate example
window.addEventListener("popstate", function(e) {
	if(e.state && e.state.url) {
		// url is set by push method
		// no we might re-execute the ajax call
		// to receive new data from server
		yourAjaxMethod({url: e.state.url});
	} else if(e.state && e.state.content) {
		// in case you stored the orignal content 
		// via the push method, update the DOM 
		document.title = stateObj.title;
		yourContentElement.innerHTML = e.state.content;
	} else if(e.state && e.state.data) {
		// or you work with some kind of 
		// object data, reinitialize the data handler
		yourDataMethod(e.state.data);
	}
});

Possible pitfalls

The push (or replace) state let you provide a bookmarkable address, as if the page had reloaded entirely, without doubt a big plus. The pop method is the place where you decide what happens, great! But be aware of some pitfalls you might step into. A “real” history.back (or forward) might execute a new server request. This basicly depends on your server settings (cache), but in practice many browsers will always use an in-memory copy within a certain timescale. This is out of your hand but with the pop method you are in charge:

  • Updating on ‘history.back’ might break the UX. E.g. you have a multi-step ordering process, step one – you offer a price, user chooses, proceed, goes back and will now get a different price list. Even if the price is lower, it might be not what the user expects.
  • Always ‘reload’ the original data from the history object, means no new request, no update. Data is consistent – but maybe out of date? Same process, step 3: server checks if the object is available for the given price, it turns out, it changed in between. User will getting some sort of error message, and again the user might be unsatisfied.

There is no general solution for this, it depends on the life-time of your data. Since the possibilities of the history API allow one more way of caching information, you have to answer yourself this question.

Whatever you do to solve this, solve it and start making history.

Best way to implement

Currently the best solution? I go for a polyfill, e.g. History.js. It uses HTML4’s hashchange event with document fragment identifiers to mimic the history API in older browsers. If one of the hash URLs is used by a modern browser, it uses replaceState to quietly correct the URL.