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):
| Membro | Cosa 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.state | Copia di sola lettura dell'object state della voce corrente. |
history.length | Numero 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
Navigare tra gli stati
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 pagine —
loadPage()modifica il contenuto di un div e chiamapushStateper aggiungere una voce nella cronologia, in modo che ogni "pagina" diventi una tappa reale di navigazione avanti/indietro. - Ripristino alla navigazione — il listener
popstateleggeevent.state.pagee ri-renderizza il contenuto corrispondente; quandoevent.stateènulltorna alla pagina iniziale. - Interfaccia familiare — i pulsanti gestiscono
history.back()ehistory.forward(), dando la sensazione di navigazione multipagina senza un singolo ricaricamento completo.
Problemi comuni
Alcuni comportamenti sorprendono regolarmente gli sviluppatori:
pushStatenon generapopstate. Solo la navigazione dell'utente (avanti/indietro,history.go()) lo genera. Dopo unpushState, renderizzate voi stessi la nuova vista nella stessa funzione.- L'argomento
titleviene ignorato. Quasi tutti i browser lo ignorano, quindi si passa"". Per cambiare il titolo della scheda, impostare direttamentedocument.title. - Gli URL devono avere la stessa origine. Passare un
urlcross-origin genera unSecurityError. 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.titlemanualmente quando la "pagina" cambia, poiché l'argomentotitleviene ignorato. - Gestire la posizione di scorrimento. I browser ripristinano lo scorrimento all'evento
popstatetramitehistory.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
pushStatecontinui a funzionare. - Usare
replaceStateper aggiornamenti non navigazionali (filtri, schede) in modo che il pulsante indietro rimanga significativo. - Ispezione della cronologia — usare
history.stateper l'object state corrente ehistory.lengthper 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.