Blog/NotesConcept

Understanding popstate event in Single Page Applications (SPAs)

A Quick guide about popstate event in JavaScript, If you’ve ever hit the back button in your browser and wondered how your Single-Page Application knows which view to render, this guide is for you.

Intermediate

Vijay Sai Krishna vsuri

Last Updated Aug 21, 2025


Understanding popstate in Single Page Applications (SPAs)

We'll demystify the popstate event, explore examples in vanilla JavaScript, and then compare how libraries like React Router handle it under the hood.

 

The Basics: What is popstate in JavaScript?

The popstate event is fired on the window whenever the active history entry changes. This usually happens when the user clicks Back or Forward in the browser

window.addEventListener("popstate", (event) => {
  console.log("Location changed to:", window.location.pathname);
  console.log("State object:", event.state);
});
Important notes:
  • Unlike hashchange, popstate deals with the History API (pushState, replaceState, back, forward, go).
  • It does not fire when you call pushState or replaceState directly.
  • It only fires on user navigation or when you explicitly call history.back(), history.forward(), etc.

A Simple Example

Imagine a single-page app with two pages:

<button id="page1">Go to Page 1</button>
<button id="page2">Go to Page 2</button>
<div id="content"></div>
const content = document.getElementById("content");

function renderPage(page) {
  content.textContent = `You are on ${page}`;
}

document.getElementById("page1").onclick = () => {
  history.pushState({ page: "Page 1" }, "", "/page1");
  renderPage("Page 1");
};

document.getElementById("page2").onclick = () => {
  history.pushState({ page: "Page 2" }, "", "/page2");
  renderPage("Page 2");
};

window.addEventListener("popstate", (event) => {
  if (event.state) {
    renderPage(event.state.page);
  }
});

Multi-Iframe Considerations

  • Iframe Isolation: Each iframe has its own history stack.
  • Parent ↔ Iframe Independence: Navigation in the parent doesn’t affect the iframe and vice versa.
  • Same-Origin Only: Direct history manipulation only works within same-origin iframes.
  • Cross-Origin Communication: Use postMessage for syncing history between origins.
  • Programmatic Calls: Just like in single-window apps, pushState/replaceState don’t trigger popstate.

To synchronize navigation across iframes, use a communication layer with postMessage.


Direct URL Access: How SPAs Handle It

Question:

👉 "If I open /page2 directly in the browser, how does my SPA know what to render?"

1. Server Responsibility

  • There’s no physical /page2.html
  • The server is configured to always return index.html (History API Fallback)

2. Client Responsibility

  • Once index.html loads:
  • Your SPA boots up and looks at window.location.pathname
  • The router (e.g. React Router) matches the route and renders the correct component

Example (React Router)

<BrowserRouter>
  <Routes>
    <Route path="/page1" element={<Page1 />} />
    <Route path="/page2" element={<Page2 />} />
  </Routes>
</BrowserRouter>
 

Opening /page2 loads index.html, then React Router handles the routing.

If the server isn’t configured: You’ll get a 404 error, since the server looks for /page2/index.html and doesn’t find it.

Solution: Configure the server to always return index.html.


How React Router Handles This

React Router (v6 and above) leverages the History API just like your vanilla JS examples, but with abstraction:

Features:

  • Wraps manual event handling behind a declarative API
  • Provides components like <Link> and hooks like useNavigate()
  • Automatically listens to popstate and re-renders the correct component tree

Example

import { BrowserRouter, Routes, Route, Link } from "react-router-dom";

export default function App() {
  return (
    <BrowserRouter>
      <nav>
        <Link to="/page1">Page 1</Link>
        <Link to="/page2">Page 2</Link>
      </nav>
      <Routes>
        <Route path="/page1" element={<div>You are on Page 1</div>} />
        <Route path="/page2" element={<div>You are on Page 2</div>} />
      </Routes>
    </BrowserRouter>
  );
}
 

React Router saves you from writing boilerplate like:

  • Listening for popstate
  • Managing route state
  • Manually rendering based on location.pathname

Key Takeaways

  • popstate is triggered on browser history navigation, not on pushState/replaceState
  • In multi-iframe apps, popstate is scoped to the iframe/window
  • SPAs render correctly on direct links by:
    • Server always returning index.html (History API fallback)
    • Client router rendering based on window.location.pathname
  • React Router simplifies this with an elegant, declarative API

Further Reading 📚


Share this post now:

💬 Comments (0)

Login to comment

Advertisement

Flaunt You Expertise/Knowledge & Help your Peers

Sharing your knowledge will strengthen your expertise on topic. Consider writing a quick Blog/Notes to help frontend folks to ace Frontend Interviews.

Advertisement


Other Related Blogs

Master Hoisting in JavaScript with 5 Examples

Alok Kumar Giri

Last Updated Jun 2, 2025

Code snippet examples which will help to grasp the concept of Hoisting in JavaScript, with solutions to understand how it works behind the scene.

Polyfill for map, filter, and reduce in JavaScript

Anuj Sharma

Last Updated Oct 2, 2025

Explore Polyfill for map, filter and reduce array methods in JavaScript. A detailed explanation of Map, filter and reduce polyfills in JS helps you to know the internal working of these array methods.

setTimeout Polyfill in JavaScript - Detailed Explanation

Anuj Sharma

Last Updated Aug 3, 2025

Explore the implementation of setTimeout in JavaScript with a detailed explanation for every step. Understand all scenarios expected to implement the setTimeout polyfill.

How does JWT (JSON Web Token) Authentication work - Pros & Cons

Frontendgeek

Last Updated Sep 25, 2025

Understand the JWT(JSON Web Token) and how JWT decode works. It also covers how the end-to-end JWT authentication works between client & server, along with the pros and cons of using JWT.

Polyfill for Async Await in JavaScript - Step by Step Explanation

Anuj Sharma

Last Updated Oct 4, 2025

Understand polyfill for Async Await in JavaScript with a step-by-step explanation. This helps in understanding the internal functioning of Async Await in JavaScript.

Hoisting in JavaScript Explained with Examples

Anuj Sharma

Last Updated Sep 14, 2025

Learn hoisting in JavaScript with clear examples and explanations. Understand variable hoisting in JavaScript, function hoisting in JavaScript, and how the temporal dead zone affects hoisting in JS.

Stay Updated

Subscribe to FrontendGeek Hub for the frontend interview preparation, interview experiences, curated resources and roadmaps.

FrontendGeek
FrontendGeek

© 2024 FrontendGeek. All rights reserved