The Browser Navigation Stack
Every browser tab maintains a session history. This history is structured as a stack with a pointer to the current entry. Each entry represents a page visit and holds the URL, document state, and an optional state object.
When you navigate to a new URL, the browser pushes a new entry onto the history stack and moves the pointer forward. The stack grows as you visit more pages. When you close the tab, the entire history stack is destroyed.
The history stack is per-tab. Each tab has its own independent stack. Opening a link in a new tab creates a new stack.
How Forward Works
When you click the forward button, the browser takes the most recently popped entry and pushes it back onto the history stack, moving the pointer forward by one. This is the redo counterpart to the undo of the back button.
The forward button is only available when you have gone back at least once. If you visit a new page after going back, the forward entries are discarded (just like how undo history is cleared when you make a new change in a text editor).
pushState and replaceState
The History API lets you manipulate the browser stack from JavaScript without reloading the page. This is critical for single-page applications (SPAs).
history.pushState(state, title, url) adds a new entry to the history stack. The page does not reload. The URL bar updates to show the new URL. You can store a state object that the browser will pass back to you when the user navigates to this entry.
history.replaceState(state, title, url) replaces the current entry in the history stack instead of adding a new one. Use this when you want to update the URL without creating a new back-button entry.
// Add a new history entry
history.pushState({ page: 'settings' }, '', '/settings');
// Replace the current entry
history.replaceState({ page: 'home' }, '', '/');
popstate Event
The popstate event fires when the user navigates using the back or forward button, or when history.back(), history.forward(), or history.go() is called.
The event object has a state property containing the state object that was passed to pushState for that entry. If the entry was created by a normal page load (not pushState), the state is null.
window.addEventListener('popstate', function(event) {
if (event.state) {
renderPage(event.state.page);
}
});
Note: popstate does not fire on the initial page load. Use the load event or check history.state on startup.
Session History vs Page State
The session history tracks which URLs you visited. The state object attached to each entry tracks application-specific data. They are separate concerns.
A common pattern is to store the current view, scroll position, or form data in the state object so that when the user returns via the back button, the application can restore its previous state. Without state objects, the browser only knows the URL, not what your app was displaying.
Common Pitfalls
Broken Back Button
The most common mistake in SPAs is not calling pushState on navigation. If the URL never changes, the back button takes the user out of the app entirely instead of to the previous view.
Not Handling popstate
If you listen for popstate but do not update the view, the URL changes but the content does not. Always pair pushState with a popstate handler.
Using hashchange Without pushState
Hash-based routing (#/page) works but produces ugly URLs. Modern SPAs should prefer pushState for clean URLs. Hash routing is a fallback for older browsers.
Forgetting Initial State
popstate does not fire on page load. You must handle the initial route separately by checking location.pathname and history.state when the app starts.
Frequently Asked Questions
What stack does the browser back button use?
The browser maintains a session history stack. Each time you visit a new page, it is pushed onto the stack. When you click back, the current entry is popped and the previous entry is loaded. The forward button uses a separate redo stack.
What is pushState?
pushState is a History API method that adds a new entry to the session history without reloading the page. It takes a state object, a title, and an optional URL. This is essential for single-page applications that need URL changes without full page reloads.
What is the popstate event?
The popstate event fires when the user clicks the back or forward button, or when history.back() or history.forward() is called. The event's state property contains the state object that was passed to pushState for that history entry.
How do I fix a broken back button in a single-page app?
Use the History API (pushState, replaceState, popstate) to manage navigation state. Each view transition should push a new history entry so the back button works naturally. Alternatively, use hash-based routing with hashchange events.