The History API is one of those JavaScript techniques that every web developer needs to know cold. Without it, a single click of the Back button will jump straight out of your application. You’ll lose any in-progress work, and the user will be forced to start all over again.

Fortunately, there’s an easy solution. Ever since the formalization of HTML5 (and the release of Internet Explorer 10), we’ve had a History API that lets you add to the history list and react to back and forward navigation. In this article, you’ll learn how it works — and you’ll try it out with a live example.

But before going ahead, let’s take a closer look at the problem.

Broken Back button syndrome

In the old days of the web, a web page did one thing—display stuff. When you wanted to look at something else, you clicked a link and went to a new URL.

As JavaScript grew more powerful, developers realized that every web page could be a complete application in its own right. A web page could rival a desktop application! The problem was the the Back and Forward buttons in the browser didn’t fit this new application model. For example, a user might perform a sequence of tasks in a single-page web application and then expect to use the Back button to go back a single step. Instead, the Back button would return to the previous web page, effectively shutting down the JavaScript app in the middle of whatever it was doing.

Often, this behavior wasn’t intuitive. For example, consider an application that uses the XMLHttpRequest object to fetch new data and update the page. To the user, it might look like they’re travelling to a new page. But if they make the mistake of using the Back button, the illusion is shattered and everything goes sideways.

Let’s take a closer look. Here’s an example that lets you step through the Dictionnaire Infernal, a famous (and now public domain) book from 1818 describing strange beasts from superstition and mythology. The trick is that the Previous and Next links don’t take you to another page. Instead, they trigger an asynchronous call through XMLHttpRequest that gets the information for the next slide. Then, the JavaScript code updates the slide box, without disturbing the content on the rest of the page.

Here’s a live version of the example, complete with History API support. Even though everything takes place in a single web page, you can use the Forward and Back buttons to seamlessly step from one picture to the next. You can even use the Reload button (reloads the current slide, rather than restarting the whole app), and you can place bookmarks that keep the current state — in other words, they lead you straight back to the slide that you’re currently looking at.

And here’s an old-school version of the example that doesn’t use the History API. It doesn’t matter how far you are into the side show — when you use the Back button, you jump back to the previous web page. The Forward and Reload buttons are similarly broken. Bookmarks restart the app from the beginning.

You can download all the source code here to play with. (However, it won’t work locally, because it use the XMLHttpRequest object to make web requests.) In the rest of this article, I’ll break down how everything works.

Preparing the example

Before we get into the History API, it’s important to have a basic understanding of how this example works. It’s all pretty simple.

The page uses a number to keep track of the slide that it’s currently showing. When you click Next or Previous, the code changes the current slide number. Then it uses its own goToSlide() function to update the page.

The goToSlide() function is where the real work takes place. It starts an asychronous request using the infamous XMLHttpRequest object.

It’s up to you to decide how the slide content is generated and sent back to the calling code in the page. Usually, you’ll have some sort of routing framework that triggers the right server-side code. That server-side code can then grab the slide content from a cache, a database, a file, or even a hard-coded block of markup.

In this example, I’ve kept things as simple as possible. The request grabs the HTML markup from a static file. So if you’re requesting the third slide (with a relative URL like Slides/3), you get a snippet of HTML markup for just that slide. Easy!

When the request finishes, the browser triggers the newSlideReceived() event handler. Then it’s simply a matter of taking the received markup and inserting it into the page, using a placeholder <div> .

That’s all you need to know to build the naive version of this page, which doesn’t support browser history.

The history object

The history object has existed almost since the dawn of JavaScript. But for much of its life, it was far less powerful (and far less useful) than it is today.

The original history object has just one property and three basic methods. The property is length , and it tells you how many entries are in the browser’s history list:

alert("You have " + history.length + " pages in the history.");

This detail is mostly useless.

The three original history methods are back() (sends a visitor one step back in the browsing history), forward() (sends a visitor one step forward) and go() (takes a numeric offset that tells the browser how many steps to go forward or backward). All this adds up to relatively little, unless you want to design your own custom back and forward buttons on a web page.

The HistoryAPI adds two much more useful ingredients: the pushState() method and the onPopState event.

Adding an entry to the history list

The pushState() method is the centerpiece of the History API. It lets you add a new item into the history list for the current page. Here’s what that looks like:

At this point, you might wonder — what’s the point in having more than one entry for the same page? The trick is that each entry has some state attached with it — a linked piece of information or object that you set. When the user steps back through the history list, you get the corresponding state information so you can return the page to its previous version.

The pushState() method takes three arguments:

Data. The first argument is any piece of data you want to store to represent the current state of this page. You’ll use this information to restore the page when the user goes back through the history list.

The first argument is any piece of data you want to store to represent the current state of this page. You’ll use this information to restore the page when the user goes back through the history list. Title. The second argument is the page title you want the browser to show. Currently, all browsers are unified in ignoring this detail. (So you can just pass null .)

