El bloqueo de renderizado y lo que puede fallar
Por qué el navegador espera al documento nuevo, cómo controlar esa espera con blocking render, y el catálogo completo de motivos por los que una transición no ocurre.
Una transición entre documentos tiene un problema que la de mismo documento no tiene: el estado nuevo no está listo cuando se captura el antiguo, hay que descargarlo. El navegador resuelve ese hueco esperando, y esa espera es el punto exacto donde una transición pasa de ser una mejora a ser una molestia. Controlarla es la diferencia entre una navegación que se siente instantánea y una que se siente rota.
- Explicar qué espera el navegador entre las dos capturas y por qué.
- Usar el bloqueo de renderizado para que la captura nueva sea la correcta.
- Enumerar las causas por las que una transición entre documentos no ocurre.
- Diagnosticar una transición que se ve a medio construir.
La espera y el estado a medias
La secuencia entre documentos tiene un hueco que no existe en la de mismo documento. Cuando el usuario pulsa el enlace, el navegador captura el estado antiguo y después tiene que esperar a que el documento nuevo llegue, se parsee y esté listo para renderizarse. Solo entonces puede capturar el estado nuevo.
El problema es decidir qué significa “listo”. Un documento HTML se parsea de forma incremental: hay un momento en que existe la cabecera pero no el contenido, y otro en que existe todo el marcado pero las imágenes aún no han llegado. Si el navegador captura demasiado pronto, la captura nueva es de una página a medio construir, y lo que el usuario ve es una transición hacia un esqueleto que un instante después se rellena de golpe. Es peor que no tener transición.
El criterio por defecto del navegador es razonable pero genérico. Para tener control fino existe el bloqueo de renderizado: se le puede decir al navegador que no considere el documento listo para pintar hasta que un elemento concreto exista.
<head>
<link rel="expect" href="#contenido-principal" blocking="render">
</head>
<body>
<header>...</header>
<main id="contenido-principal">...</main>
</body>
Con esa línea, el navegador no pinta —y por tanto no captura el estado nuevo— hasta que el elemento con ese identificador esté conectado y parseado. La captura nueva incluye entonces el contenido real y la transición aterriza donde debe.
El estado de soporte de esta pieza es más estrecho que el de la propia transición: el atributo blocking="render" está en Chromium desde la 105 y en Safari desde la 18.2, pero rel="expect" solo está en Chromium desde la 124. En Safari y Firefox el link se ignora, así que la técnica es una mejora sobre una mejora. El modo de fallo es benigno —vuelves al criterio por defecto del navegador— pero conviene no diseñar contando con ella.
El presupuesto de tiempo
Que el navegador espere significa que hay un límite. Si el documento nuevo tarda demasiado, el navegador abandona la transición y hace la navegación sin animar. Ese comportamiento es un salvavidas, no una garantía de calidad: el usuario que llega a él ya se ha quedado mirando la página vieja congelada durante ese tiempo.
La consecuencia arquitectónica es importante y se enuncia en una frase: una transición entre documentos no puede ser más rápida que la respuesta del servidor. Si tu página tarda ochocientos milisegundos en responder, la transición durará al menos ochocientos milisegundos, por muy bien que hayas escrito las animaciones.
Eso reordena las prioridades de una forma que sorprende. Las tres cosas que más mejoran una transición entre documentos no tienen nada que ver con la animación:
El tiempo hasta el primer byte. Es el suelo de toda la operación. Una página estática servida desde una red de distribución transiciona de forma instantánea; la misma página generada en cada petición contra una base de datos, no.
Reservar espacio para las imágenes. Con atributos width y height o con aspect-ratio, el layout de la página nueva es correcto desde el primer parseo y la captura no cambia de tamaño después. Sin ellos, la captura nueva se toma con un layout que cambiará, y la transición aterriza en un sitio que se mueve inmediatamente después.
Cargar las fuentes con métricas ajustadas. Una fuente web que llega tarde y tiene métricas distintas de la de reserva recompone el texto después de la captura, con el mismo efecto.
Ninguna de esas tres es trabajo de animación y las tres importan más que cualquier cubic-bezier.
El catálogo de fallos
Cuando una transición entre documentos no ocurre, la causa está en esta lista.
| Causa | Cómo se comprueba |
|---|---|
Una de las dos páginas no declara @view-transition |
Buscar la regla en el CSS de las dos |
| La navegación cruza de origen | Comparar los orígenes, incluidas las redirecciones |
| La navegación es una recarga | El tipo es reload, no transiciona nunca |
| La navegación viene de la barra de direcciones | Solo traverse transiciona desde la interfaz |
| Nombres duplicados en alguno de los dos documentos | Escuchar el rechazo de ready en los dos lados |
| El documento nuevo tarda demasiado | Mirar el tiempo hasta el primer byte |
| El navegador es Firefox | Es lo esperado, no es tu código |
| La pestaña dejó de estar visible | No hay transición si nadie mira |
Para las tres primeras, un diagnóstico que responde en un segundo:
window.addEventListener('pageswap', (e) => {
console.log('pageswap', {
hayTransicion: Boolean(e.viewTransition),
tipo: e.activation.navigationType,
desde: e.activation.from?.url,
hasta: e.activation.entry.url,
});
});
Si hayTransicion es false, el problema está en las condiciones de la navegación o en la regla. Si es true pero no se ve nada, el problema está en el otro documento o en un nombre duplicado, y toca poner el mismo aviso en pagereveal con un catch sobre ready.
Hay un efecto secundario de activar transiciones entre documentos que nadie anticipa y que es, en realidad, lo más valioso que aportan. Sin transición, una navegación lenta se percibe como una espera con feedback: la página se queda, aparece la barra de progreso del navegador, el cursor cambia, y el usuario entiende que algo está pasando. Con transición, el navegador se queda mostrando la captura del estado antiguo sin ningún indicador, porque a efectos visuales la página sigue ahí, entera y quieta. El resultado es que la misma latencia que antes se leía como “está cargando” ahora se lee como “la página se ha colgado”. Las transiciones no hacen tu sitio más lento: hacen visible una lentitud que la interfaz del navegador estaba disimulando. Esto lleva a una conclusión de método que conviene tomarse en serio: no actives transiciones entre documentos en un sitio que no sea rápido, porque lo empeorarás. Y al revés, si el sitio ya es rápido —HTML estático, respuesta bajo cien milisegundos, imágenes con dimensiones reservadas— las transiciones son casi gratis y el resultado es indistinguible de una aplicación de página única. El orden correcto de trabajo es primero el rendimiento y después la transición, nunca al contrario. Hay una segunda lectura, más útil todavía: como la transición amplifica la latencia, es un instrumento de medida. Si al activarla notas la espera, tu sitio ya era lento y tus usuarios ya lo sufrían; lo único que ha cambiado es que ahora tú también lo notas.
Una nota sobre la caché de retroceso
El navegador guarda páginas completas en una caché para que retroceder y avanzar sea instantáneo. Cuando una navegación se sirve desde esa caché, el documento no se vuelve a construir: se restaura tal cual, con su DOM y su estado.
Eso interactúa bien con las transiciones y merece conocerse porque explica un comportamiento asimétrico que si no desconcierta. Una navegación hacia delante paga la descarga; el retroceso, si la página está en caché, no paga nada y la transición es instantánea. El usuario percibe que volver es más rápido que ir, lo cual es cierto y es bueno.
Las dos consecuencias prácticas. Primera: los tipos de transición se limpian solos al acabar, así que una página restaurada de la caché no arrastra los tipos de su vida anterior. Segunda, y menos agradable: los view-transition-name que asignaste desde JavaScript sí se quedan puestos, porque forman parte del DOM restaurado. Si nombraste una miniatura al salir y no lo limpiaste, al volver por la caché ese nombre sigue ahí y puede colisionar con el que asignes en pagereveal. La limpieza en finished, con finally, no es opcional tampoco aquí.