Escrito por Ramón Saquete
Índice
Vamos a estudiar un técnica de WPO avanzada que bien utilizada nos va a permitir conseguir una mejora considerable de las métricas de WPO, para los usuarios que entran en nuestro sitio por primera vez y sin afectar, o incluso mejorando, las de los visitantes recurrentes. Se trata de una de las mejoras que ofrece el relativamente nuevo protocolo HTTP/2, que no es más que la capacidad de responder a una petición del cliente con archivos adicionales al que ha solicitado, empujando o forzando la descarga de los mismos desde el servidor, de ahí que esta técnica reciba el nombre de «HTTP/2 Server Push«.
¿Cómo se debe aplicar HTTP/2 Server Push?
Habitualmente, cuando pedimos una página desde el navegador, el servidor devuelve el HTML y el navegador lo analiza con un algoritmo llamado escáner de precarga, con el que busca los recursos que tiene que pedir al servidor antes de empezar a montar la página. Lo que hace HTTP/2 Server Push, es permitirnos adelantar la carga de los recursos para que se descarguen al mismo tiempo que el HTML, evitando así que el navegador tenga que pedirlos después de descargar y analizar el HTML.
Para poder usar esta característica, tanto el cliente como el servidor deben estar preparados para utilizarla. Actualmente, todos los navegadores ya lo permiten, pero no todos los servicios web con HTTP/2 admiten el uso de HTTP/2 Server Push, para ello normalmente deberemos disponer de las versiones más actualizadas del servicio web y sus respectivos módulos o extensiones para HTTP/2. No son muchos los servicios de hosting o incluso de CDN que permiten su uso actualmente.
Para aplicar la técnica correctamente, debemos adelantar los recursos críticos para visitantes nuevos: en primer lugar, recordemos que los recursos críticos son aquellos necesarios para visualizar la parte de la página que visualiza primero el usuario. Por ejemplo, en una web en la que se genere el HTML en el cliente con JavaScript, éste será un recursos crítico, pero en otro caso podría no serlo. Por lo general, siempre van a ser recursos críticos partes del CSS, algunas fuentes y, excepcionalmente, algunas imágenes, es decir, todo aquello que se usa para pintar las partes importante de la parte superior de la página (el «above the fold») y hacerla interactiva. Sin embargo, no es recomendable adelantar la carga de todos los recursos críticos, ya que podríamos ralentizar la descarga del HTML, que dentro de los recursos críticos, es el de mayor importancia. Así que recomiendo, por lo general, no adelantar imágenes y probar siempre, con alguna herramienta de WPO como Lighthouse o webpagetest.org, si la forma de aplicar esta técnica está optimizando o desoptimizando la carga, el pintado y la interactividad de la página.
Aplicar esta técnica sobre el CSS crítico, tiene la ventaja de que deja de ser necesario empotrarlo en el HTML, de forma que se puede cachear en el navegador, así que no aumenta el tamaño del HTML para las visitas recurrentes, por lo que éstas también tendrán una pequeña mejora. Igualmente, si se adelanta una imagen, no será necesario empotrarla en el HTML y se podrá cachear.
Implementación de HTTP/2 Server Push
En la implementación, además de empujar algunos recursos, también tenemos la opción de precargarlos, estudiaremos la diferencia a continuación y como se implementa cada caso.
Sin precarga y sin Push
Veamos primero un caso sin optimizar. Supongamos que tenemos una página HTML que enlaza a un CSS y a una imagen, luego dentro del CSS se enlaza a una fuente. La descarga en HTTP/2 sucedería de forma normal de la siguiente manera:
La imagen y la hoja de estilo, se descargan a la vez porque las dos están referenciadas desde el HTML y HTTP/2 permite enviar varios archivos a la vez multiplexados.
Push
Ahora queremos optimizar el caso anterior y suponemos que el CSS completo y la fuente son un recursos críticos, por lo que queremos adelantar la descarga de estos recursos para que suceda de la siguiente forma:
Para implementarlo sólo debemos incluir los recursos que queremos «empujar», en la cabecera del archivo solicitado con el parámetro Link de la siguiente forma:
Link: </estilo.css>;rel=preload;as=style,
</fuente.woff>;rel=preload;as=font
En el atributo «as» podemos tener estos valores o podemos omitirlo si es un documento HTML.
Precarga
Si incluimos la palabra nopush, en lugar de enviarse el recurso al mismo tiempo, se pedirá cuando el navegador analice la cabecera de la petición durante la descarga del HTML. Llamaremos, a esto que no es un push, precarga.
Link: </app/style.css>; rel=preload; as=style; nopush
Este comportamiento, es similar al que tendríamos si usamos la misma directiva, en el HTML de la página, de la siguiente forma:
<link rel="preload" as="style" href="/app/style.css" />
Haciendo esto último el navegador se descarga el HTML y al encontrar esta etiqueta en la cabecera pide el recurso al servidor. Esto no tiene mucho sentido si vamos a pedir una hoja de estilo que va encontrar igualmente en la cabecera del HTML, pero sí lo puede tener para adelantar la carga de una fuente que esté enlazada desde el CSS o cualquier recurso que se encuentre en el tercer nivel del árbol de dependencias de los recursos y no disponemos de HTTP/2 Server Push, ya que evitamos que el navegador tenga que descargar y analizar el CSS para pedirlo. Gráficamente:
Recursos empujados en la cascada de red
Cuando se implemente un push, se debe comprobar en la cascada de red del navegador si los recursos que queremos están siendo empujados. Si hemos escrito mal la cabecera, no aparecerán empujados por lo que el iniciador de la descarga será «Parser» que es el analizador del escáner de precarga, y si el servidor no permite el uso de HTTP/2 server push, aparecerá que el iniciador de la descarga ha sido «Other» puesto que se habrá lanzado la petición al ver la cabecera HTTP, pero no será un envío simultaneo con el HTML si no que se comportará casi como una precarga hecha desde la cabecera del HTML.
Veamos algunos ejemplos de cada caso en la cascada de descargas de Google Chrome, que es el único que muestra los Push correctamente:
En esta primera captura vemos una imagen cuyo iniciador es «Other» y que no se descarga hasta que empieza a analizar el CSS, veamos que pasa si la adelantamos con rel=»preload» en la cabecera del HTML:
El iniciador ahora es el analizador del HTML (parser), ya que ahora se carga al empezar a analizar éste.
Veamos que ocurre con un Push:
En este caso se ha empujado un CSS, por lo que cuando se ha terminado de descargar el HTML, éste ya se ha descargado.
Cuidado con la cache
Se debe tener en cuenta en la implementación de los push que esta optimización debe aplicarse sólo a usuarios nuevos, ya que los recurrentes tendrán esos datos en la cache, por lo que no es necesario enviarles de nuevo estos datos, ya que si se han establecido correctamente las cabeceras de cache del protocolo HTTP, estas se enviarán con los archivos «empujados».
Si se empuja un archivo cacheado, el envío del mismo se cancelará cuando el navegador descubra que ya tiene ese recurso en cache, pero aun así se malgastan recursos.
Como en la petición, no podemos saber a priori si el usuario es nuevo o recurrente, la mejor estrategia que podemos aplicar, es establecer una cookie en la primera visita, donde indiquemos en el valor de la misma, la versión de los recursos que se han enviado. De esa forma cuando nos llegue una nueva petición de ese cliente, sabremos si tenemos o no que hacer el envío de esos recursos por Push. Aunque no tener la cookie no garantiza que los recursos no estén cacheados en el cliente, pero evitamos la mayoría de envíos innecesarios.
En un futuro se espera que se amplíe la especificación para solucionar este problema, pero de momento debe resolverlo el desarrollador.
Implementación específica para el servicio web
Añadir cabeceras a los HTML, no es la única forma de implementar los push. También podemos hacer uso de directivas especificas del servicio web que estemos utilizando, de forma que podremos realizar el push de los recursos incluso antes de que se tenga que empezar a procesar el HTML en el servidor. Por ejemplo, en Apache lo haríamos con la directiva H2PushResource desde un .htaccess y con la que además podríamos indicar la prioridad de unos push sobre otros, si no decidirá la prioridad por tipo de archivo. En el siguiente ejemplo se prioriza la descarga del CSS sobre el JavaScript usando esta directiva:
H2Push on
<Location /index.html>
H2PushResource "/css/estilos-criticos.css" critical
H2PushResource "/js/javascript-critico.js"
</Location>
Formas alternativas de sugerir cómo cargar los recursos y otras consideraciones
Aparte de poder usar el atributo «rel=preload», ya sea en la etiqueta link de HTML o en la cabecera Link de HTTP, también podemos usar los valores rel=prefetch, rel=prerender, rel=preconnect, rel=dns-prefetch y rel=»preconnect» para sugerir al navegador como cargar los recursos con los siguientes significados:
- prefetch: sirve para descargar recursos que se van a ver después de la página actual, por lo que se descargan después de que se haya terminado la descarga del resto de recursos. Safari no la permite.
- prerender: al igual que la anterior, sirve para descargar recursos que se van a ver después de la página actual después de que se haya terminado la descarga del resto de recursos pero, además, el navegador puede iniciar el pintado de estos recursos cuando ya ha pintado la página actual y tiene recursos de CPU y memoria libres. De momento pocos navegadores lo soportan, por lo que se recomienda usar la anterior.
- dns-prefetch: sirve para adelantar la resolución DNS de dominios externos que cargan recursos en nuestra página. Todos los navegadores permiten usarla.
- preconnect: sirve para adelantar la resolución DNS, establecimiento de conexión TCP y negociación TLS (si usa HTTPS). Con esta ahorramos más ciclos de ida y vuelta que con dns-prefetch, pero existen unos pocos navegadores que no lo soportan.
Adicionalmente, podemos añadir media queries para cargar determinados recursos dependiendo del tamaño de pantalla. También puede consultar aquí cómo optimizar las fuentes usadas en una Web.
También podemos aplicar los valores de la etiqueta link que se usan para el SEO, como rel=»canonical» o rel=»alternate», aunque este caso no tiene nada que ver con sugerir la forma en la que se cargan los recursos:
- canonical: sirve para indicar la URL canónica a la araña para evitar que variantes de una misma URL se indexen como contenido duplicado. Enviar este parámetro en la cabecera, en lugar de en el cuerpo del HTML, es la única forma de establecer la URL canónica para archivos que no son HTML y que actualmente se usa sólo para PDFs. Ejemplo:
Link: <http://www.-----.com/articulo.pdf>; rel="canonical"
- alternate: sirve para indicar una versión alternativa de la URL actual a la araña puede ser un idioma o idioma y país distinto o una versión específica para móviles. Ejemplo:
Link: <http://www.-----.com/articulo>; rel="alternate"; hreflang="es-ES" Link: <http://www.-----.com/article>; rel="alternate"; hreflang="en-GB"
- amphtml: sirve para indicar la versión alternativa en AMP de una página que no es AMP. Ejemplo:
Link: <http://www.-----.com/articulo>; rel="amphtml";
Finalmente, podemos encadenar varias cabeceras Link, separándolas por comas en la cabecera HTTP:
Link: <//pagead2.googlesyndication.com>; rel=dns-prefetch,
</js/bootstrap.min.js>; as=script; rel=preload,
</ads.html>; rel=prerender,
</css/bootstrap.min.css>; as=style; rel=preload
Esto ahorra algo de código respecto a introducir una etiqueta Link en el HTML o un nuevo parámetro Link en la cabecera por cada recurso.
Conclusiones
La técnica HTTP/2 Server Push no es fácil de implementar y si se aplica mal, adelantando demasiados recursos o recursos no críticos y sin tener en cuenta la cache, podríamos empeorar el rendimiento tanto de usuarios nuevos como de usuarios recurrentes, pero si la llevamos a cabo correctamente, mejorará el considerablemente las métricas de WPO, sobre todo de los usuarios nuevos, por lo que mejoraremos la primera impresión que genere el sitio en estos usuarios.
Puesto que esta tecnología todavía tiene algunos aspectos en fase experimental, previsiblemente en un futuro su implementación cambie y sea más sencilla, ya que actualmente no tiene mucho sentido que para hacer un push en la cabecera HTTP haya que usar un código equivalente a un preload en la cabecera del HTML y para hacer un preload en la cabecera HTTP se tenga que añadir nopush. También debería incluir mejoras, sería muy interesante que pudiera usarse para acelerar las redirecciones, haciendo que el servicio web envíe un push del HTML al que se va a redirigir automáticamente, que es algo que actualmente no funciona ni siquiera manualmente.
Actualización: el 27 de Septiembre de 2022 se elimina el soporte de HTTP/2 Server Push de Chrome, debido a la dificultad de su implementación, comentada en este artículo, y a su baja adopción.
Bibliografía
https://httpd.apache.org/docs/2.4/mod/mod_http2.html
https://www.w3.org/TR/preload/#server-push-http-2
https://www.w3.org/TR/resource-hints/
Muy buen post, gracias por compartir esta información
saludos
Nicolás