W3docs

JavaScript History API

En el desarrollo web moderno, la JavaScript History API te permite manipular el historial del navegador sin recargar la página.

Introducción a la JavaScript History API

En el desarrollo web moderno, crear experiencias de usuario fluidas implica con frecuencia manipular el historial del navegador. La JavaScript History API te permite leer y modificar el historial de sesión del navegador — la lista de páginas (y estados de URL) que el usuario ha visitado en la pestaña actual. Con esta API, los desarrolladores pueden actualizar la barra de direcciones y la pila de navegación hacia atrás/adelante sin provocar una recarga completa de la página, que es exactamente lo que las aplicaciones de una sola página (SPAs) necesitan para sentirse rápidas y nativas.

Esta página cubre los tres métodos principales (pushState, replaceState y los helpers de navegación), cómo reaccionar al evento popstate, los errores comunes que suelen confundir a los desarrolladores y las buenas prácticas para usar la API en producción.

Por qué existe la History API

Antes de esta API, la única forma de cambiar la URL era navegar, lo que recargaba el documento completo. Las SPAs renderizan nuevas "páginas" con JavaScript, por lo que necesitan que la URL se mantenga sincronizada sin una recarga — de lo contrario, el botón de retroceso, los marcadores y los enlaces compartibles dejan de funcionar.

La History API soluciona esto dándote una forma de:

  • Añadir una nueva entrada a la pila de navegación hacia atrás/adelante (pushState).
  • Modificar la entrada actual en su lugar (replaceState).
  • Reaccionar cuando el usuario se mueve por esa pila con los botones de retroceso/avance (popstate).

Puedes leer los datos de la entrada actual mediante la propiedad de solo lectura history.state, y history.length indica cuántas entradas hay en la pila de sesión. Para construir las URLs que pasas a estos métodos, el objeto URL es un complemento muy útil.

El object history de un vistazo

La API se expone en el objeto global window.history (puedes escribir simplemente history):

MiembroQué hace
history.pushState(state, title, url)Añade una nueva entrada a la pila de historial y actualiza la URL.
history.replaceState(state, title, url)Reemplaza la entrada actual — no se crea una nueva entrada en la pila.
history.stateCopia de solo lectura del object de estado de la entrada actual.
history.lengthNúmero de entradas en el historial de sesión.
history.back()Retrocede una entrada (equivalente al botón de retroceso del navegador).
history.forward()Avanza una entrada.
history.go(n)Salta n entradas (negativo = retroceder, positivo = avanzar).

Uso de la History API en aplicaciones web

La History API permite navegar entre diferentes estados de una aplicación sin recargar la página. pushState() recibe tres argumentos — un object state (cualquier valor serializable), un title (ignorado por la mayoría de los navegadores, así que pasa una string vacía) y una url (resuelta de forma relativa a la página actual y debe tener el mismo origen). Así es como se empuja un nuevo 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>

Esto añade un nuevo estado a la pila de historial usando pushState(). Observa lo que no hace: no carga new-state-url y no dispara un evento popstate. pushState solo actualiza la barra de direcciones y la pila — renderizar el contenido correspondiente es tu responsabilidad.

Manejo del evento Popstate

Cuando el usuario hace clic en el botón de retroceso o avance del navegador (o cuando llamas a history.back() / history.forward()), se dispara el evento popstate. La propiedad state del evento contiene el object de estado que pasaste previamente a pushState/replaceState para la entrada que se activa en ese momento. Manéjalo para restaurar la vista correcta:

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

Este listener reacciona a los eventos popstate, registrando los cambios y permitiendo ajustes de estado en función del historial de navegación del usuario. Cuando event.state es null, el usuario ha navegado de vuelta a una entrada creada por una carga de página normal (una que nunca recibió un object de estado), así que renderiza tu vista predeterminada.

Reemplazar el estado actual

A veces quieres actualizar la entrada actual sin añadir un nuevo registro a la pila — por ejemplo, sincronizar un filtro o una selección de pestaña en la URL donde añadir un paso al botón de retroceso sería molesto. Para eso sirve 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>

