History API & Client-Side Routing0%

History API & Client-Side Routing

Beginner12 min read•Updated: Jul 11, 2026
Study Materials

History API & Client-Side Routing in Modern JavaScript

Single Page Applications (SPAs) like React, Vue, and Next.js do not reload the entire HTML page when navigating between views. Instead, they intercept navigation and manipulate the browser's session history using the HTML5 History API (pushState, replaceState, and popstate), creating seamless transitions while preserving bookmarkable URLs.


1. The History API Methods

The window.history object exposes three primary routing primitives:

  1. 1
    history.pushState(state, title, url): Pushes a new entry onto the browser's history stack and updates the URL bar without triggering a page reload.
  2. 2
    history.replaceState(state, title, url): Overwrites the current history entry (ideal for query parameter updates or redirects).
  3. 3
    window.onpopstate: Fires when the user clicks the browser's Back or Forward navigation buttons.
Output
User clicks navigation link
│
e.preventDefault()
│
history.pushState({ view: 'settings' }, '', '/settings')
│
URL bar updates to /settings (Zero page reload!)
│
Render Settings View Component into DOM

2. Building a Lightweight Client-Side Router

JavaScript
class ClientRouter {
constructor(routes, outletSelector = '#app-root') {
this.routes = routes;
this.outlet = document.querySelector(outletSelector);
 
this.init();
}
 
init() {
// 1. Intercept internal anchor clicks
document.addEventListener('click', (e) => {
const link = e.target.closest('a[data-link]');
if (link) {
e.preventDefault(); // Stop default browser page refresh!
this.navigate(link.getAttribute('href'));
}
});
 
// 2. Handle browser Back/Forward navigation
window.addEventListener('popstate', (e) => {
this.resolveRoute(window.location.pathname, e.state);
});
 
// 3. Resolve initial page load
this.resolveRoute(window.location.pathname);
}
 
navigate(url, state = {}) {
// Push state and update browser URL bar
history.pushState(state, '', url);
this.resolveRoute(url, state);
}
 
resolveRoute(path, state = {}) {
const routeHandler = this.routes[path] || this.routes['/404'];
this.outlet.innerHTML = routeHandler(state);
}
}

Route Table & Usage:

JavaScript
const routes = {
'/': () => '<h1>Home Page</h1><p>Welcome to our SPA!</p>',
'/products': () => '<h1>Product Catalog</h1><p>Browse our collection.</p>',
'/settings': () => '<h1>User Settings</h1><p>Configure preferences.</p>',
'/404': () => '<h1>404 Not Found</h1><p>Page does not exist.</p>'
};
 
// Mount router
const router = new ClientRouter(routes);

3. Server Configuration: The "SPA Fallback" Rewrite

A critical production gotcha: If a user refreshes the page while at https://mysite.com/settings, the browser sends an HTTP GET request to the web server for /settings.

If the server is not configured to rewrite all routes to index.html, it will return a 404 Not Found!

Production Server Rule (Nginx / Cloudflare / Netlify):

All non-file requests must rewrite to index.html:

NGINX
# Nginx SPA Configuration
location / {
try_files $uri $uri/ /index.html;
}

Interactive Quiz

Practice Quiz

Question 1
What does history.pushState(state, title, url) do?
Question 2
When does the window popstate event fire?
Question 3
Why does history.pushState() NOT fire the popstate event?
Question 4
What server-side configuration is mandatory for client-side SPA routing to work on page refresh?
Question 5
How does history.replaceState() differ from history.pushState()?
Next Lesson

Geolocation, Clipboard & Device APIs

Continue learning with hands-on practice, examples, and exercises in the upcoming topic.

Practice Quiz

Test your understanding of this lesson with 5 questions. Each question has one correct answer.

PrevNext