Navigation and History in JavaScript

Introduction

Browsers track where users go with the History API and location object. Single-page apps (SPAs) change URLs without full page reloads using history.pushState. This chapter covers reading URLs, programmatic navigation, and listening for back/forward buttons.

Prerequisites

location Object

javascript
// Current page URL parts (in browser console)
console.log(location.href);
console.log(location.pathname);
console.log(location.search);
console.log(location.hash);

Navigate away (full reload):

javascript
// Assigning href reloads page
// location.href = "https://example.com/docs";

Query String with URLSearchParams

javascript
// Parse ?q=node&page=2
const params = new URLSearchParams(location.search);
console.log(params.get("q"));
console.log(params.get("page"));
 
params.set("page", "3");
const nextSearch = params.toString();
console.log(`${location.pathname}?${nextSearch}`);

history.pushState — SPA Routes

javascript
// Change URL without reload
function showView(view) {
  const url = `/app/${view}`;
  history.pushState({ view }, "", url);
  renderView(view);
}
 
function renderView(view) {
  document.body.dataset.view = view;
  console.log("render", view);
}
 
window.addEventListener("popstate", (event) => {
  const view = event.state?.view ?? "home";
  renderView(view);
});
 
// Demo: push two states
showView("dashboard");
showView("settings");

Framework routers (React Router, Vue Router) wrap the same History API.

history.replaceState

Like pushState but replaces current entry—good for redirects without extra back-stack entries.

javascript
// Language and online status — not navigation but common with locale
console.log(navigator.language);
console.log(navigator.onLine);
 
window.addEventListener("online", () => console.log("back online"));
window.addEventListener("offline", () => console.log("offline"));

Geolocation is a separate permission-gated API (navigator.geolocation)—use only with user consent.

Mini Example: Hash-Based Tab Switch

html
<nav>
  <a href="#home">Home</a>
  <a href="#profile">Profile</a>
</nav>
<div id="panel"></div>
<script>
  const panel = document.getElementById("panel");
 
  function render() {
    const tab = location.hash.replace("#", "") || "home";
    panel.textContent = `Tab: ${tab}`;
  }
 
  window.addEventListener("hashchange", render);
  render();
</script>

Hash routing avoids server config for simple demos; path routing needs server fallback to index.html.

FAQ

pushState vs location.hash?

pushState gives clean URLs (/app/settings); hashes (#/settings) are easier for static hosting without rewrite rules.

<a target="_blank" rel="noopener noreferrer"> for external URLs.

SSR frameworks?

Server must know routes too—client-only pushState is not enough alone.

What comes next?

Web Workers.