Introducción: Por qué cambiar la URL sin recargar es un superpoder
\n\nImagine: abre un servicio web moderno — digamos, un cliente de correo o un editor de documentos en línea. Cambia entre carpetas, abre correos, modifica configuraciones — y la página no se recarga. Mientras tanto, la barra de direcciones del navegador cambia, los botones «Atrás» y «Adelante» funcionan, y los marcadores guardan el estado deseado. ¿Cómo funciona esto? Magia de la modificación de URL sin recargar la página.
\n\nEsto no es solo un «adorno» — es la base de las aplicaciones de una sola página (SPA) de alto rendimiento. Antiguamente, los desarrolladores usaban hashes (#), pero era feo e incómodo. Hoy tenemos la History API — una herramienta poderosa que permite cambiar la URL al instante, sin parpadeos, sin pérdida de estado, sin solicitudes HTTP innecesarias.
\n\nEn este artículo, analizaremos cómo funcionan pushState y replaceState, cómo afectan el rendimiento y cómo evitar errores comunes al optimizar. ¡Allá vamos!
Fundamentos: History API y sus métodos
\n\nLa especificación HTML5 nos regaló la interfaz window.history. Los dos métodos principales para cambiar la URL sin recargar son pushState y replaceState.
pushState — añade una nueva entrada al historial
\n\npushState crea una nueva entrada en el historial del navegador. El usuario puede presionar «Atrás» y volver a la URL anterior.
// Sintaxis\nhistory.pushState(state, title, url);\n\n// Ejemplo\nhistory.pushState({ page: 1 }, 'Inicio', '/home');\n\n\nParámetros:
\n- \n
- state — objeto de datos asociado con esta entrada. Puede almacenar cualquier dato serializable (por ejemplo, estado de un filtro, ID de un elemento). \n
- title — título de la página (la mayoría de los navegadores lo ignoran, pero es mejor pasar una cadena vacía o un texto significativo). \n
- url — la nueva URL. Debe ser relativa o absoluta, pero dentro del mismo origin (protocolo + dominio + puerto). \n
replaceState — reemplaza la entrada actual
\n\nreplaceState modifica la entrada actual en el historial sin crear una nueva. El usuario no podrá volver «atrás» a la URL anterior — será reemplazada.
// Sintaxis\nhistory.replaceState(state, title, url);\n\n// Ejemplo — actualizar la URL al cambiar un filtro\nfunction applyFilter(filterName) {\n history.replaceState({ filter: filterName }, '', `?filter=${filterName}`);\n updateUI(filterName);\n}\n\n\npopstate — escuchar cambios en el historial
\n\nCuando el usuario presiona «Atrás» o «Adelante», el navegador genera el evento popstate. Importante: pushState y replaceState no disparan este evento — debe manejarse por separado.
window.addEventListener('popstate', function(event) {\n if (event.state) {\n // Restaurar el estado según los datos de event.state\n restorePageState(event.state);\n } else {\n // Si el state está vacío — quizás el usuario salió de los límites de nuestra aplicación\n loadDefaultState();\n }\n});\n\n\nOptimización del rendimiento: qué es importante saber
\n\nLa modificación de URL es rápida. Pero hay matices que pueden convertir un superpoder en una fuente de errores.
\n\n1. No llame a pushState con demasiada frecuencia
\n\nCada llamada a pushState añade una entrada al historial. Si lo llama con cada movimiento del ratón o cada carácter en una búsqueda — el historial se inflará y el usuario tendrá que presionar «Atrás» 100 veces para salir del filtro. Use replaceState para estados intermedios y pushState para acciones lógicamente completas (por ejemplo, abrir una nueva página).