How does the History API work?
By FrontendPro Editorial Team Updated 9/26/2026
Answer
It lets you change the URL and the history stack without reloading the page — the mechanism every client-side router is built on.
history.pushState({ page: 2 }, "", "/products?page=2"); // add an entry
history.replaceState({ page: 2 }, "", "/products?page=2"); // change the current one
history.back();
history.forward();
history.go(-2);
history.length;
The three arguments are a state object, a title (ignored by browsers — pass ""), and the new URL. The URL must be same-origin; anything else throws.
Neither method loads anything
pushState changes the address bar and the stack, nothing more. Rendering the new view is your job — this is exactly what a router does for you.
Handling back and forward
window.addEventListener("popstate", (event) => {
render(event.state); // the state object you pushed
});
popstate fires when the user navigates the history, not when you call pushState yourself. Forgetting this is the classic bug: the forward navigation works and the back button does nothing.
push or replace?
pushState when the user should be able to go back — changing page, opening a detail view. replaceState when they should not — updating a filter as they type, or normalising a URL. Pushing on every keystroke fills the stack so the back button becomes useless.
Practical notes
The state object is structured-cloned and capped (around 640 KB in Firefox), so store an id and refetch rather than a whole dataset.
For simple query-string updates, combine it with URL:
const url = new URL(location.href);
url.searchParams.set("page", "2");
history.replaceState(null, "", url);
And in a framework, prefer its router — calling pushState directly leaves the router's internal state out of sync with the URL.
