Signatur
Beschreibung
history.pushState() legt einen neuen Eintrag auf den Verlaufsstapel der aktuellen Sitzung. Die Adressleiste zeigt danach die neue URL an, der Browser lädt aber nichts nach und prüft auch nicht, ob die URL auf dem Server existiert. Das ist die Grundlage für clientseitiges Routing in Single-Page-Applications (SPA): Die Ansicht wechselt per JavaScript, die URL bleibt trotzdem teilbar und als Lesezeichen nutzbar.
Über den Parameter state kann man jedem Eintrag ein serialisierbares Objekt mitgeben. Wenn der Nutzer später mit Zurück/Vor navigiert, löst der Browser das Ereignis popstate aus. Dessen Eigenschaft event.state enthält eine Kopie dieses Objekts, mit der die Anwendung die passende Ansicht wiederherstellt. Den Zustand des aktuellen Eintrags liest man jederzeit über history.state.
Im Unterschied zu history.replaceState(), das den aktuellen Eintrag überschreibt, erzeugt pushState() immer einen zusätzlichen Eintrag. Alle „Vorwärts“-Einträge hinter der aktuellen Position werden dabei verworfen. Verwende pushState() für echte Navigationsschritte, etwa beim Wechsel zu einer anderen Seite der App. Für kleine Zustandsänderungen wie Filter oder Scroll-Position ist replaceState() meist besser, damit der Verlauf nicht mit Einträgen zugemüllt wird.
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $state Pflicht | any (strukturiert klonbar) | null | Beliebiges Objekt, das dem neuen Eintrag zugeordnet wird. Es wird mit dem Structured-Clone-Algorithmus gespeichert, deshalb sind Funktionen, DOM-Knoten und Ähnliches nicht erlaubt. Wenn du keinen Zustand brauchst, übergib null. |
|
| $unused Pflicht | string | "" | Früher als Titel gedacht, wird aber von allen Browsern ignoriert. Aus Kompatibilitätsgründen trotzdem angeben, üblich ist ein leerer String "". |
| $url | string | URL | aktuelle URL | Neue URL des Eintrags, relativ oder absolut. Sie muss dieselbe Origin haben wie das aktuelle Dokument, sonst wird ein SecurityError geworfen. Wenn du sie weglässt, bleibt die aktuelle URL erhalten. |
Rückgabewert
undefined zurück.Beispiele
Einfacher Verlaufseintrag mit Zustand
// Aktuelle Seite: https://example.com/
history.pushState({ page: 2 }, "", "/produkte?seite=2");
console.log(location.pathname + location.search);
console.log(history.state);
Minimaler SPA-Router mit popstate
const app = document.querySelector("#app");
const render = (route) => {
app.textContent = `Ansicht: ${route}`;
};
// Interne Links abfangen und per pushState navigieren
document.addEventListener("click", (event) => {
const link = event.target.closest("a[data-route]");
if (!link) return;
event.preventDefault();
const route = link.dataset.route;
history.pushState({ route }, "", link.href);
render(route); // pushState löst KEIN popstate aus!
});
// Zurück/Vor-Buttons des Browsers behandeln
window.addEventListener("popstate", (event) => {
render(event.state?.route ?? "start");
});
// Startzustand für den ersten Eintrag setzen
history.replaceState({ route: "start" }, "", location.href);
render("start");
Zustand eines Detail-Dialogs in der URL festhalten
const openProduct = async (id) => {
const res = await fetch(`/api/produkte/${id}`);
const product = await res.json();
showDialog(product);
history.pushState({ dialog: "produkt", id }, "", `/produkte/${id}`);
};
const showDialog = (product) => {
console.log(`Dialog geöffnet: ${product.name}`);
};
window.addEventListener("popstate", (event) => {
if (event.state?.dialog !== "produkt") {
console.log("Dialog geschlossen");
}
});
SecurityError bei fremder Origin abfangen
try {
history.pushState(null, "", "https://andere-domain.de/seite");
} catch (err) {
console.log(err.name);
}
// Wichtig · Fallstricke
- Kein
popstatebeim Aufruf:pushState()selbst löstpopstatenicht aus. Die Ansicht musst du direkt nach dem Aufruf selbst aktualisieren.popstatekommt nur bei Zurück/Vor, beihistory.back()/go()und Ähnlichem. - Server-Konfiguration: Lädt der Nutzer eine per
pushState()gesetzte URL neu, fragt der Browser genau diese URL beim Server an. Der Server muss dann die App ausliefern (Fallback aufindex.html), sonst gibt es einen 404-Fehler. - Same-Origin-Pflicht: Eine URL mit anderem Protokoll, anderem Host oder anderem Port wirft einen
SecurityError. Beifile://-URLs verhalten sich die Browser unterschiedlich restriktiv. - Größe des Zustands: Das
state-Objekt wird serialisiert und kann auf der Festplatte gespeichert werden. Firefox begrenzt es auf etwa 16 MiB. Speichere dort nur IDs oder kleine Daten, keine großen Datenmengen und keine sensiblen Daten wie Tokens. - Nicht klonbare Werte wie Funktionen oder DOM-Elemente führen zu einem
DataCloneError. - Drosselung: Browser begrenzen sehr häufige Aufrufe, Safari und Chrome z. B. auf ca. 100 Aufrufe in 10 Sekunden. Für Scroll- oder Eingabe-Ereignisse besser gedrosselt
replaceState()verwenden. - Nur ein Wechsel des Fragments (
#abc) perpushState()löst keinhashchangeaus. - Sicherheit: Werte aus
event.stateoderlocationniemals ungeprüft perinnerHTMLausgeben, denn URLs sind vom Nutzer manipulierbar (XSS-Gefahr). - Wird von allen modernen Browsern unterstützt. Für neue Projekte in Chromium-Browsern lohnt ein Blick auf die modernere
Navigation API.