How To Navigate In JavaScript

Aug 13, 2026·15 min read

How To Navigate In JavaScript

Choose based on what should change: use full-page navigation when the browser should load another document, same-document navigation when the current document stays loaded, and SPA interception when the application should render a new view itself.

You click a link to /reports, the address changes, and a document arrives. Then a tab inside that page changes the URL without loading anything. Both actions are navigation, but they do different work.

JavaScript gives you several ways to start or manage that change. This article walks through full-document navigation, same-document navigation, ordinary links, and the newer Navigation API.

The quick answer: which JavaScript navigation method do you need?

Navigation falls into three categories.

Full-document navigation loads another document. Use a link or the Location object when the destination is a page that should load normally.

Same-document navigation changes the URL and browser history while retaining the current document. Use the History API when your code can render the new view inside the existing page.

SPA navigation is application-controlled view changes built on same-document navigation. A single-page application decides which content to render and usually responds to Back and Forward itself.

Does the browser loada new document?yesnoFull-document<a href>location.assign()location.replace()location.reload()Same-documentpushState()replaceState()back() / forward()event.intercept()the browserrendersyour coderenders
Every navigation choice starts with one question: does the document get replaced?

Use this decision table:

You need to…Reach for…What happens
Load another document and keep the current page in Back historylocation.href, window.location, or location.assign()Full-document navigation
Load another document without keeping the replaced entrylocation.replace()Full-document navigation
Change the URL without loading a documenthistory.pushState()New same-document history entry
Change the current URL without adding an entryhistory.replaceState()Current same-document entry changes
Move through existing historyhistory.back() or history.forward()The browser traverses history
Manage application navigation with a newer APInavigation.navigate()Navigation API navigation, with promises for its stages

Start with an ordinary link when a link expresses the action. It gives you browser navigation, bookmarking, refresh, keyboard access, and the option to open the destination in another tab. JavaScript enters when you need a programmatic destination or an application-owned view.

The Browser environment explains the browser objects around these APIs. The next sections keep one distinction in view: changing the document is not the same as changing the URL.

The window.location property returns a Location object describing the current document location. The global location name refers to the same browser location in ordinary page code.

Assigning a URL to location.href, assigning a URL to window.location, and calling location.assign() all choose a new document:

location.href = "/reports";
window.location = "/reports";
location.assign("/reports");

Each form starts full-document navigation. The browser leaves the current document and requests /reports.

assign() makes the action explicit when the destination comes from a variable:

const nextPage = "/reports?month=august";

location.assign(nextPage);

The current page remains available through the Back button after this kind of navigation. That is the normal choice when a user moves from one page to another and should be able to return.

location.replace() chooses a new document while replacing the current history entry:

location.replace("/signed-out");

The destination loads as a document, but the replaced entry is not preserved for the Back button. This suits a transition where returning to the current page would be wrong, such as leaving a page that contains an expired session.

That differs from history.replaceState(). The Location method performs document navigation. The History API method changes the current URL and state without loading the supplied URL as a document.

To reload the current document, call location.reload():

location.reload();

A reload keeps the current URL and requests the current document again. It does not mean “render a different view in this page.” It means the document starts its loading process again.

A small function can choose between keeping and replacing the current entry:

function leaveAccount({ expired }) {
  if (expired) {
    location.replace("/sign-in");
    return;
  }

  location.assign("/account");
}

When expired is true, the sign-in page replaces the current entry. When it is false, /account becomes a new document while the previous page remains in history.

For a user-initiated page change, an ordinary link still carries the clearest meaning. JavaScript is useful when a condition decides the destination, when code starts the action after another operation, or when you need to redirect from application logic.

Change the URL without reloading with the History API

Same-document navigation keeps the current document in place. The URL and history change, but your code must decide what the new view contains.

history.pushState() creates and activates a new history entry without loading the new URL as a document:

history.pushState(
  { section: "billing" },
  "",
  "/settings?section=billing"
);

The browser now has a new entry for /settings?section=billing. The document does not reload, and pushState() does not render the billing view for you.

That last part is the division of labour:

const views = {
  profile: "Your profile",
  billing: "Your billing details",
};

function render(section) {
  const title = views[section] ?? views.profile;
  document.querySelector("#app").textContent = title;
}

function showSection(section) {
  const url = `/settings?section=${encodeURIComponent(section)}`;

  history.pushState({ section }, "", url);
  render(section);
}

showSection("billing") changes the address and then updates the visible content. The History API owns the entry. Your application owns the rendering.

The Back button needs a listener. When the browser moves to another same-document history entry, the popstate event gives your code a chance to read the current URL and render it:

function sectionFromUrl() {
  const params = new URLSearchParams(location.search);
  return params.get("section") ?? "profile";
}

window.addEventListener("popstate", () => {
  render(sectionFromUrl());
});

render(sectionFromUrl());

The final call handles the initial page load. The listener handles later Back and Forward actions. pushState() itself does not render a page or fire popstate.

tab clickBack / ForwardpushState()URL changes onlypopstate firesbrowser tells youyou must callrenderrender reads the URL, not the click
pushState never calls render for you; popstate is the only path the browser triggers.

A complete small view switcher looks like this:

const views = {
  profile: "Your profile",
  billing: "Your billing details",
  alerts: "Your alerts",
};

const app = document.querySelector("#app") ?? document.body.appendChild(document.createElement("div"));

function sectionFromUrl() {
  return new URLSearchParams(location.search).get("section") ?? "profile";
}

function render() {
  const section = sectionFromUrl();
  app.textContent = views[section] ?? views.profile;
}

function navigateToSection(section) {
  const url = new URL(location.href);
  url.searchParams.set("section", section);

  history.pushState({ section }, "", url);
  render();
}

window.addEventListener("popstate", render);
render();

The current URL is the source for the rendered section. The state object can carry related data, but reading the URL makes refresh and shared links easier to reason about.

Use history.replaceState() when the current entry needs a different URL or state and no new Back-button stop should appear:

const url = new URL(location.href);
url.searchParams.set("section", "profile");

history.replaceState({ section: "profile" }, "", url);
render();

This changes the current entry without loading the supplied URL. It is useful for normalising an initial URL or updating application state without adding another step to history.

The traversal methods move through entries that already exist:

history.back();
history.forward();

history.back() asks the browser to move one step backward. history.forward() asks it to move one step forward. Your popstate listener is where the application responds by reading the resulting URL and rendering the matching view.

The View Transitions API can add a visual transition around a view change, but it does not replace the history decision. First choose whether the change should create, replace, or traverse a history entry.

An ordinary link is the default navigation mechanism because it already describes a destination. It works with keyboard input, browser history, bookmarks, refresh, and open-in-new-tab actions.

Use JavaScript to enhance a link when the application needs to render a same-document view. Keep the destination in the link itself, then intercept only the cases your application owns:

document.addEventListener("click", (event) => {
  const link = event.target.closest("a");

  if (!link || link.origin !== location.origin) {
    return;
  }

  if (link.download || event.button !== 0 || event.metaKey || event.ctrlKey) {
    return;
  }

  event.preventDefault();

  const url = new URL(link.href);
  history.pushState({}, "", url);
  render();
});

The handler leaves external links, downloads, modified clicks, and non-primary mouse actions alone. Those cases belong to the browser.

A fragment identifies a position or view within a document:

location.href = "#billing";

A fragment URL can move the browser to an element with the matching identifier. It can also act as a small view signal when your code reads location.hash. The document remains the same.

Query parameters describe values in the URL:

const url = new URL(location.href);
url.searchParams.set("page", "2");

history.pushState({}, "", url);
render();

URL keeps URL construction structured, and URLSearchParams reads and writes query parameters without manual string splitting. The resulting URL can be bookmarked and refreshed.

A link with a query string is still a link. If the destination should load a separate document, let the browser follow it. If the destination is an application view, use the same URL with pushState() and render from its parameters.

The DOM navigation guide covers the document side of navigation. The important boundary here is practical: do not replace a link with a button when the action is actually “go to this URL.”

The Navigation API: navigate() and NavigateEvent

The Navigation API is a newer browser API for initiating, inspecting, intercepting, and managing browser navigation actions. It is accessed through window.navigation, which returns the current window’s Navigation object.

It can represent more navigation actions in one model than the older Location and History APIs. That does not make it a universal replacement. Ordinary links remain the fallback, and location.assign() remains a clear choice for full-document navigation.

Feature-detect the API before using it:

function goToReports() {
  if (window.navigation) {
    window.navigation.navigate("/reports");
    return;
  }

  location.assign("/reports");
}

navigation.navigate() accepts a URL and an optional options object. The options can contain state, info, and a history value such as "auto", "push", or "replace":

const result = window.navigation.navigate("/settings?section=billing", {
  state: { section: "billing" },
  info: { source: "settings-tab" },
  history: "push",
});

The method returns two promises. result.committed fulfills when the visible URL changes. result.finished fulfills after intercepted navigation handlers complete.

const result = window.navigation.navigate("/reports");

result.committed.then(() => {
  console.log("the URL changed");
});

result.finished.then(() => {
  console.log("navigation work finished");
});

The two stages answer different questions. The committed promise tells you that the address has changed. The finished promise waits for work attached to the navigation to complete.

