W3docs

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

MembroO 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.stateCópia somente de leitura do objeto state da entrada atual.
history.lengthNú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

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áginasloadPage() altera o conteúdo de uma div e chama pushState para 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 popstateevent.state.page e volta a renderizar o conteúdo correspondente; quando event.state é null, recorre à página inicial.
  • Interface familiar — os botões acionam history.back() e history.forward(), dando uma sensação de múltiplas páginas sem um único recarregamento completo.

Erros comuns

Alguns comportamentos surpreendem regularmente os programadores:

  • pushState não dispara popstate. Apenas a navegação do utilizador (retrocesso/avanço, history.go()) o dispara. Após um pushState, 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, defina document.title diretamente.
  • As URLs devem ter a mesma origem. Passar uma url de origem diferente lança um SecurityError. 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.title você mesmo quando a "página" muda, uma vez que o argumento title é ignorado.
  • Gira a posição de deslocamento. Os navegadores restauram o deslocamento no popstate através de history.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 pushState ainda funcione.
  • Use replaceState para atualizações não navegacionais (filtros, separadores) para que o botão de retrocesso permaneça significativo.
  • Inspecionar o histórico — use history.state para o objeto state atual e history.length para 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.

Prática

Prática
Quais das seguintes são funcionalidades da JavaScript History API?
Quais das seguintes são funcionalidades da JavaScript History API?
Was this page helpful?