History API
JavaScript · Reference cheat sheet
History API
JavaScript · Reference cheat sheet
📋 Overview
The History API lets SPAs update the URL and session history without full reloads: pushState, replaceState, and the popstate event. Pair with the Navigation API / URL patterns for modern routing. Same-origin only for state URLs.
🔧 Core concepts
history.pushState(state, unused, url?): add entry; no reload.history.replaceState(...): replace current entry.history.state: current state object (structured-cloneable).history.back()/forward()/go(n): traverse.popstate: fires on back/forward (not onpushStateitself).hashchange: hash-only changes (older routing style).- Title arg: ignored in modern browsers — update
document.titleyourself.
history.pushState({ page: 2 }, "", "/page/2");
window.addEventListener("popstate", (e) => {
render(e.state);
});💡 Examples
// Navigate in SPA
function navigate(path, state = {}) {
history.pushState(state, "", path);
renderRoute(path, state);
}
function replace(path, state = {}) {
history.replaceState(state, "", path);
renderRoute(path, state);
}
window.addEventListener("popstate", (e) => {
renderRoute(location.pathname, e.state);
});
// Initial hydrate
renderRoute(location.pathname, history.state);
// Query updates without stacking junk
const url = new URL(location.href);
url.searchParams.set("q", query);
history.replaceState(history.state, "", url);
// Scroll restoration
if ("scrollRestoration" in history) {
history.scrollRestoration = "manual";
}
// Guard external links vs internal
document.addEventListener("click", (e) => {
const a = e.target.closest("a[href]");
if (!a || a.origin !== location.origin || a.target === "_blank") return;
if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
e.preventDefault();
navigate(a.pathname + a.search + a.hash);
});// State must be cloneable
history.pushState({ id: 1, when: new Date() }, "", "/item/1");
// history.pushState({ fn: () => {} }, "", "/x"); // DataCloneError⚠️ Pitfalls
pushStatedoes not firepopstate— you must render yourself after push.- State is structured-cloned — no functions, DOM nodes, or symbols.
- Relative URLs resolve against current URL — easy to create wrong paths.
- Back button through many
pushStateentries can feel sticky — usereplaceStatefor filters. - Cross-origin URLs throw — stay same-origin.
🔗 Related
- url.md — URL / URLSearchParams
- structured_clone.md — state cloning
- DOM/navigation.md — location / navigation
- DOM/events.md — popstate
- encode.md — encoding query parts