The navigate event fires when any type of navigation is initiated. Its NavigateEvent contains information about the destination and provides methods for intercepting and controlling the navigation.

A handler can inspect the destination before deciding whether the application owns it:

function renderRoute(url) {
  console.log("Render", url.pathname);
}

window.navigation?.addEventListener("navigate", (event) => {
  const destination = event.destination;

  if (!destination.url.startsWith(location.origin)) {
    return;
  }

  if (!event.canIntercept) {
    return;
  }

  event.intercept({
    handler() {
      renderRoute(new URL(destination.url));
    },
  });
});

event.destination describes where the navigation is going. event.canIntercept tells you whether this navigation can be intercepted. The handler returns without changing anything for a destination the application does not own.

intercept() turns the navigation into a same-document navigation and allows custom handling. Your handler can render the new view instead of allowing the browser to load another document. It can also control focus resetting and scrolling through the options supported by the event.

A route handler can use the destination URL as its only input:

function renderRoute(url) {
  const route = `${url.pathname}${url.search}${url.hash}`;

  document.querySelector("#app").textContent =
    route === "/reports" ? "Reports" : "Unknown route";
}

window.navigation?.addEventListener("navigate", (event) => {
  if (!event.canIntercept) {
    return;
  }

  const url = new URL(event.destination.url);

  if (url.origin !== location.origin) {
    return;
  }

  event.intercept({
    scroll: "manual",
    handler() {
      renderRoute(url);
    },
  });
});

The scroll option expresses how scrolling should be handled during the intercepted navigation. Focus handling can be controlled in the same navigation options when the application needs to place focus deliberately after rendering.

The Navigation API also exposes the application’s navigation history entries. That gives an application a model for examining and managing navigation history beyond calling one History method at a time.

Support is the practical limit. MDN currently labels the Navigation API, navigation.navigate(), and NavigateEvent as Baseline 2026 features newly available across the latest devices and browser versions since January 2026, while warning that older devices and browsers may not support them. Keep the feature check and a working fallback in the same route action.

The Navigation API guide covers the platform API in its own place. For a beginner route, the order is stable: use links first, use Location for another document, use the History API for established same-document views, and add the Navigation API when its event model solves a problem your application actually has.

Common navigation bugs and compatibility rules

Same-document APIs do not turn an external site into part of your application. A client-rendered route can handle URLs your application owns, but external destinations and downloads should remain browser actions.

A server also needs to know what to return for a URL that users can refresh or open directly. If your application renders /reports in the browser but the server does not return the application document for that path, a refresh can fail before your JavaScript runs. Design the server fallback and the client route together.

The first load needs separate handling with the Navigation API. It does not currently fire a navigate event for the page’s initial load, so render the initial URL during startup:

function start() {
  renderRoute(new URL(location.href));
}

start();

window.navigation?.addEventListener("navigate", handleNavigation);

Do not assume that pushState() renders anything. It changes the entry. Your code must update the view and listen for popstate.

Do not confuse the two replacement methods:

  • location.replace() loads another document and does not preserve the replaced entry.
  • history.replaceState() changes the current URL and state without loading a document.

Do not intercept every click. Preserve downloads, external links, modified clicks, and browser actions that users expect to keep. A link remains the fallback when the Navigation API is unavailable.

State also needs a clear boundary. Put durable route information in the URL when users should be able to bookmark, refresh, or share it. Use history state for related data your application needs while moving between entries.

If you need the complete browser material, Part 4: The Browser Platform follows these APIs alongside the rest of the page environment. Navigation is one part of that system.

The first rule is still the most useful: start with a link. Then choose Location, the History API, or the Navigation API according to whether you are loading a document, retaining the document, or intercepting an application route.

Frequently asked questions

How do you navigate to another page in JavaScript?
Assign a URL to location.href, assign to window.location, or call location.assign(). Each performs full-document navigation and loads the destination as a new document.
How do you change the URL without reloading the page?
Call history.pushState() to create and activate a same-document history entry, then render the view yourself. Use the popstate event to respond when the user moves through those entries with the Back or Forward button.
What is the difference between location.replace() and history.replaceState()?
location.replace() navigates to another document and does not preserve the replaced entry for the Back button. history.replaceState() changes the current URL and state without loading a new document.
Should you use navigation.navigate() instead of location?
Not everywhere. The Navigation API is newer and older browsers may not support it, so detect window.navigation and keep a fallback such as an ordinary link or location.assign().
Does the Navigation API handle the first page load?
The Navigation API does not currently fire a navigate event on a page's first load. A client-rendered application needs a separate initialization step for the initial URL.