The second argument is the page title you want the browser to show. Currently, all browsers are unified in ignoring this detail. (So you can just pass .) URL. The third argument is the new version of the URL that you want to show for the page in the browser’s address bar. This lets you add better support for the Reload button and browser bookmarks.

In the Dictionnaire Infernal slideshow example, it’s easy to add history support All you need to keep track of is the slide number. You add to the history list every time the slide changes.

Here’s the line of code you need to add:

history.pushState(slideNumber, null, null);

You’ll notice that this code doesn’t change the page title with the second argument (because it doesn’t work anyway) and doesn’t change the URL (you’ll see how to do that in a moment). All it does is store the slide number.

For the full picture, here are the two calls to history.pushState() in their proper place — the event handlers for the Next and Previous links:

Returning to a previous page

When you use the pushState() method, you also need to think about its natural counterpart, the onPopState event. While the pushState() method puts a new entry into the browser’s history list, the onPopState event gives you the chance to deal with it when the user returns.

To understand how this works, consider what happens if a visitor steps through all the slides in the Dictionnaire Infernal example. As each new slide is loaded up, the page adds a history entry. But if the user steps back with the Back button, nothing happens.

To fix this shortcoming, you need to handle the onPopState event, which is triggered after every history navigation. (This includes if you use one of the history methods, like history.back() , as well as when the user clicks the browser navigation buttons.)

The onPopState event provides your code with the state information you stored earlier with pushState() . In this case, that’s just the slide number, which you can grab from the state property of the event argument, like so:

var slideNumber = e.State;

Your job is to use the state information to restore the proper version of the page. In the current example, that means loading the corresponding slide by calling the handy goToSlide() function that handles all the slide navigation. The complete code is short and straightforward:

Notice that you need to check if e.State is null , which happens on some browsers when the page is loaded for the first time and there is no state saved.

Changing the URL

The current version of this example supports the history list but doesn’t play nicely with the Reload button. For example, if you click through to the third slide and then refresh the page, you’ll wind up back at the beginning. You’ll find yourself in the same situation if you attempt to bookmark one of the slides — return to the bookmark and you’ll start back on the first slide.

The solution to these problems is to use the History API to modify the URL. But there’s an important reason that I’ve left this step until last. Often, examples that use the History API change the URL but don’t use the new URL. This can lead to a web app that breaks when the user refreshes it (with an ugly 404 error), or goes to a similar but slightly different version of the page. If you aren’t sure how to handle different URLs, there’s no shame in using the simplified approach from the previous example.

So let’s say you’re ready to dive in, make better URLs, and play nicely with bookmarks and browser refreshes. There are two basic approaches you can take:

Change the URL page name altogether. In the Dictionnaire Infernal example, you might change the URL to Slide1.html, Slide2.html, and so on as the user moves through the slides. The catch is that you need to either have actual pages with these names (don’t do that, it’s cumbersome), or you need to set up routing so that when your web server gets a request for Slide2.html it runs code that creates the right version of the page.

In the Dictionnaire Infernal example, you might change the URL to Slide1.html, Slide2.html, and so on as the user moves through the slides. The catch is that you need to either have actual pages with these names (don’t do that, it’s cumbersome), or you need to set up routing so that when your web server gets a request for Slide2.html it runs code that creates the right version of the page. Keep the same page name but add information to the query string. This is a simpler, smaller-scale approach. As the user travels through the slideshow, you’ll end up with URLs like SlideShow.html?slide=1, then SlideShow.html?slide=2, SlideShow.html?slide=3, and so on. The benefit of this approach is that it’s trivially easy to write a little piece of JavaScript that checks the query string and makes sure you’re on the right slide when the page is reloaded. You don’t need any server code to make this work — one page can do it all.

Here’s a modified version of the pushState() call you saw earlier that uses the second approach. This code stuffs the slide number onto the end of the URL:

history.pushState(slide, null, "Dictionnaire.html?slide=" + slide);

And here’s the code that runs when the page is reloaded. It checks for the slide information in the URL, and — if it finds a proper number — calls the goToSlide() function to load up the slide.

Now you’ll never lose your place in the slideshow. You can try out the complete version of this example or download everything here.

Final details

The History API is easy to use, but it also makes it easy to create bigger problems that you solve. Now that you know how everything fits together, here are some final bits of advice:

If you push, you must pop. The pushState() method and onPopState event are partners. What you put in with one, you get back with the other.

The method and event are partners. What you put in with one, you get back with the other. Don’t change the URL unless you’re ready to handle it. It’s nice to change your URL to reflect your changing page, but you need to be able to handle requests for every URL you use. If your URL points to a page that doesn’t exist, your application will break on refreshes and bookmarks.

It’s nice to change your URL to reflect your changing page, but you need to be able to handle requests for every URL you use. If your URL points to a page that doesn’t exist, your application will break on refreshes and bookmarks. Use a custom state object to store more information. You don’t need to stick with a simple number. You can put an entire custom object into state, which can bundle together all the information you need for a complex task.

Enjoyed this read? Let us know your JavaScript pain points in comments below! And consider subscribing for friend links and a monthly newsletter.