W3docs

JavaScript History API

Nello sviluppo web moderno, la JavaScript History API consente di manipolare la cronologia del browser senza ricaricare la pagina.

Introduzione alla JavaScript History API

Nello sviluppo web moderno, creare esperienze utente fluide spesso implica la manipolazione della cronologia del browser. La JavaScript History API consente di leggere e modificare la cronologia di sessione del browser — ovvero l'elenco delle pagine (e degli stati URL) che l'utente ha visitato nella scheda corrente. Utilizzando questa API, gli sviluppatori possono aggiornare la barra degli indirizzi e lo stack avanti/indietro senza provocare un ricaricamento completo della pagina, che è esattamente ciò di cui le applicazioni a pagina singola (SPA) hanno bisogno per sembrare veloci e native.

Questa pagina tratta i tre metodi principali (pushState, replaceState e gli helper di navigazione), come reagire all'evento popstate, i problemi comuni in cui ci si imbatte e le best practice per utilizzare l'API in produzione.

Perché esiste la History API

Prima di questa API, l'unico modo per modificare l'URL era navigare, il che ricaricava l'intero documento. Le SPA renderizzano nuove "pagine" con JavaScript, quindi hanno bisogno che l'URL rimanga sincronizzato senza un ricaricamento — altrimenti il pulsante indietro, i segnalibri e i link condivisibili smettono di funzionare.

La History API risolve questo problema offrendo un modo per:

  • Aggiungere una nuova voce allo stack avanti/indietro (pushState).
  • Modificare la voce corrente sul posto (replaceState).
  • Reagire quando l'utente si sposta attraverso quello stack con i pulsanti avanti/indietro (popstate).

I dati della voce corrente si leggono tramite la proprietà di sola lettura history.state, mentre history.length indica quante voci si trovano nello stack di sessione. Per costruire gli URL da passare a questi metodi, l'oggetto URL è un utile compagno.

L'object history in sintesi

L'API è esposta sull'object globale window.history (si può scrivere semplicemente history):

MembroCosa fa
history.pushState(state, title, url)Aggiunge una nuova voce allo stack della cronologia e aggiorna l'URL.
history.replaceState(state, title, url)Sostituisce la voce corrente — non viene creata alcuna nuova voce nello stack.
history.stateCopia di sola lettura dell'object state della voce corrente.
history.lengthNumero di voci nella cronologia di sessione.
history.back()Torna indietro di una voce (equivalente al pulsante indietro del browser).
history.forward()Va avanti di una voce.
history.go(n)Salta di n voci (negativo = indietro, positivo = avanti).

Utilizzo della History API nelle applicazioni web

La History API consente la navigazione tra diversi stati di un'applicazione senza ricaricare la pagina. pushState() accetta tre argomenti — un object state (qualsiasi valore serializzabile), un title (ignorato dalla maggior parte dei browser, quindi si passa una stringa vuota) e un url (risolto relativamente alla pagina corrente e deve avere la stessa origine). Ecco come inserire un nuovo stato:

<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>

Questo aggiunge un nuovo stato allo stack della cronologia usando pushState(). Notate cosa non fa: non carica new-state-url e non genera un evento popstate. pushState aggiorna solo la barra degli indirizzi e lo stack — renderizzare il contenuto corrispondente è compito vostro.

Gestire l'evento Popstate

Quando l'utente fa clic sui pulsanti avanti o indietro del browser (oppure si chiama history.back() / history.forward()), viene generato l'evento popstate. La proprietà state dell'evento contiene l'object state precedentemente passato a pushState/replaceState per la voce ora attivata. Gestirlo permette di ripristinare la vista corretta:

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

Questo listener reagisce agli eventi popstate, registrando le modifiche e consentendo gli adattamenti dello stato in base alla cronologia di navigazione dell'utente. Quando event.state è null, l'utente è tornato a una voce creata da un normale caricamento di pagina (che non ha mai ricevuto un object state), quindi è necessario visualizzare la vista predefinita.

Sostituire lo stato corrente

A volte si vuole aggiornare la voce corrente senza aggiungere un nuovo record allo stack — ad esempio, sincronizzare un filtro o la selezione di una scheda nell'URL in cui aggiungere un passo con il pulsante indietro sarebbe fastidioso. Questo è lo scopo di replaceState():

