Diseño para la caché de retroceso/avance: Requisitos, restauración y estado
Aprende cómo la caché de retroceso/avance del navegador restaura páginas completas, qué impide silenciosamente esta función y cómo manejar el estado restaurado con pageshow y pagehide.
Fuentes: The Browser Feature Most Frontend Developers Completely Ignore
Cuando un usuario pulsa Atrás, la mayoría de las aplicaciones vuelven a realizar en silencio las tareas que el navegador podría haber omitido: volver a descargar el HTML, ejecutar cada script, reconstruir el DOM y recargar los datos. El caché de navegación hacia atrás/adelante (bfcache) existe precisamente para evitar eso, pero el código cotidiano a menudo lo desactiva sin que nadie se dé cuenta o maneja incorrectamente la página que vuelve a cargar. Esta guía explica cómo se diferencia bfcache del caché ordinario, qué hábitos lo bloquean, cómo detectar y reaccionar ante una página restaurada, y cómo verificar su comportamiento en sus propias pantallas.
Por qué la navegación por el historial merece atención especial
El trabajo relacionado con el rendimiento tiende a centrarse en la primera visita. Los equipos comprimen imágenes, dividen los archivos, precargan fuentes, reducen el costo de carga inicial, ajustan las llamadas a API y vigilan el tiempo de carga del contenido más importante. Todo eso es valioso, pero asume en silencio que cada visualización de página comienza desde una pestaña en blanco y una URL escrita recientemente.
Ese no es el modo en que la gente navega. Un comprador abre un producto, vuelve a los resultados, abre otro artículo y vuelve de nuevo. Un lector sigue un enlace desde un artículo y regresa para terminarlo de leer. Alguien vuelve a consultar un formulario incompleto. Los botones Atrás y Adelante se encuentran entre los controles más utilizados en cualquier navegador, y estos procesos son algo habitual y no excepcional.
Los navegadores optimizan este patrón mediante bfcache. En lugar de eliminar una página cuando el usuario se va, el navegador puede conservar una instantánea de todo el documento en memoria. Cuando el usuario regresa a través del historial de sesiones, puede mostrar esa instantánea en lugar de repetir la solicitud de red, el análisis del HTML, la ejecución de scripts, la carga de datos y su renderizado. Cada motor importante implementa alguna versión de esto, aunque las reglas exactas de aplicabilidad y los mecanismos internos varían entre navegadores.
Desde el punto de vista del usuario, una restauración exitosa parece casi instantánea. El desplazamiento de despliegue permanece donde lo dejaron, el DOM está intacto, los campos del formulario pueden seguir conteniendo sus valores y la pila de JavaScript simplemente continúa funcionando. Sin embargo, hay dos problemas: muchos equipos nunca verifican si sus páginas cumplen con los requisitos, y el código que asume que cada visita corresponde a una página completamente nueva puede comportarse incorrectamente cuando vuelve a utilizarse una antigua.
Por lo tanto, bfcache es algo más que un simple acelerador de velocidad gratuito. Proporciona un ciclo de vida más realista: una página puede quedar inactiva mientras sigue existiendo, pero luego volver a activarse sin que su código de inicio se ejecute nuevamente.
Cómo bfcache difiere del caché HTTP
La palabra compartida “cache” es la primera causa de confusión. Es fácil pensar que bfcache se refiere simplemente al reuso de CSS, scripts, imágenes o respuestas de API que ya se descargaron anteriormente. Eso es el caché HTTP, y funciona a un nivel mucho más detallado.
El caché HTTP almacena respuestas individuales. Al volver a una página, el navegador puede omitir algunas descargas, pero el documento en sí aún debe reconstruirse: se analiza el HTML, se ejecutan los scripts, se inicia el framework, se crea el DOM, se restablecen las suscripciones y se envían solicitudes nuevas.
bfcache mantiene toda la página, incluyendo su DOM y su heap de JavaScript. Cuando el usuario navega a otra página, la ejecución de los scripts puede pausarse; al regresar mediante el historial, el navegador muestra la página conservada y la reanuda. Por eso, un acceso a bfcache es incluso mejor que una recarga bien ajustada servida en gran medida desde el caché HTTP: simplemente queda mucho menos trabajo por hacer.
Esto cambia la forma en que se debe razonar sobre la navegación. Al restaurar un bfcache, el camino habitual de inicio no necesariamente se ejecuta nuevamente. Una variable a nivel de módulo puede seguir manteniendo su valor anterior. El DOM sigue reflejando lo que el usuario hizo antes de irse. Los temporizadores y otras actividades en tiempo de ejecución continúan desde donde se detuvieron, en lugar de crearse desde cero.
El código desarrollado bajo la premisa de que volver siempre implica una recarga completa puede, por lo tanto, pasar por alto actualizaciones, mantener un estado obsoleto en la pantalla o crear duplicados al intentar configurar algo que ya existe. El modelo mental a adoptar es simple: el navegador no volvió a renderizar una respuesta HTML antigua; simplemente reactivó una página suspendida.
Una gran ventaja que prácticamente no requiere una nueva interfaz
La mayoría de las mejoras en el rendimiento conllevan algún costo visible: componentes rediseñados, nuevos estados de carga, renderizado en el servidor o una estrategia diferente para obtener datos. bfcache es inusual porque el navegador ya implementa dicho mecanismo.
Su tarea no es crear un caché, sino evitar que el navegador no pueda utilizar el que ya tiene y hacer que el ciclo de vida de su página sea compatible con su restauración.
Eso lo convierte en una de las optimizaciones más efectivas a disposición de muchos equipos. Una página restaurada desde la memoria evita transferencias repetidas y la mayor parte del procesamiento necesario para una navegación normal. La ventaja es más notable en dispositivos y redes lentas, pero incluso las máquinas rápidas muestran una diferencia clara entre reconstruir una página y simplemente mostrarla.
También preserva la experiencia, no solo el momento en que se accede a ella. Imagine a alguien que está a mitad de un artículo largo, abre un enlace relacionado y luego vuelve. Una recarga convencional debe reconstruir la página e intentar restaurar la posición de desplazamiento, a menudo después de que el diseño haya cambiado. La restauración mediante bfcache lo lleva casi exactamente al lugar donde estaba.
Las listas de productos ilustran este punto aún más claramente. Alguien utiliza cuatro o cinco filtros para reducir un catálogo, recorre una larga lista, selecciona un producto y luego regresa. Si la lista vuelve inmediatamente al mismo estado, la experiencia parece continua. Si se recarga, se vuelve a obtener los datos, se eliminan los filtros o se vuelve a la primera fila, el sitio parece frágil, por buenos que sean sus indicadores en la primera carga.
El rendimiento no se refiere solo a la rapidez con la que las personas acceden a una aplicación. También depende de cuánto esfuerzo repetido les ahorra el producto al pasar de una pantalla a otra.
Cómo el hábito de usar unload elimina silenciosamente las ventajas
Una de las razones por las que bfcache pasa desapercibido es que no hay fallo visible cuando una página no cumple los requisitos. El navegador simplemente realiza una navegación normal por el historial. Todo sigue funcionando; los usuarios simplemente pierden la posibilidad de restaurar instantáneamente la página.
El evento unload es el culpable clásico. El código más antiguo lo utiliza para enviar datos de análisis, liberar recursos o informar al servidor que la página se está cerrando, partiendo de la idea de que la navegación siempre destruye la página y brinda una última oportunidad para limpiarla.
Esa teoría ya no se ajusta a la realidad. Una página puede ser suspendida para bfcache en lugar de eliminada, y unload ha sido poco fiable durante mucho tiempo, especialmente en dispositivos móviles, donde con frecuencia nunca se ejecuta. El comportamiento varía entre navegadores, pero registrar un oyente de unload puede hacer que una página no sea apta para bfcache en algunos de ellos. La guía actual de la plataforma desaconseja encarecidamente el uso de unload y recomienda eventos de ciclo de vida que se adapten al modelo moderno. En el momento de redactar este texto, Chrome también está en proceso de descontinuar unload; por lo tanto, consulte la documentación actual del navegador para conocer el estado más reciente.
El listener ni siquiera tiene que provenir de tu propio código. Los fragmentos de análisis, las etiquetas publicitarias, los widgets de chat de soporte y las dependencias antiguas a veces registran uno internamente. Pasa la revisión de código porque no tiene efecto visible, pero puede eliminar una optimización a nivel del navegador en cada visita a esa página.
Sustituir unload no consiste simplemente en cambiar el nombre del evento. El sustituto adecuado depende de la función real del código:
- Los datos de análisis pueden enviarse con
navigator.sendBeacon()o una solicitudfetchconfigurada adecuadamente (por ejemplo, conkeepalive) cuando la visibilidad de la página cambia a oculta. - Las advertencias sobre datos de formulario no guardados pueden utilizar
beforeunload, pero ese listener solo debe agregarse mientras haya cambios no guardados y eliminarse una vez que se guarden, en lugar de permanecer activo durante toda la vida útil de la página.
La lección general: salir de una página no equivale al final de su ciclo de vida. El código basado en esa premisa termina siendo menos fiable y más lento de lo necesario.
Detección de la restauración con pageshow y pagehide
Dos eventos delimitan el período oculto. pagehide se dispara cuando una navegación saca el documento de la pantalla, y pageshow se dispara cuando la navegación vuelve a hacer que un documento sea el actual, lo cual incluye una restauración desde bfcache. Ambos eventos tienen una propiedad persisted. En pageshow, persisted es true cuando la página salió de bfcache; en pagehide, indica que el navegador está intentando mantener la página. A diferencia de unload, pagehide está diseñado para coexistir con bfcache.
El fragmento a continuación muestra la forma básica. El manejador pageshow devuelve el resultado de inmediato en una carga normal, por lo que la lógica de actualización solo se ejecuta cuando vuelve a cargarse una página guardada. El manejador pagehide pausa las conexiones en tiempo real cada vez que la página se oculta, independientemente de si termina almacenada en caché o no. Las funciones auxiliares son marcadores de posición para la lógica de su propia aplicación.
window.addEventListener("pageshow", (event) => {
if (!event.persisted) {
return;
}
refreshSessionState();
refreshTimeSensitiveData();
});
window.addEventListener("pagehide", () => {
pauseRealtimeConnections();
});
Observe lo que no hace: no vuelve a cargar todo. Ejecutar nuevamente toda la capa de datos en cada restauración anularía gran parte de los beneficios. El objetivo es actualizar solo la información cuya exactitud pueda cambiar significativamente mientras la página permanece suspendida.
Eso depende de la página:
- Una página principal de noticias podría querer buscar titulares actualizados.
- La pantalla de cuenta podría necesitar confirmar que la sesión no se cerró en otra pestaña.
La verdadera mejora es que la aplicación ahora puede distinguir entre una carga nueva y una restauración. Sin esa distinción, los equipos suelen tener que elegir entre dos opciones poco satisfactorias: no actualizar nunca y arriesgarse a contenido obsoleto, o volver a ejecutar toda la inicialización y perder gran parte de la velocidad. Una aplicación que comprende bfcache trata la restauración como una fase distinta del ciclo de vida, manteniendo lo que sigue siendo válido y actualizando lo que ya no se puede confiar.
Cuando el estado restaurado se convierte en un problema de corrección
La restauración instantánea parece algo completamente positivo hasta que la página conservada contiene contenido que ya no debería mostrarse.
Considere a un usuario que abre el panel de control de su cuenta, pasa a otra página, cierra sesión en una segunda pestaña y finalmente hace clic en Atrás. La captura de pantalla restaurada podría seguir mostrando detalles de la sesión anterior. El navegador no infringe ninguna regla de seguridad al recordar una página dentro de la misma sesión de navegación, pero su aplicación podría necesitar verificar que la sesión siga siendo válida antes de permitir cualquier interacción adicional.
Los datos sensibles al tiempo plantean el mismo problema. Es posible que un boleto haya sido comprado en otra pestaña, que un colega haya editado un registro o que una ventana de disponibilidad ya se haya cerrado. Una página restaurada puede ser una imagen perfectamente fiel del pasado, pero aún así ser la fuente de información incorrecta para el presente.
Una reacción común es desactivar por completo el caché. Por lo general, eso es demasiado drástico. La página puede mantener su estado DOM e interacción al actualizar solo los datos que deben estar actualizados, y pageshow es el lugar adecuado para realizar esa sincronización. La guía oficial indica claramente que las páginas cuyo contenido cambia con frecuencia pueden cargar datos frescos una vez se restablezcan, en lugar de optar por desactivar completamente el bfcache.
Las páginas sensibles desde el punto de vista de la seguridad siguen necesitando políticas deliberadas de caché y sesiones, y la forma en que los navegadores tratan encabezados como Cache-Control: no-store ha ido cambiando. Por ejemplo, Chrome ha comenzado a permitir que ciertas páginas con no-store se incluyan en el bfcache cuando se cumplen condiciones específicas. Evite basarse en una suposición simplista sobre cómo cada navegador maneja ese encabezado, y consulte la documentación actual del navegador antes de confiar en ella.
El enfoque más seguro es decidir de forma explícita:
- qué estado se puede conservar sin riesgos tal como está;
- qué estado debe volver a validarse después de una restauración;
- qué acciones del usuario siempre requieren una nueva verificación de autorización en el servidor, independientemente de lo que muestre la interfaz actualmente.
Esa última categoría es la más importante. El cliente puede actualizar su vista, pero el servidor nunca debe confiar en una página restaurada para demostrar que un usuario sigue conectado.
Pausar conexiones de larga duración durante la suspensión
Las conexiones abiertas como WebSockets, los manejadores de IndexedDB, los bloqueos compartidos y las solicitudes en proceso complican el funcionamiento de bfcache, ya que el navegador debe congelar la página mientras otras pestañas y documentos siguen en ejecución. Una página congelada que posee un bloqueo compartido o una conexión a base de datos abierta puede obstaculizar a las páginas que aún están activas. Las reglas de elegibilidad difieren entre navegadores, y en algunos casos una conexión abierta o activa es suficiente para mantener una página fuera del caché. El patrón recomendado es liberar o suspender dichos recursos cuando la página se oculta y luego restablecerlos con cuidado una vez que vuelva a ser visible.
Eso no significa que sea necesario eliminar cada uno de los listeners o observadores. Aquellos elementos que se encuentran completamente dentro de la página, como los listeners del DOM, suelen poder permanecer inactivos sin causar problemas. En cambio, hay que prestar atención a todo aquello que se conecta con sistemas externos más allá del documento inactivo, o a cualquier componente que no debería parecer activo cuando nadie está viendo la página.
Por ejemplo, un panel de control en tiempo real podría cerrar su conexión WebSocket en el evento pagehide y abrir una nueva cuando la página se restaure. La implementación debe protegerse contra duplicados, ya que tanto el proceso normal de inicio como el manejo de restauración podrían intentar conectarse; una única función connect() que verifique si ya existe una conexión es una forma sencilla de lograr que sea idempotente. Un wrapper para IndexedDB podría cerrar su conexión a la base de datos antes de suspenderla y volver a abrirla solo cuando sea necesario.
Esta disciplina trae beneficios que van más allá de bfcache. Obliga al equipo a decidir quién es el responsable de cada recurso externo, cuándo debe existir y qué ocurre cuando el documento se vuelve inactivo. Esos acuerdos son igualmente valiosos para pestañas en segundo plano, dispositivos móviles que congelan las páginas y sesiones interrumpidas.
Las aplicaciones de página única siguen beneficiándose
bfcache funciona con las navegaciones entre documentos que gestiona el navegador. Los cambios de ruta manejados en JavaScript, donde no se carga un nuevo documento, quedan fuera de su alcance. Pasar de /products a /products/42 mediante un enrutador del lado del cliente sigue siendo algo que la SPA y su framework gestionan por sí mismos.
Aun así, las SPAs tienen mucho que ganar. Las personas suelen abandonar una SPA para dirigirse a una página separada, un sitio de terceros, un proveedor de inicio de sesión o incluso a otra aplicación, y regresar más tarde. En ese caso, el navegador puede volver a cargar toda la aplicación en lugar de buscar los paquetes nuevamente, volver a renderizar cada componente y ejecutar el proceso de configuración de nuevo. La guía oficial señala explícitamente esta ventaja, al tiempo que indica que los cambios de ruta dentro de la aplicación están fuera del control de bfcache.
Esos regresos pueden exponer suposiciones a nivel del framework:
- Se espera que la inicialización de la raíz se ejecute cada vez que el documento se vuelve visible;
- Un layer de datos que asume que sus cachés siempre comienzan vacíos;
- Una conexión en tiempo real que se recrea mientras la anterior también vuelve a activarse;
- Un componente que muestra cifras que estaban correctas en el momento de la salida y que ahora podrían estar desactualizadas.
Ningún framework abstracta el ciclo de vida propio del navegador. Ya sea que uses React, Vue, Angular o Svelte, el código sigue ejecutándose dentro de un documento que el navegador puede conservar y restaurar. Un framework es bueno para organizar el estado, pero no tiene forma de determinar por ti qué datos empresariales siguen siendo válidos después de una suspensión. En React, por ejemplo, los componentes no se vuelven a montar al restaurarse bfcache, por lo que los efectos que se ejecutaron al montar no se volverán a ejecutar; un listener pageshow es la forma en que la aplicación sabe que ha pasado tiempo.
Muchos desarrolladores front-end conocen mucho mejor el comportamiento de montaje y desmontaje de su framework que el ciclo de vida del documento del navegador. bfcache demuestra que el montaje y el desmontaje describen solo una capa de lo que realmente ocurre con una página.
Probar bfcache en minutos
bfcache sigue siendo ignorado en parte porque es difícil determinar si está funcionando. Volver a la página ya puede parecer rápido, y el panel de Red no es de mucha ayuda, ya que una página restaurada omite por completo la secuencia habitual de solicitudes.
Chrome DevTools incluye una herramienta específica para verificar bfcache, que se encuentra en el panel de Aplicaciones. Al ejecutarla, Chrome navega hacia afuera y de vuelta, y la herramienta indica si la página fue restaurada. Cuando la página no cumple los requisitos, DevTools muestra las razones concretas y señala qué marcos son responsables.
Incluya esta verificación como parte de las pruebas habituales de rendimiento para páginas importantes, especialmente resultados de búsqueda, artículos, listas de productos, paneles de control y cualquier pantalla a la que los usuarios accedan con frecuencia. Ejécutela también con scripts de terceros activados, ya que la capacidad de restauración a menudo se ve bloqueada por código externo a su propio paquete.
En cuanto a los datos de campo, la información notRestoredReasons que se muestra a través de la API de rendimiento (en la entrada de tiempo de navegación) puede explicar por qué las navegaciones reales del historial no se restauraron. Este soporte no es universal, por lo que debe considerarse como una herramienta de medición progresiva y no como algo del cual dependa su código en todos los navegadores. Sigue siendo valioso, ya que ninguna prueba local puede reproducir cada extensión de navegador, iframe de terceros, conexión abierta, encabezado o condición del navegador que traigan sus usuarios. Una alternativa ligera es registrar event.persisted de pageshow en sus análisis para medir su tasa real de visitas.
No espere que cada navegación por el historial acceda al caché. Los navegadores eliminan las capturas para gestionar la memoria, y algunos bloqueos están fuera del control de la página. El objetivo realista es eliminar los bloqueos evitables y gestionar correctamente la restauración cada vez que ocurre.
Conclusión
bfcache es fácil de pasar por alto precisamente porque nada en su código lo activa. No existe ningún paquete, componente ni archivo de configuración que haga que parezca parte de la arquitectura; el navegador simplemente intenta ofrecer a los usuarios una mejor experiencia. Esa invisibilidad tiene efectos en ambos sentidos: cuando funciona, los usuarios disfrutan de una navegación por el historial casi instantánea con casi ningún código adicional; cuando se bloquea, el sitio se ralentiza sin presentar ningún error; y cuando la restauración no se gestiona correctamente, vuelve a aparecer un estado obsoleto porque la aplicación asume que volver siempre implica recargar.
Una lista de verificación práctica para tus páginas más visitadas:
- Elimina los oyentes de
unload, incluidos aquellos añadidos por scripts de terceros, y traslada su funcionalidad apagehide,visibilitychangeosendBeacon(). - Asocia
beforeunloadsolo cuando haya cambios no guardados. - Cierra o pausa las conexiones externas en
pagehidey vuelve a conectarte de manera idempotente al restaurar la página. - Utiliza
pageshowconevent.persistedpara actualizar únicamente los datos que pueden volverse obsoletos, y mantiene las verificaciones de autorización en el servidor. - Ejecuta la prueba de caché hacia atrás/adelante en las Herramientas de Desarrollo y sigue las tasas reales de restauración donde lo permitan las APIs.
El cambio fundamental radica en cómo se concibe una página. En lugar de una línea recta desde su creación hasta su destrucción, su ciclo de vida puede incluir fases de configuración, ocultamiento, congelamiento, restauración, reconciliación y continuación. Los equipos invierten mucho esfuerzo en acelerar la reconstrucción de las páginas, mientras que el navegador a menudo está listo para omitir por completo esa reconstrucción y devolver a los usuarios la página que ya tenían.