SKU // 127

JS History

The history object is the browser's back-and-forward list for the current tab. Your code can move through it, and it can add its own entries without reloading the page, which is how web apps change the address and keep the Back button working. This lesson covers moving through the list, pushState, replaceState, and the popstate event.

// The Back and Forward List

Every page the tab visits is an entry in a list, and the browser sits at one position in it. For privacy, your code cannot read the addresses in the list. It can only count the entries and move.

Property or method Does
history.lengthhow many entries the list has
history.back()go back one entry (same as the Back button)
history.forward()go forward one entry
history.go(-2)move by a number of entries: negative is back, positive is forward
history.statethe data attached to the current entry (or null)
history.back();      // one page back
history.go(-2);      // two pages back
history.go(0);       // reload the current page

Moving past either end of the list does nothing. These calls are asynchronous: the move happens a moment after the call, so code on the next line still runs on the current page.

console.log(typeof history.length, history.length >= 1);
console.log(history.state);
console.log(history.scrollRestoration);
number true null auto

// Adding Entries: pushState

history.pushState(state, "", url) adds a new entry without loading anything. The page does not reload, but the address changes and the Back button now has somewhere to go. The three arguments:

const before = history.length;
history.pushState({ page: 2 }, "");

console.log(history.length === before + 1);
console.log(history.state);
true {page: 2}
// on a page of your own: change the address too
history.pushState({ page: 2 }, "", "/lessons/page-2.html");

Run the second example on a real page. In the Try It editor the page lives in a frame, where changing the address is blocked, so the first example leaves the URL out.

// Changing the Current Entry: replaceState

history.replaceState takes the same arguments but rewrites the current entry instead of adding one. The list does not grow. It is for fixing up the address or state of the page you are already on:

const before = history.length;
history.replaceState({ filter: "active" }, "");

console.log(history.length === before);
console.log(history.state);
true {filter: "active"}

// The popstate Event

When the user presses Back or Forward and lands on an entry created by pushState, the window fires a "popstate" event. event.state is the data you saved. This is how a page knows to redraw itself for the entry the user went back to:

window.addEventListener("popstate", function (event) {
  console.log("went to", event.state);
  // redraw the page for event.state here
});
pushState does not fire popstate
The event fires only for Back, Forward, and history.go(). When your own code calls pushState you already know what changed, so you update the page yourself.

// A Tiny Page Navigator

This page keeps a "page number" in the history. The buttons add entries, and the browser's Back and Forward move between them. Try it, then press Back:

<h3 id="view"></h3>
<button id="next">Next page</button>

<script>
  let page = 1;
  function show() { document.getElementById("view").textContent = "Page " + page; }

  history.replaceState({ page: 1 }, "");
  show();

  document.getElementById("next").addEventListener("click", function () {
    page++;
    history.pushState({ page: page }, "");
    show();
  });

  window.addEventListener("popstate", function (event) {
    page = event.state ? event.state.page : 1;
    show();
  });
</script>

Single-page apps work this way: one real page, with pushState and popstate making it behave like many pages.

Do not trap people
Never push entries just to stop the Back button from leaving. Pages that do it are frustrating, and browsers have started to skip such entries on purpose.

// Quick Check

Which method adds a new entry to the history list without reloading the page?

  1. history.back()
  2. history.pushState()
  3. history.replaceState()
  4. location.reload()
Show answer
B. pushState adds an entry. replaceState rewrites the current one, and the others move or reload.

What does this log (assume history.length starts at 3)?

history.pushState({}, "");
history.replaceState({}, "");
console.log(history.length);
Show answer
4. pushState grew the list from 3 to 4. replaceState changed the current entry in place, so the length stayed at 4.

When does the popstate event fire?

Show answer
When the user navigates with Back or Forward (or code calls history.go) onto a history entry. It does not fire when your own code calls pushState or replaceState.

// Try It

  1. Click Open Playground and run it. Click the button twice, then press the browser's Back button
  2. Change the Playground so each entry saves a name as well as a number, and show both
  3. Use history.go(-2) after pushing three entries. Where do you land?
  4. Explain in your notes why replaceState does not change history.length
// Video Walkthroughs
Last updated: Oct 11, 2026, 6:56 PM ET