W3docs

JavaScript History API

Erfahren Sie, wie die JavaScript History API die Browser-History manipuliert und SPAs ohne Seitenneuladen ermöglicht.

Einführung in die JavaScript History API

In der modernen Webentwicklung erfordert das Schaffen nahtloser Benutzererfahrungen häufig das Manipulieren der Browser-History. Die JavaScript History API ermöglicht es Ihnen, die Sitzungshistorie des Browsers zu lesen und zu ändern — also die Liste der Seiten (und URL-Zustände), die der Benutzer im aktuellen Tab besucht hat. Mit dieser API können Entwickler die Adressleiste und den Vor-/Zurück-Stapel aktualisieren, ohne einen vollständigen Seitenneuladevorgang auszulösen — genau das, was Single-Page-Anwendungen (SPAs) benötigen, um schnell und nativ zu wirken.

Diese Seite behandelt die drei Kernmethoden (pushState, replaceState und die Navigations-Hilfsmethoden), wie man auf das popstate-Ereignis reagiert, typische Fallstricke sowie Best Practices für den produktiven Einsatz der API.

Warum die History API existiert

Vor dieser API war die einzige Möglichkeit, die URL zu ändern, die Navigation, die das gesamte Dokument neu lud. SPAs rendern neue „Seiten" mit JavaScript, daher muss die URL synchron bleiben, ohne Neuladen — andernfalls sind der Zurück-Button, Lesezeichen und teilbare Links funktionslos.

Die History API löst dieses Problem, indem sie Ihnen folgende Möglichkeiten bietet:

  • Hinzufügen eines neuen Eintrags zum Vor-/Zurück-Stapel (pushState).
  • Ändern des aktuellen Eintrags an Ort und Stelle (replaceState).
  • Reagieren wenn der Benutzer sich mit den Vor-/Zurück-Tasten durch den Stapel bewegt (popstate).

Den Datenzustand des aktuellen Eintrags lesen Sie über die schreibgeschützte Eigenschaft history.state, und history.length gibt an, wie viele Einträge sich im Sitzungsstapel befinden. Zum Aufbau der URLs, die Sie an diese Methoden übergeben, ist das URL-Objekt ein nützlicher Begleiter.

Das history-Objekt auf einen Blick

Die API wird über das globale window.history-Objekt bereitgestellt (Sie können auch einfach history schreiben):

MemberFunktion
history.pushState(state, title, url)Fügt einen neuen Eintrag zum History-Stapel hinzu und aktualisiert die URL.
history.replaceState(state, title, url)Ersetzt den aktuellen Eintrag — es wird kein neuer Stapeleintrag angelegt.
history.stateSchreibgeschützte Kopie des state-Objekts des aktuellen Eintrags.
history.lengthAnzahl der Einträge in der Sitzungshistorie.
history.back()Geht einen Eintrag zurück (entspricht dem Browser-Zurück-Button).
history.forward()Geht einen Eintrag vor.
history.go(n)Springt n Einträge (negativ = zurück, positiv = vorwärts).

Verwendung der History API in Webanwendungen

Die History API ermöglicht die Navigation zwischen verschiedenen Zuständen einer Anwendung, ohne die Seite neu zu laden. pushState() nimmt drei Argumente entgegen — ein state-object (ein beliebiger serialisierbarer Wert), einen title (von den meisten Browsern ignoriert, übergeben Sie daher einen leeren string) und eine url (relativ zur aktuellen Seite aufgelöst und muss same-origin sein). So fügen Sie einen neuen Zustand hinzu:

<div>
    <button onclick="changeState()">Go to New State</button>
</div>

<script>
    // Function to change state
    function changeState() {
        const newState = { id: 'newState' };
        // Push a new state to the history stack
        window.history.pushState(newState, 'New State', 'new-state-url');
    }
</script>

Damit wird ein neuer Zustand mit pushState() zum History-Stapel hinzugefügt. Beachten Sie, was dabei nicht passiert: new-state-url wird nicht geladen, und kein popstate-Ereignis wird ausgelöst. pushState aktualisiert nur die Adressleiste und den Stapel — das Rendern des passenden Inhalts ist Ihre Aufgabe.

Umgang mit dem Popstate-Ereignis

