How to change the URL without reloading the page?
Change the URL without a reload using history.pushState and replaceState, update query parameters, handle the back button and use your framework's router.
Use the History API. history.pushState() changes the URL and adds a new entry to the browser history. history.replaceState() changes the URL of the current entry instead. Neither reloads the page.
// New entry: the back button returns to the previous URL
history.pushState({ productId: 42 }, '', '/products/42');
// Same entry: the back button skips this change
history.replaceState(null, '', '/products?sort=price');The three arguments are:
- a state object with data you want back later, or
null; - a title, which is unused for historical reasons, so pass an empty string;
- the new URL, absolute or relative.
The URL must stay on the same origin. Pushing a URL on another domain throws a SecurityError.
Use pushState for changes the user expects the back button to undo, like opening a product or a tab with different content. Use replaceState for small changes like sorting, filters or a search query as you type, so the history isn't full of tiny steps.
Update a query parameter
Change query parameters with the URL object instead of gluing strings together:
// Current URL: https://shop.example/products?sort=price&highlight=7
const url = new URL(location.href);
url.searchParams.set('page', '2');
url.searchParams.delete('highlight');
console.log(url.href);
// https://shop.example/products?sort=price&page=2
history.replaceState(null, '', url);searchParams also takes care of encoding: set('q', 'red shoes') gives ?q=red+shoes.
Handle the back and forward buttons
When the user goes back or forward between entries you created, the page doesn't reload either. The browser fires popstate on window, and it's up to you to show the matching content:
window.addEventListener('popstate', () => {
const params = new URLSearchParams(location.search);
showPage(Number(params.get('page') ?? 1));
});event.state holds the object you passed to pushState, but it's null for the entry the user originally loaded, so reading the URL itself is often simpler. popstate doesn't fire when you call pushState or replaceState yourself, so update the page right after you call them.
Changing only the hash
Setting location.hash doesn't reload the page either. It adds a history entry, fires hashchange, and scrolls to the element with that id if there is one:
location.hash = 'reviews'; // the URL now ends with #reviewsIf you want the hash in the URL without the jump, use history.pushState(null, '', '#reviews'), which doesn't scroll.
Make the URL work on refresh
pushState only changes what the address bar shows. When someone refreshes the page or opens a shared link like /products/42, the browser requests that URL from the server, so the server has to return the right page for it. For a single-page app, that usually means a rewrite that serves index.html for all app routes.
On page load, read the URL and render the matching state, such as the page number, filters or open tab. See how to get the current URL in JavaScript for reading its parts.
In React, Next.js or Vue, use the router
Frameworks keep their own record of the current route. Calling history.pushState behind the router's back can leave its hooks and links out of sync. Use the router's push and replace instead. In the Next.js App Router:
'use client';
import {
usePathname,
useRouter,
useSearchParams,
} from 'next/navigation';
export function Pagination() {
const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();
function goToPage(page) {
const params = new URLSearchParams(searchParams.toString());
params.set('page', String(page));
router.replace(`${pathname}?${params}`, { scroll: false });
}
return <button onClick={() => goToPage(2)}>Page 2</button>;
}The Next.js App Router also syncs with direct window.history.pushState and replaceState calls, but don't assume other routers do. React Router has useNavigate and useSearchParams, and Vue Router has router.push and router.replace.