Esto actualiza la entrada actual en su lugar. El botón de retroceso sigue llevando a la página anterior, porque replaceState no empujó un nuevo paso.

Ejemplo completo estilo SPA

Ahora reunamos todo. El ejemplo siguiente simula una aplicación de una sola página: al hacer clic en un botón se intercambia el contenido de un div y se actualiza la URL con pushState, mientras que un listener de popstate restaura el contenido correcto cuando el usuario navega con los botones de retroceso/avance. Este es el mismo patrón que usa un enrutador del lado del cliente 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>

Cómo funciona:

  • Carga de páginas dinámicaloadPage() cambia el contenido de un div y llama a pushState para añadir una entrada al historial, de modo que cada "página" se convierte en un punto real de retroceso/avance.
  • Restauración en la navegación — el listener de popstate lee event.state.page y vuelve a renderizar el contenido correspondiente; cuando event.state es null vuelve a la página de inicio.
  • UI familiar — los botones controlan history.back() y history.forward(), dando una sensación de múltiples páginas sin ninguna recarga completa.

Errores comunes

Algunos comportamientos suelen sorprender a los desarrolladores:

  • pushState no dispara popstate. Solo la navegación del usuario (retroceso/avance, history.go()) lo dispara. Después de un pushState, renderiza la nueva vista tú mismo en la misma función.
  • El argumento title es ignorado. Casi todos los navegadores lo ignoran, así que pasa "". Para cambiar el título de la pestaña, establece document.title directamente.
  • Las URLs deben tener el mismo origen. Pasar una url de origen cruzado lanza un SecurityError. La ruta se resuelve de forma relativa al documento actual.
  • El estado debe ser serializable. El object de estado se clona con el algoritmo de clonado estructurado, por lo que funciones, nodos DOM e instancias de clase no pueden almacenarse — y existe un límite de tamaño (comúnmente unos pocos MB).
  • Una recarga de página vuelve a ejecutar tu aplicación en la nueva URL. Cuando el usuario actualiza una URL creada con pushState, el servidor debe responder a esa ruta (o tu SPA debe manejarla al cargar), de lo contrario obtendrán un 404. Este fallback del lado del servidor es el problema de enrutamiento que las SPAs deben resolver.

Buenas prácticas para usar la History API

  • Mantén el estado pequeño. Almacena un identificador o unos pocos valores primitivos en el object de estado y deriva el resto. No vuelques grandes conjuntos de datos en el historial — esto aumenta el tamaño de la sesión y puede alcanzar el límite de tamaño.
  • Actualiza siempre document.title tú mismo cuando cambie la "página", ya que el argumento title es ignorado.
  • Gestiona la posición del scroll. Los navegadores restauran el scroll en popstate mediante history.scrollRestoration; establécelo en "manual" si quieres controlar el desplazamiento tú mismo. Consulta tamaños de ventana y desplazamiento para las APIs de scroll.
  • Proporciona un fallback del lado del servidor para que actualizar o compartir una URL creada con pushState siga funcionando.
  • Usa replaceState para actualizaciones no navegacionales (filtros, pestañas) para que el botón de retroceso siga siendo significativo.
  • Inspeccionar el historial — usa history.state para el object de estado actual y history.length para el número de entradas en la pila de sesión.

Dado que la History API solo cambia la URL, se combina de forma natural con fetch para cargar los datos que cada "página" necesita.

Conclusión

La JavaScript History API te permite manipular el historial de sesión del navegador — añadiendo, reemplazando y reaccionando a la navegación — sin recargas completas de página. Dominar pushState, replaceState y el evento popstate, respetando los errores comunes relacionados con popstate, la serialización y las URLs de mismo origen, es lo que hace que las aplicaciones de una sola página se sientan tan rápidas y navegables como los sitios web tradicionales de múltiples páginas.

Práctica

Práctica
¿Cuáles de las siguientes son funcionalidades de la JavaScript History API?
¿Cuáles de las siguientes son funcionalidades de la JavaScript History API?
Was this page helpful?