Acelera tu sitio web con señales iniciales y señales de prioridad
Los sitios web siguen tardando demasiado en cargarse. En el momento más importante del ciclo de vida de la carga de las páginas, tu conexión suele estar prácticamente inactiva. Kazuho Oku, ingeniero de Fastly, ha ideado una innovadora tecnología que pretende aprovechar mejor esos primeros instantes vitales.
Tratas de entrar en un sitio web con tu móvil, pero acabas con la pantalla en blanco durante varios segundos. Te suena, ¿verdad? A nadie le gusta tener que esperar a que se carguen los elementos personalizados de un sitio web (p. ej., las fuentes) y sería lógico pensar que esos elementos tan esenciales se deben cargar lo antes posible. Para solucionar este problema, se creó Link rel=preload para que el navegador analice los encabezados HTTP de una página antes de empezar a leer el contenido, un momento ideal para ordenar al navegador que empiece a descargar un activo que sabemos que vamos a necesitar.
La web es lenta por naturaleza
Analicemos qué ocurre si no utilizas «preload». El navegador empieza a cargar recursos cuando detecta que los necesita. Los recursos que más rápido se cargan son los que están en el HTML durante el análisis inicial del documento (como <script>
, <link rel=stylesheet>
y <img>
).
Los elementos y procesos relativos a la generación del «render tree» (es decir, el lugar donde se decide si necesitas una fuente personalizada) tardan más en cargarse. Por ejemplo, la hoja de estilos, su análisis y la generación del modelo de objetos CSS; solo entonces podrás generar el «render tree».
Aún más lentos en cargarse son los recursos que añades al documento a través de los cargadores de JavaScript, que se activan mediante un evento del tipo DOMContentLoaded
. Si agregas todos estos elementos, se obtiene una cascada nada optimizada y, más o menos, carente de sentido. Hay muchísimo tiempo de inactividad en la red, y los recursos se cargan demasiado pronto o demasiado tarde:
Link rel=preload es muy útil
En los últimos años, hemos encontrado la forma de mitigar este problema utilizando Link rel=preload. Por ejemplo:
Link: </resources/fonts/myfont.woff>; rel=preload; as=font; crossorigin
Link: </resources/css/something.css>; rel=preload; as=style
Link: </resources/js/main.js>; rel=preload; as=script
Si ejecutamos estas líneas de código, el navegador puede empezar a cargar los recursos en cuanto se reciben los encabezados y antes de empezar a analizar el cuerpo HTML:
Se trata de una mejora significativa, sobre todo en páginas grandes y recursos esenciales que, de otro modo, no se detectarían a tiempo (p. ej., fuentes o archivos de datos con los que poner en marcha una aplicación JavaScript).
Sin embargo, sigue habiendo margen de mejora: el navegador no tiene actividad desde que termina de enviar la petición hasta que recibe los primeros bytes de la respuesta (se representa en el tramo verde más largo del gráfico de la petición inicial).
Aprovecha el «tiempo de reflexión del servidor» con las señales iniciales
El servidor sí que está ocupado: genera la respuesta y determina si es correcta o no. Tras varias consultas a bases de datos, llamadas a la API, autenticaciones, etc., es posible que decidas que la respuesta adecuada es un error 404.
Unas veces, el tiempo de reflexión del servidor es inferior a la latencia de la red; otras, es mucho mayor. Sin embargo, lo que importa de verdad es conseguir que no se solapen. Mientras el servidor reflexiona, no se envían datos al cliente.
Seguro que, ya antes de generar la respuesta, conoces algunos de los estilos y fuentes que deben cargarse para visualizar la página. Al fin y al cabo, tus páginas de error suelen utilizar el mismo estilo y diseño que tus páginas normales. Sería fantástico si pudieras enviar esos encabezados Link: rel=preload
antes de que el servidor empiece a reflexionar. Esa es precisamente la función que realiza el código de estado Early Hints (señales iniciales). Sus especificaciones vienen detalladas en la norma RFC8297, elaborada por el grupo de trabajo sobre HTTP y, en concreto, por Kazuho Oku, con el que trabajo en Fastly. Bueno, ahora alucina con varias líneas de estado en una sola respuesta:
HTTP/1.1 103 Early Hints
Link: <some-font-face.woff2>; rel="preload"; as="font"; crossorigin
Link: <main-styles.css>; rel="preload"; as="style"
HTTP/1.1 404 Not Found
Date: Fri, 26 May 2017 10:02:11 GMT
Content-Length: 1234
Content-Type: text/html; charset=utf-8
Link: <some-font-face.woff2>; rel="preload"; as="font"; crossorigin
Link: <main-styles.css>; rel="preload"; as="style"
El servidor puede escribir el primer estado de respuesta «informativa» tan pronto como reciba la petición y enviarlo a la red. A continuación, puede ponerse a decidir cuál va a ser la respuesta real y a generarla. Mientras tanto, en el navegador, puedes empezar la precarga con mucha más antelación:
Esta novedad va a exigir algunas modificaciones en el funcionamiento de los navegadores, los servidores y las CDN, y los proveedores de algunos navegadores ya han expresado sus reservas al respecto, alegando que implementarlas va a ser complejo. Así que no está claro cuándo se van a implementar. Puedes informarte de los avances en su implementación a través de los canales de seguimiento de Chrome y Firefox.
Aunque no hay un plazo previsto, esperamos incorporar a la gama de productos de Fastly la funcionalidad que te permita emitir señales iniciales sin que ello te impida enviar la petición al origen. Todavía no hemos decidido cómo darles visibilidad en VCL, así que ¿por qué no nos cuentas cómo querrías que aparecieran?
¿Y qué hacemos con la inserción del servidor HTTP/2?
HTTP/2 incorpora una nueva función denominada inserción del servidor que, al parecer, solventa el mismo problema que enviar Link rel=preload en una respuesta a un código de estado Early Hints. Aunque la inserción funciona de verdad (hasta puedes generar inserciones personalizadas desde el edge con Fastly), hay quienes se oponen frontalmente a su uso, alegando una serie de razones:
El servidor no sabe si el cliente ya tiene el recurso, con lo que tiende a realizar inserciones cuando no es necesario. Debido al almacenamiento en búfer de la red y a la latencia, lo normal es que el cliente no tenga la oportunidad de cancelar la inserción antes de recibir todo el contenido. (Sin embargo, este problema quizás ya tenga una solución: el encabezado Cache Digest, en el que está trabajando Kazuho en colaboración con Yoav Weiss, de Akami).
Los recursos insertados están vinculados a la conexión, por lo que resulta fácil insertar un recurso que un cliente acaba por no utilizar, porque intenta capturarlo a través de una conexión diferente. Es posible que los clientes tengan que utilizar otra conexión porque el recurso esté ubicado en un origen diferente con el que no comparta el certificado TLS, o porque se capture en un modo de credenciales diferente.
La inserción HTTP/2 no se ha implementado de manera sistemática en los principales navegadores. Esta circunstancia dificulta hacer previsiones sobre si funcionará en tu caso de uso concreto.
Desventajas aparte, las señales iniciales y la inserción del servidor ofrecen algunas concesiones. Por ejemplo, facilitan un uso de la red más eficaz a cambio de un trayecto más de ida y vuelta. Si prevés una latencia corta de la red y un tiempo largo de reflexión del servidor, las señales iniciales probablemente sean la solución más adecuada.
Sin embargo, no siempre lo son. Imaginemos por un momento que los humanos llegamos a Marte a medio plazo. Si nos conectamos a internet desde allí, sufriríamos una latencia de 20-45 minutos por cada viaje de ida y vuelta. O sea, que ese viaje de ida y vuelta adicional sería extremadamente costoso, mientras que el tiempo de reflexión del servidor sería, en comparación, irrelevante. En este ejemplo, la inserción del servidor ganaría con diferencia. Pero, si la navegación marciana por la web es una realidad algún día, lo más probable es que para ello tengamos que descargarnos algún tipo de paquete, un poco en la línea teorizada por los paquetes web actuales y las propuestas de intercambios firmados.
Valor añadido: más rapidez en la reducción de peticiones a una sola
Aunque el caso de uso principal asociado a las señales iniciales es el navegador, las CDN posiblemente puedan beneficiarse también. Si Fastly recibe una avalancha de peticiones del mismo recurso al mismo tiempo, lo que solemos hacer es enviar únicamente una de ellas al origen y poner las demás en cola. A este proceso lo llamamos «reducción de peticiones a una sola». Si el origen nos acaba enviando una respuesta que incluye el encabezado Cache-Control: private
, tenemos que sacar de la cola a todas las peticiones que estaban en espera y enviarlas una a una al origen porque no está permitido utilizar esa sola respuesta para satisfacer varias peticiones.
No podemos tomar esa decisión hasta haber recibido la respuesta a la primera petición. Pero ¿qué pasaría si tuviéramos compatibilidad con las señales iniciales, el servidor emitiera una respuesta al código de estado Early Hints y en esta se incluyera el encabezado Cache-Control? Sabríamos con mucha más antelación que la cola no se puede reducir a una sola petición. En su lugar, podríamos enviar de inmediato al origen todas las peticiones que están en cola.
Ordena el contenido menos esencial con señales de prioridad
Las señales iniciales son una forma extraordinaria de acceder a algunos rincones del espacio más valioso de la cascada: si la red está inactiva, el usuario tiene que esperar, solo hay una petición en curso y la pantalla se queda en blanco. Sin embargo, una vez que se carga el HTML y la página se analiza, el número de recursos que deben recuperarse crece de manera exponencial. En estos casos, lo importante no es cargar elementos lo más rápido posible, sino hacerlo en el orden correcto. Los navegadores suelen utilizar una matriz de heurística increíblemente compleja para decidir de forma autónoma qué prioridad de carga deben seguir. Si prefieres ignorar la decisión que tome el navegador, en el futuro quizás puedas hacerlo a través de señales de prioridad:
<script src="main.js" async importance="high"></script>
<script src="non-critical-1.js" async importance="low"></script>
Este nuevo atributo de relevancia posiblemente permita a los desarrolladores controlar el orden de descarga de los recursos si la demanda de la red es alta. Además, quizás permita que el navegador retrase la descarga de recursos menos prioritarios hasta que la CPU y la red tengan capacidad suficiente, o hasta que se cumplan ciertas condiciones que fije el dispositivo.
¿Puedo usar ya estas señales?
Ni los códigos de estado de señales iniciales ni los de señales de prioridad van a estar disponibles inmediatamente. H2O, el servidor HTTP/2 que Fastly utiliza y con el que es compatible su gama de productos, acaba de conseguir la compatibilidad con las señales iniciales (consulta las solicitudes de incorporación 1727 y 1767). Además, el equipo de Chrome tiene previsto implementar las señales de prioridad en su navegador, así como el seguimiento activo de respuestas 1xx, para que se proporcionen datos tras adoptarlos. Hasta entonces, ¿por qué no pruebas a marcar tendencia y te conviertes en pionero de las señales iniciales? ¡Adelante, y mucho ánimo!