🚀 LEVEL UP TO SENIOR:Unlock 500+ Advanced Practical Challenges & Exercises.
🎓 COURSERA PARTNER:Earn professional Google, Meta, and IBM certificates to supercharge your resume.
JS MASTER CLASS /// MASTER THE ENGINE /// BUILD LOGIC /// ASYNC PATTERNS /// JS MASTER CLASS /// MASTER THE ENGINE ///

The History API | JavaScript Tutorial - In-Depth Guide

Master the History API: pushState and replaceState, the popstate event, building a minimal client-side router, and how the History API relates to Single Page Application navigation.

Total XP: 0|💻 javascript XP: 0

Skill Matrix

UNLOCK NODES BY LEARNING NEW TAGS.

System Hub

Core logic.

Quick Quiz //

Does calling history.pushState() trigger a full page reload?


🚀 LEVEL UP TO SENIOR:Unlock 500+ Advanced Practical Challenges & Exercises.
🎓 COURSERA PARTNER:Earn professional Google, Meta, and IBM certificates to supercharge your resume.

The History API lets JavaScript change the browser's URL and manage navigation history without triggering a full page reload — the foundational mechanism every client-side router (React Router, Vue Router) is built on.

1The History API | JavaScript Tutorial - In-Depth Guide Part 1

history.pushState() changes the URL shown in the address bar and adds a new entry to the browser's history, without reloading the page.

+
history.pushState({ page: 'profile' }, '', '/profile');
// URL is now /profile, no reload occurred
localhost:3000
🕰️

pushState()

2The History API | JavaScript Tutorial - In-Depth Guide Part 2

history.replaceState() works like pushState() but replaces the current history entry instead of adding a new one — useful for redirects that shouldn't create an extra 'back' step.

+
history.replaceState(null, '', '/normalized-path');
localhost:3000

replaceState()

3The History API | JavaScript Tutorial - In-Depth Guide Part 3

The 'popstate' event fires when the user navigates via the browser's back/forward buttons, letting your app respond by rendering the corresponding view.

+
window.addEventListener('popstate', (event) => {
  renderRouteFor(location.pathname, event.state);
});
localhost:3000

The popstate Event

4The History API | JavaScript Tutorial - In-Depth Guide Part 4

The state object passed to pushState() is retrievable later via event.state in a popstate handler, letting you restore view-specific data without re-fetching it.

+
history.pushState({ scrollY: window.scrollY }, '', '/list');
// later, on popstate:
window.addEventListener('popstate', (e) => {
  window.scrollTo(0, e.state?.scrollY ?? 0);
});
localhost:3000

Attaching State Data

5The History API | JavaScript Tutorial - In-Depth Guide Part 5

A minimal client-side router combines pushState (for programmatic navigation), an intercepted click handler on links, and popstate (for back/forward) to fully control in-app navigation.

+
document.addEventListener('click', (e) => {
  const link = e.target.closest('a[data-route]');
  if (!link) return;
  e.preventDefault();
  history.pushState({}, '', link.href);
  renderRouteFor(location.pathname);
});
localhost:3000

Building a Minimal Router

6Step-by-Step Breakdown

history.pushState() changes the URL shown in the address bar and adds a new entry to the browser's history, without reloading the page.

Checkpoint: Does calling history.pushState() trigger a full page reload?

  • Yes, the page reloads with the new URL
  • No, only the URL and history entry change

history.replaceState() works like pushState() but replaces the current history entry instead of adding a new one — useful for redirects that shouldn't create an extra 'back' step.

The 'popstate' event fires when the user navigates via the browser's back/forward buttons, letting your app respond by rendering the corresponding view.

Checkpoint: Does the popstate event fire when your own code calls history.pushState()?

  • Yes, every history change fires popstate
  • No, only actual back/forward navigation fires it

The state object passed to pushState() is retrievable later via event.state in a popstate handler, letting you restore view-specific data without re-fetching it.

A minimal client-side router combines pushState (for programmatic navigation), an intercepted click handler on links, and popstate (for back/forward) to fully control in-app navigation.

Next, we'll explore 'Web Storage Best Practices'.

Level Up 🚀

Advanced cheat sheets, SEO tricks, and interview prep for this topic.

Browser Support

ChromeSupported

Fully supported.

FirefoxSupported

Fully supported.

SafariSupported

Fully supported.

EdgeSupported

Fully supported.

Accessibility (A11y)

1Move Focus to the New View After a Client-Side Route Change

Because pushState()-based navigation never triggers a full page reload (which would normally reset focus to the top of the document), a custom router must explicitly move focus to the new view's main heading or content area after rendering, so screen reader and keyboard users aren't left with focus stranded on a now-irrelevant element.

SEO Implications

  • 1

    Client-Side-Only Routing Can Hurt Crawlability Without Server-Side Rendering

    Search engine crawlers may not execute JavaScript-driven pushState() navigation the same way a user's browser does; pairing client-side routing with server-side rendering (or static generation) for the initial load of each route is important for reliable indexing.

Best Practices

Intercept Internal Link Clicks to Prevent Full Page Reloads

A Single Page Application should call preventDefault() on internal link clicks and drive navigation through pushState() plus its own rendering logic, reserving real navigation for external links.

Use replaceState() for Redirects and URL Normalization

Adding a full history entry for a redirect or cleanup step creates a confusing double "back" experience; replaceState() avoids polluting history with steps the user never intentionally visited.

Frequent Bugs

THE BUG

Building a custom router that calls pushState() but forgets that popstate won't fire for it, so the view isn't re-rendered until the user separately triggers a browser back/forward action.

THE FIX

Explicitly call your rendering function immediately after every pushState()/replaceState() call, and separately in the popstate handler for back/forward navigation.

THE BUG

Relying on pushState() alone without a popstate listener, so clicking the browser's back button changes the URL but leaves the previous view rendered.

THE FIX

Always register a popstate event listener that re-renders the appropriate view based on the current location whenever back/forward navigation occurs.

Real-World Examples

A Minimal Vanilla-JS Router

A small project wanted client-side navigation between a few views without pulling in a full routing library.

function navigate(path) {
  history.pushState({}, '', path);
  render(path);
}
window.addEventListener('popstate', () => render(location.pathname));
function render(path) {
  document.getElementById('app').innerHTML = routes[path]?.() ?? notFoundView();
}

Interview Prep

?Frequently Asked Questions

Pascual Vila

Pascual Vila

Frontend Instructor // Code Syllabus

Common Pitfalls & Errors

The Error //

Forgetting to handle the popstate event in a custom router

window.addEventListener('popstate', () => render(location.pathname));

The Solution //

Always add a popstate listener that re-renders based on the current location for back/forward navigation.

Lesson Glossary

[01]History API

The browser interface for manipulating session history and the address bar without a page reload.

Code Preview
window.history

[02]pushState()

Adds a new history entry and updates the URL without reloading.

Code Preview
history.pushState(...)

[03]replaceState()

Replaces the current history entry and URL without adding a new one.

Code Preview
history.replaceState(...)

[04]popstate Event

Fires when the user navigates via back/forward, not on programmatic pushState/replaceState calls.

Code Preview
addEventListener('popstate', fn)

[05]Client-Side Routing

Handling navigation entirely in JavaScript using the History API, without full page reloads.

Code Preview
SPA router

Continue Learning