Skip to Content
Course content
Click on the "Edit" button in the top corner of the screen to edit your slide content.

I see this all the time when developers start building Single Page Applications: they assume that calling history.pushState() is just a fancy way of telling the browser to "go to this page." They expect the browser to perform a navigation, fetch the new document from the server, and then run some JavaScript on the new page.

pushState doesn't actually "navigate"

Here is the crucial distinction: history.pushState() updates the URL in the address bar and adds an entry to the browser's session history, but it does not cause the browser to load a new page.

If you run window.history.pushState({page: 1}, "title", "/settings"); in your console right now, you'll see the URL change to /settings. But notice that the page didn't blink. No network request was sent to the server for a "settings" HTML file. Your current JavaScript state is still alive and well. If you had a counter running on the screen, it wouldn't reset. This is the "magic" that allows modern apps to feel like desktop software rather than a series of linked documents.

Managing the stack without the blink

Since the browser isn't doing the heavy lifting of loading a new page, you are now responsible for the UI. When you call pushState, you're essentially lying to the browser, saying, "We've moved to a new place," while you secretly stay on the same page. You then have to manually update the DOM to reflect that change.

Let's say you're building a photo gallery. When a user clicks a thumbnail, you don't want a full page reload (which is slow and jarring). Instead, you do this:

const openPhoto = (photoId) => {
  // 1. Update the URL so the user can bookmark this specific photo
  const url = `/photo/${photoId}`;
  window.history.pushState({ id: photoId }, `Photo ${photoId}`, url);

  // 2. Manually update the UI to show the photo
  renderPhotoModal(photoId);
};

I'll also mention history.replaceState(). It does the exact same thing as pushState, but instead of adding a new entry to the history stack, it overwrites the current one. This is incredibly useful for things like search filters. If a user changes a "sort by" dropdown ten times, you probably don't want them to have to click the "Back" button ten times just to get back to the previous page. You'd use replaceState to keep the URL current without bloating the history.

Handling the "Back" button with popstate

Here is where most people get tripped up. Since pushState doesn't trigger a reload, the browser won't automatically "go back" to the previous state when the user hits the Back button; it will just change the URL back to the previous one. The page content will stay exactly as it was unless you're listening for it.

To fix this, you use the popstate event. This event fires whenever the user navigates through their session history (like clicking Back or Forward).

window.addEventListener('popstate', (event) => {
  // The 'event.state' object is whatever we passed in as the first 
  // argument to pushState or replaceState.
  if (event.state && event.state.id) {
    renderPhotoModal(event.state.id);
  } else {
    closePhotoModal();
  }
});

It's a bit of a mental shift. You aren't reacting to "pages" anymore; you're reacting to "state changes" that happen to be mirrored in the URL.




📋 Practical Task

Build a Client-Side Tab System with URL Syncing

Your goal is to create a simple tabbed interface where switching tabs updates the URL without refreshing the page, and clicking the browser's "Back" button reverts the active tab.

  • Create three buttons (tabs): "Home", "About", and "Contact".
  • Create a content <div> that displays different text based on the active tab.
  • When a tab is clicked, use history.pushState to update the URL to /home, /about, or /contact.
  • Ensure the content <div> updates immediately when a tab is clicked.
  • Implement a popstate event listener so that when the user clicks the browser's Back button, the UI switches back to the previous tab's content.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.