Wenn der Benutzer auf den Zurück- oder Vorwärts-Button des Browsers klickt (oder Sie history.back() / history.forward() aufrufen), wird das popstate-Ereignis ausgelöst. Die state-Eigenschaft des Ereignisses enthält das state-object, das Sie zuvor für den nun aktivierten Eintrag an pushState/replaceState übergeben haben. Reagieren Sie darauf, um die richtige Ansicht wiederherzustellen:

window.addEventListener('popstate', function(event) {
        if(event.state) {
            console.log('State changed:', event.state);
            // Handle the state object here
        }
    });

Dieser Listener reagiert auf popstate-Ereignisse, protokolliert Änderungen und ermöglicht die Anpassung des Zustands anhand der Navigationshistorie des Benutzers. Wenn event.state null ist, hat der Benutzer zu einem Eintrag zurücknavigiert, der durch einen normalen Seitenaufruf erstellt wurde (dem nie ein state-object übergeben wurde) — zeigen Sie dann Ihre Standardansicht an.

Den aktuellen Zustand ersetzen

Manchmal möchten Sie den aktuellen Eintrag aktualisieren, ohne einen neuen Datensatz im Stapel anzulegen — etwa beim Synchronisieren eines Filters oder einer Tab-Auswahl in die URL, wo ein zusätzlicher Zurück-Schritt störend wäre. Dafür ist replaceState() gedacht:

<div>
    <button onclick="replaceCurrentState()">Replace State</button>
    <p id="replace-status">Ready</p>
</div>

<script>
    function replaceCurrentState() {
        const newState = { id: 'replacedState' };
        // Replace the current state
        window.history.replaceState(newState, 'Replaced State', 'replaced-state-url');
        document.getElementById('replace-status').textContent = 'State replaced successfully!';
    }
</script>

Damit wird der aktuelle Eintrag an Ort und Stelle aktualisiert. Der Zurück-Button führt weiterhin zu der Seite, die zuvor aufgerufen wurde, da replaceState keinen neuen Schritt hinzufügt.

Vollständiges SPA-Beispiel

Bringen wir nun alles zusammen. Das folgende Beispiel simuliert eine Single-Page-Anwendung: Ein Button-Klick tauscht den Inhalt eines div aus und aktualisiert die URL mit pushState, während ein popstate-Listener den richtigen Inhalt wiederherstellt, wenn der Benutzer mit den Vor-/Zurück-Buttons navigiert. Dies ist dasselbe Muster, das ein clientseitiger Router intern verwendet.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <title>SPA Style History API Example</title>
</head>
<body>
    <h1>Page Navigation with History API</h1>
    <div id="content">Start Page</div>

    <!-- Buttons for navigation -->
    <button onclick="loadPage('page1')">Load Page 1</button>
    <button onclick="loadPage('page2')">Load Page 2</button>
    <button onclick="manualGoBack()">Go Back</button>
    <button onclick="manualGoForward()">Go Forward</button>

    <!-- Display the current status of the history -->
    <p id="historyStatus">History Status: Start</p>

    <script>
        // Loads a "page" and updates the browser's history state
        function loadPage(page) {
            const state = { page: page }; // State to be pushed to history
            history.pushState(state, `Page ${page}`, `${page}.html`); // Pushing state to the history
            document.getElementById('content').innerHTML = `<h2>This is ${page.replace('page', 'Page ')}</h2>`; // Update the content
            updateHistoryStatus(state); // Update the history status display
        }

        // Handles the browser's back and forward button actions
        window.addEventListener('popstate', function(event) {
            if (event.state) {
                // Update the page content and history status when navigating through history
                document.getElementById('content').innerHTML = `<h2>This is ${event.state.page.replace('page', 'Page ')}</h2>`;
                updateHistoryStatus(event.state);
            } else {
                // Fallback content when the history does not have any state
                document.getElementById('content').innerHTML = `<h2>Start Page</h2>`;
                document.getElementById('historyStatus').textContent = "History Status: Start";
            }
        });

        // Updates the display of the current history status
        function updateHistoryStatus(state) {
            document.getElementById('historyStatus').textContent = `History Status: ${state.page}`;
        }

        // Function to manually trigger going back in history
        function manualGoBack() {
            history.back();
        }

        // Function to manually trigger going forward in history
        function manualGoForward() {
            history.forward();
        }
    </script>
