What is the History API?

The History API lets JavaScript change the URL and back button without a reload. How pushState, replaceState and popstate work, plus SEO traps.

What is the History API?

The History API is a browser interface, exposed as window.history, that lets JavaScript move through the session history and add or change entries. With pushState() and replaceState() a page can update the address bar and the back button without a full reload, which is how a single-page app gives every screen its own URL.

How it works

  • history.back(), history.forward() and history.go(n) move through existing entries, like the browser buttons. history.length is the number of entries.
  • history.pushState(state, "", url) adds a new entry. history.replaceState(state, "", url) overwrites the current one instead.
  • state is any serializable object stored with the entry. The second argument is unused but cannot be left out, so pass an empty string.
  • url is optional and must be the same origin as the current page, otherwise the call throws a SecurityError.
  • Neither method loads the URL or checks that it exists. They only change what the browser records and shows.
  • The popstate event fires when the user moves through history (back, forward, go()). Calling pushState() or replaceState() does not fire it, and browsers may skip it until the page has been interacted with.

The MDN reference for pushState() documents the details.

A minimal router

Navigation calls pushState and renders the view itself. Back and forward trigger popstate, and the handler renders the view for the URL the browser has restored:

function navigate(path) {
  history.pushState({ path: path }, "", path);
  render(path);
}

window.addEventListener("popstate", function () {
  render(location.pathname);
});

The renderer works on the DOM in place, so no new document is requested.

The server still has to answer those URLs

A URL created with pushState is only a record in the browser. If a visitor reloads on /products/42, bookmarks it or follows a shared link, the server receives that request. It must return the app (or real content) for it, not a 404. MDN notes the browser will not load the URL after pushState(), but may load it later, for instance after a restart.

SEO and crawlers

Google's guidance for JavaScript sites is to use the History API for routing between views and not to use URL fragments to load different content. Links must be real <a href="..."> elements: crawlers find links through the href, and a click handler on a span or an anchor without href gives them nothing to follow. Error views also need care, because a client-routed 404 page still returns status 200 unless you redirect to a URL that returns a real 404 or add a noindex robots tag. See Google's JavaScript SEO basics.

Common mistakes

  • No popstate handler. The URL changes on Back but the screen does not.
  • Links without href. They work in the browser and are invisible to crawlers, and middle-click and "open in new tab" break.
  • Hash routing for content. Fragment URLs are not reliably resolved by crawlers.
  • Large state objects. Some browsers cap the serialized size and throw. Keep state small and use sessionStorage for more.
  • Expecting the title to change. The second argument is ignored, so set document.title yourself and move focus for screen-reader users.
  • No server fallback. Deep links return 404 on reload.

How it relates to testing and monitoring

A pushState navigation usually does not request a new HTML page. The cost sits in the API calls and rendering that follow, so a test that only fetches HTML misses most of it. Test the API calls the app makes, weighted the way users make them: see load testing single page applications and run them with load testing. API monitoring keeps watching the endpoints behind your routes, and synthetic monitoring covers whole journeys. Note that deep URLs should also be checked directly: a request for a route URL should return 200 from the server, not only work after in-app navigation.

FAQ: History API

Does pushState reload the page?

No. It adds a history entry and changes the URL shown in the address bar, but the browser does not load that URL and does not check that it exists.

Does pushState trigger the popstate event?

No. popstate fires when the active history entry changes through user navigation, such as Back, Forward or history.go(). After your own pushState call you update the view yourself.

What is the difference between pushState and replaceState?

pushState adds a new entry, so Back returns to the previous one. replaceState overwrites the current entry, so no extra Back step is created. Use replace for corrections such as redirects or updating a filter in the URL.

Should I use hash routing or the History API?

For content that should be indexed, use the History API with real paths. Google advises against using URL fragments to load different content. Hash routing needs no server configuration, which can suit internal tools that do not need search traffic.

Do I need server configuration for History API routes?

Yes. The server must answer every route URL, usually by returning the app shell or pre-rendered content, because a reload, bookmark or shared link requests that URL directly.

How fast is your website?

Elevate its speed and SEO seamlessly with our Free Speed Test.

Free Website Speed Test

Analyze your website's load speed and improve its performance with our free page speed checker.