<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>

Questo aggiorna la voce corrente sul posto. Il pulsante indietro porta comunque alla pagina precedente, poiché replaceState non ha inserito un nuovo passo.

Esempio completo in stile SPA

Ora mettiamo tutto insieme. L'esempio seguente simula un'applicazione a pagina singola: facendo clic su un pulsante si sostituisce il contenuto di un div e si aggiorna l'URL con pushState, mentre un listener popstate ripristina il contenuto corretto quando l'utente naviga con i pulsanti avanti/indietro. Questo è lo stesso pattern che utilizza internamente un router lato client.

<!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>

Come funziona:

  • Caricamento dinamico delle pagineloadPage() modifica il contenuto di un div e chiama pushState per aggiungere una voce nella cronologia, in modo che ogni "pagina" diventi una tappa reale di navigazione avanti/indietro.
  • Ripristino alla navigazione — il listener popstate legge event.state.page e ri-renderizza il contenuto corrispondente; quando event.state è null torna alla pagina iniziale.
  • Interfaccia familiare — i pulsanti gestiscono history.back() e history.forward(), dando la sensazione di navigazione multipagina senza un singolo ricaricamento completo.

Problemi comuni

Alcuni comportamenti sorprendono regolarmente gli sviluppatori:

  • pushState non genera popstate. Solo la navigazione dell'utente (avanti/indietro, history.go()) lo genera. Dopo un pushState, renderizzate voi stessi la nuova vista nella stessa funzione.
  • L'argomento title viene ignorato. Quasi tutti i browser lo ignorano, quindi si passa "". Per cambiare il titolo della scheda, impostare direttamente document.title.
  • Gli URL devono avere la stessa origine. Passare un url cross-origin genera un SecurityError. Il percorso viene risolto relativamente al documento corrente.
  • Lo state deve essere serializzabile. L'object state viene clonato con l'algoritmo structured clone, quindi funzioni, nodi DOM e istanze di classi non possono essere memorizzati — ed esiste un limite di dimensione (solitamente alcuni MB).
  • Un ricaricamento della pagina riesegue l'app al nuovo URL. Quando l'utente aggiorna un URL inserito con pushState, il server deve rispondere a quel percorso (oppure la SPA deve gestirlo al caricamento), altrimenti si ottiene un 404. Questo fallback lato server è il problema di routing che le SPA devono risolvere.

Best practice per l'utilizzo della History API

  • Mantenere lo state piccolo. Memorizzare un identificatore o pochi valori primitivi nell'object state e derivare il resto. Non inserire grandi dataset nella cronologia — gonfia la sessione e rischia di superare il limite di dimensione.
  • Aggiornare sempre document.title manualmente quando la "pagina" cambia, poiché l'argomento title viene ignorato.
  • Gestire la posizione di scorrimento. I browser ripristinano lo scorrimento all'evento popstate tramite history.scrollRestoration; impostarlo su "manual" se si vuole controllare lo scorrimento autonomamente. Consultare dimensioni e scorrimento della finestra per le API di scorrimento.
  • Fornire un fallback lato server in modo che aggiornare o condividere un URL inserito con pushState continui a funzionare.
  • Usare replaceState per aggiornamenti non navigazionali (filtri, schede) in modo che il pulsante indietro rimanga significativo.
  • Ispezione della cronologia — usare history.state per l'object state corrente e history.length per il numero di voci nello stack di sessione.

Poiché la History API modifica solo l'URL, si abbina naturalmente a fetch per caricare i dati necessari a ogni "pagina".

Conclusione

La JavaScript History API consente di manipolare la cronologia di sessione del browser — inserendo, sostituendo e reagendo alla navigazione — senza ricaricamenti completi della pagina. Padroneggiare pushState, replaceState e l'evento popstate, rispettando le insidie relative a popstate, alla serializzazione e agli URL della stessa origine, è ciò che rende le applicazioni a pagina singola veloci e navigabili quanto i tradizionali siti multipagina.

Pratica

Pratica
Quali delle seguenti sono funzionalità della JavaScript History API?
Quali delle seguenti sono funzionalità della JavaScript History API?
Was this page helpful?