</body>
</html>

So funktioniert es:

  • Dynamisches Laden von SeitenloadPage() ändert den Inhalt eines div und ruft pushState auf, um einen History-Eintrag hinzuzufügen, sodass jede „Seite" ein echter Vor-/Zurück-Stopp wird.
  • Wiederherstellung bei Navigation — der popstate-Listener liest event.state.page und rendert den passenden Inhalt neu; wenn event.state null ist, wird auf die Startseite zurückgefallen.
  • Vertraute Benutzeroberfläche — die Buttons steuern history.back() und history.forward(), was ein mehrseitiges Feeling ohne einen einzigen vollständigen Neuladevorgang erzeugt.

Typische Fallstricke

Einige Verhaltensweisen überraschen Entwickler regelmäßig:

  • pushState löst kein popstate aus. Nur Benutzernavigation (Vor-/Zurück, history.go()) löst es aus. Nach einem pushState rendern Sie die neue Ansicht selbst in derselben Funktion.
  • Das title-Argument wird ignoriert. Fast jeder Browser ignoriert es, übergeben Sie daher "". Um den Tab-Titel zu ändern, setzen Sie document.title direkt.
  • URLs müssen same-origin sein. Das Übergeben einer cross-origin url wirft einen SecurityError. Der Pfad wird relativ zum aktuellen Dokument aufgelöst.
  • Der Zustand muss serialisierbar sein. Das state-object wird mit dem Structured-Clone-Algorithmus geklont, daher können Funktionen, DOM-Knoten und Klasseninstanzen nicht gespeichert werden — und es gibt ein Größenlimit (üblicherweise einige MB).
  • Ein Seitenneuladung startet Ihre App unter der neuen URL neu. Wenn der Benutzer eine per pushState gesetzte URL aktualisiert, muss der Server auf diesen Pfad antworten (oder Ihre SPA muss ihn beim Laden verarbeiten), sonst erhalten sie eine 404. Diese serverseitige Fallback-Lösung ist das Routing-Problem, das SPAs lösen müssen.

Best Practices für die Verwendung der History API

  • Halten Sie den Zustand klein. Speichern Sie einen Bezeichner oder wenige primitive Werte im state-object und leiten Sie den Rest ab. Keine großen Datensätze in die History dumpen — das bläht die Sitzung auf und riskiert das Überschreiten des Größenlimits.
  • Aktualisieren Sie document.title stets selbst, wenn die „Seite" wechselt, da das title-Argument ignoriert wird.
  • Scroll-Position verwalten. Browser stellen den Scroll-Zustand bei popstate über history.scrollRestoration wieder her; setzen Sie es auf "manual", wenn Sie das Scrollen selbst steuern möchten. Weitere Scroll-APIs finden Sie unter Fenstergrößen und Scrollen.
  • Stellen Sie einen serverseitigen Fallback bereit, damit das Aktualisieren oder Teilen einer per pushState gesetzten URL weiterhin funktioniert.
  • Verwenden Sie replaceState für nicht-navigatorische Aktualisierungen (Filter, Tabs), damit der Zurück-Button sinnvoll bleibt.
  • History prüfen — nutzen Sie history.state für das aktuelle state-object und history.length für die Anzahl der Einträge im Sitzungsstapel.

Da die History API nur die URL ändert, ergänzt sie sich natürlich mit fetch zum Laden der Daten, die jede „Seite" benötigt.

Fazit

Die JavaScript History API ermöglicht es Ihnen, die Sitzungshistorie des Browsers zu manipulieren — Einträge hinzuzufügen, zu ersetzen und auf Navigation zu reagieren — ohne vollständige Seitenneuladezyklen. Das Beherrschen von pushState, replaceState und dem popstate-Ereignis sowie das Kennen der Fallstricke rund um popstate, Serialisierung und same-origin-URLs ist es, was Single-Page-Anwendungen so schnell und navigierbar erscheinen lässt wie traditionelle mehrseitige Websites.

Übungen

Übung
Welche der folgenden Aussagen beschreiben Funktionalitäten der JavaScript History API?
Welche der folgenden Aussagen beschreiben Funktionalitäten der JavaScript History API?
Was this page helpful?