JavaScript History API
No desenvolvimento web moderno, criar experiências fluidas envolve manipular o histórico do navegador. A JavaScript History API fornece as ferramentas
Introdução à JavaScript History API
No desenvolvimento web moderno, criar experiências de utilizador fluidas muitas vezes envolve manipular o histórico do navegador. A JavaScript History API permite ler e alterar o histórico de sessão do navegador — a lista de páginas (e estados de URL) que o utilizador visitou na aba atual. Ao utilizar esta API, os programadores podem atualizar a barra de endereços e a pilha de retrocesso/avanço sem desencadear um recarregamento completo da página, que é exatamente o que as aplicações de página única (SPAs) precisam para se sentir rápidas e nativas.
Esta página abrange os três métodos principais (pushState, replaceState e os auxiliares de navegação), como reagir ao evento popstate, os erros comuns que apanham as pessoas de surpresa e as melhores práticas para usar a API em produção.
Por que existe a History API
Antes desta API, a única forma de alterar a URL era navegar, o que recarregava o documento inteiro. As SPAs renderizam novas "páginas" com JavaScript, por isso precisam que a URL permaneça sincronizada sem recarregamento — caso contrário, o botão de retrocesso, os favoritos e os links partilháveis ficam todos quebrados.
A History API resolve isto fornecendo uma forma de:
- Adicionar uma nova entrada à pilha de retrocesso/avanço (
pushState). - Modificar a entrada atual no lugar (
replaceState). - Reagir quando o utilizador se move pela pilha com os botões de retrocesso/avanço (
popstate).
O utilizador lê os dados da entrada atual através da propriedade somente de leitura history.state, e history.length indica quantas entradas existem na pilha de sessão. Para construir as URLs que passa a estes métodos, o objeto URL é um companheiro útil.
O objeto history num relance
A API é exposta no objeto global window.history (pode escrever apenas history):
| Membro | O que faz |
|---|---|
history.pushState(state, title, url) | Adiciona uma nova entrada à pilha de histórico e atualiza a URL. |
history.replaceState(state, title, url) | Substitui a entrada atual — não é criada nenhuma nova entrada na pilha. |
history.state | Cópia somente de leitura do objeto state da entrada atual. |
history.length | Número de entradas no histórico de sessão. |
history.back() | Volta uma entrada (o mesmo que o botão de retrocesso do navegador). |
history.forward() | Avança uma entrada. |
history.go(n) | Salta n entradas (negativo = retroceder, positivo = avançar). |
Utilizar a History API em Aplicações Web
Navegar Entre Estados
A History API permite navegar entre diferentes estados de uma aplicação sem recarregar a página. pushState() recebe três argumentos — um objeto state (qualquer valor serializável), um title (ignorado pela maioria dos navegadores, por isso passe uma string vazia) e uma url (resolvida relativamente à página atual e deve ter a mesma origem). Eis como inserir um novo estado:
<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>Isto adiciona um novo estado à pilha de histórico usando pushState(). Note o que não faz: não carrega new-state-url e não dispara um evento popstate. pushState apenas atualiza a barra de endereços e a pilha — renderizar o conteúdo correspondente é da sua responsabilidade.
Gerir o Evento Popstate
Quando o utilizador clica no botão de retrocesso ou avanço do navegador (ou chama history.back() / history.forward()), o evento popstate é disparado. A propriedade state do evento contém o objeto state que passou anteriormente a pushState/replaceState para a entrada que está a ser ativada. Trate-o para restaurar a vista correta:
window.addEventListener('popstate', function(event) {
if(event.state) {
console.log('State changed:', event.state);
// Handle the state object here
}
});Este ouvinte reage a eventos popstate, registando alterações e permitindo ajustes de estado com base no histórico de navegação do utilizador. Quando event.state é null, o utilizador navegou de volta para uma entrada criada por um carregamento normal de página (que nunca recebeu um objeto state), por isso renderize a sua vista predefinida.
Substituir o Estado Atual
Por vezes, pretende atualizar a entrada atual sem adicionar um novo registo à pilha — por exemplo, sincronizar um filtro ou uma seleção de separador na URL onde adicionar um passo de retrocesso seria inconveniente. É para isso que serve 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>Isto atualiza a entrada atual no lugar. O botão de retrocesso continua a ir para a página que veio antes, porque replaceState não inseriu um novo passo.
Exemplo completo estilo SPA
Agora vamos juntar tudo. O exemplo abaixo simula uma aplicação de página única: clicar num botão troca o conteúdo de uma div e atualiza a URL com pushState, enquanto um ouvinte popstate restaura o conteúdo correto quando o utilizador navega com os botões de retrocesso/avanço. Este é o mesmo padrão que um router do lado do cliente usa internamente.
<!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>Como funciona:
- Carregamento dinâmico de páginas —
loadPage()altera o conteúdo de uma div e chamapushStatepara adicionar uma entrada no histórico, para que cada "página" se torne uma paragem real de retrocesso/avanço. - Restaurar na navegação — o ouvinte
popstatelêevent.state.pagee volta a renderizar o conteúdo correspondente; quandoevent.stateénull, recorre à página inicial. - Interface familiar — os botões acionam
history.back()ehistory.forward(), dando uma sensação de múltiplas páginas sem um único recarregamento completo.
Erros comuns
Alguns comportamentos surpreendem regularmente os programadores:
pushStatenão disparapopstate. Apenas a navegação do utilizador (retrocesso/avanço,history.go()) o dispara. Após umpushState, renderize a nova vista você mesmo na mesma função.- O argumento
titleé ignorado. Quase todos os navegadores não o consideram, por isso passe"". Para alterar o título do separador, definadocument.titlediretamente. - As URLs devem ter a mesma origem. Passar uma
urlde origem diferente lança umSecurityError. O caminho é resolvido relativamente ao documento atual. - O state deve ser serializável. O objeto state é clonado com o algoritmo de clonagem estruturada, por isso funções, nós DOM e instâncias de classe não podem ser armazenados — e existe um limite de tamanho (geralmente alguns MB).
- Um recarregamento de página volta a executar a sua aplicação na nova URL. Quando o utilizador atualiza uma URL inserida com
pushState, o servidor deve responder a esse caminho (ou a sua SPA deve tratá-lo no carregamento), caso contrário obtém um 404. Esta solução de recurso do lado do servidor é o problema de roteamento que as SPAs devem resolver.
Melhores Práticas para Usar a History API
- Mantenha o state pequeno. Armazene um identificador ou alguns primitivos no objeto state e derive o resto. Não despeje grandes conjuntos de dados no histórico — isso sobrecarrega a sessão e arrisca atingir o limite de tamanho.
- Atualize sempre
document.titlevocê mesmo quando a "página" muda, uma vez que o argumentotitleé ignorado. - Gira a posição de deslocamento. Os navegadores restauram o deslocamento no
popstateatravés dehistory.scrollRestoration; defina-o como"manual"se quiser controlar o deslocamento você mesmo. Consulte tamanhos de janela e deslocamento para as APIs de deslocamento. - Forneça uma solução de recurso do lado do servidor para que atualizar ou partilhar uma URL inserida com
pushStateainda funcione. - Use
replaceStatepara atualizações não navegacionais (filtros, separadores) para que o botão de retrocesso permaneça significativo. - Inspecionar o histórico — use
history.statepara o objeto state atual ehistory.lengthpara o número de entradas na pilha de sessão.
Como a History API apenas altera a URL, combina naturalmente com fetch para carregar os dados de que cada "página" necessita.
Conclusão
A JavaScript History API permite manipular o histórico de sessão do navegador — inserindo, substituindo e reagindo à navegação — sem recarregamentos completos de página. Dominar pushState, replaceState e o evento popstate, respeitando os erros comuns em torno de popstate, serialização e URLs de mesma origem, é o que faz com que as aplicações de página única se sintam tão rápidas e navegáveis quanto os sites tradicionais de múltiplas páginas.