The JavaScript history.pushState() method
The history.pushState() method changes the URL shown in the address bar and adds a history entry, all without reloading the page. history.pushState(state, "", "/new-path"). It's the foundation of client-side routing in single-page apps. Listen for the popstate event to react when the user presses Back.
Overview
history.pushState() lets JavaScript update the URL without a full page load. It takes a state object (data you want associated with this history entry), a title argument (ignored by browsers — pass ""), and the new URL. The address bar updates and a new entry is added to the browser's history, but the page doesn't reload — your code is responsible for updating what's shown.
This is the engine behind client-side routing in single-page apps (React Router, Vue Router and friends all build on it). When the user clicks an in-app link, you prevent the default navigation, call pushState() to update the URL, and swap the content with JavaScript — giving real URLs and a working Back button without server round-trips.
The crucial partner is the popstate event: when the user presses Back or Forward, the browser fires popstate (with your saved state) so you can render the right view for the new URL. Note that pushState() itself does not fire popstate — only user navigation does. Its sibling replaceState() updates the current entry instead of adding a new one (useful for redirects or filter changes you don't want in history).
Syntax
history.pushState(state, "", url);
history.pushState({ page: 2 }, "", "/products?page=2");
// react to Back/Forward
window.addEventListener("popstate", (event) => {
render(event.state); // event.state is what you pushed
});
history.replaceState(state, "", url); // replace, don't add
Parameters
The history.pushState() method accepts the following parameters.
| Parameter | Description |
|---|---|
state |
A data object stored with the history entry (available later as event.state on popstate). |
unused |
A title argument that browsers ignore — pass an empty string. |
url |
The new URL to show. Must be the same origin as the current page. |
Example
// minimal client-side routing
function navigate(url, state) {
history.pushState(state, '', url); // change URL, no reload
render(state);
}
window.addEventListener('popstate', (e) => {
render(e.state); // user pressed Back/Forward
});
document.querySelector('a.nav').addEventListener('click', (e) => {
e.preventDefault();
navigate(e.target.href, { view: 'about' });
});
Best practices
- Pair it with a
popstatelistener so Back/Forward render the right view. - Pass
""for the title argument — browsers ignore it. - Store what you need to re-render in the
stateobject. - Use
replaceState()for changes that shouldn't add a new history entry (redirects, filter tweaks).
Frequently asked questions
How do I change the URL without reloading the page?
history.pushState(state, "", "/new-url"). It updates the address bar and adds a history entry with no reload.How do I handle the Back button with pushState?
popstate event on window — it fires on Back/Forward and gives you the saved state to re-render.What is the difference between pushState() and replaceState()?
pushState() adds a new history entry; replaceState() modifies the current one without adding to history.Does pushState() trigger popstate?
popstate. After pushState() you update the view yourself.