Reglas PageSpeed para HTML, CSS, JS e imágenes

Introducción

Extensión PageSpeed para Chrome

En este tema se exponen las reglas PageSpeed a tener en cuenta en documentos HTML, CSS, JS e imágenes.

Documento HTML

El documento HTML es el primero que se descarga. Contiene vínculos a otros recursos CSS, JS e imágenes, entre otros. Por lo tanto en el HTML hemos de tener en cuenta algunas medidas para optimizar la página.

Optimizar el orden de los estilos y de las secuencias de comandos

PageSpeed Docs: orden CSS y JS Ordenar correctamente las hojas de estilo externas y las secuencias de comandos en línea y externas permite mejorar las descargas en paralelo y acelera el tiempo de representación del navegador.

El proceso de renderizado lee el HTML para construir la página. Lo esencial para montar una página es conocer el CSS que da el estilo necesario para pintarla. Este estilo puede venir en un archivo CSS externo y también los JS pueden modificarlo. Si tenemos varios <link rel="stylesheet" ...> y <script src="...>, cuando los JS están antes de un CSS el navegador bloquea la descarga del archivo CSS hasta descargar y evaluar los JS intermedios. Esto lo hace así porque en esos JS puede ser que hayan cosas que modifican el DOM y por tanto pudieran ser objeto del CSS posterior.

Una posible lista de recomendaciones que refleja como lo estoy haciendo actualmente:

  • Usar el menor número posible de archivos CSS externos (los <link rel="stylesheet" ...>) y ubicarlos lo más arriba posible en el <head> del documento.
  • A continuación poner los elementos <style> con estilo inline de la página. Ver también Introducir recursos CSS en el encabezado del documento.
  • Todo el JavaScript irá al final de la página antes del tag de cierre </body>. Pondríamos el elemento <script> con el window.onload con el JavaScript particular para esa página. A continuación irán los JS externos <script src="...> siempre asíncronos (ver Utilizar preferentemente recursos asíncronos).
  • Nunca poner CSS dentro del <body>. Utilizar la menor cantidad posible de CSS inline al elemento, el que se pone en un atributo style en el HTML.
  • Aparte del que va al final, poner la menor cantidad posible de JS dentro del <body> especialmente los que van en los eventos. Adjudicar eventos desde el window.onload con addEventListener().
  • No usar document.write ni otras cosas que modifiquen el DOM cuando se evalúa un JavaScript. Diferir estas ejecución hasta el window.onload (ver más en Aplazar el análisis de JavaScript).

Priorizar el contenido visible (on-line)

PageSpeed Docs: priorizar contenido visible Reducir el tamaño del contenido de la mitad superior de la página: Si la cantidad de datos necesarios es superior al margen de congestión inicial, se producirá un nuevo flujo de datos entre el servidor y el navegador del usuario. En el caso de los usuarios en redes con latencias altas, como las redes móviles, esto puede causar retrasos significativos en la carga de las páginas. Esta regla se activa cuando PageSpeed Insights detecta que puede producirse un flujo adicional de datos en la red para mostrar el contenido de la mitad superior de una página.

Test de la conexión web

En un test de la conexión web realizado a este sitio observé que entre 14000 y 15000 Bytes se produce un salto de un RTT. Esto es porque el servidor tiene el IW configurado en 14600 Bytes. Por lo tanto lo óptimo sería que el HTML completo no tuviera un tamaño superior a 14000 Bytes, dejando esos 600 Bytes para las cabeceras. O al menos que en los primeros 14000 Bytes del HTML estuviera todo lo necesario para renderizar y pintar la parte superior de la página.

Esto está intimamente relacionado con la regla sobre optimizar la entrega de CSS. Aunque debemos tener en cuenta que además del CSS son varias las cosas que afectan a la parte superior de la página, concepto que a veces vemos como priorizar above-the-fold, que quiere decir priorizar el contenido visible. El navegador va recuperando HTML, CSS, imágenes y recursos que va encontrando y va formado el DOM. Cuando considera que tiene lo necesario procede a pintar la parte superior de la página que se muestra en pantalla. Pero si en esa parte hay un trozo que debe ser descargado y del que el navegador no sabe, por ejemplo, las dimensiones finales, podría bloquearse el pintado hasta recuperar ese recurso.

La idea general es pintar lo más importante de la parte superior en menos de un segundo. Y postergar el resto para ejecutarlo en el window.onload. Y para eso el HTML+CSS que pinte la parte superior no debe ser mayor de 14000 Bytes.

Especificar un conjunto de caracteres al principio

PageSpeed Docs: charset Especificar un conjunto de caracteres al principio de sus documentos HTML permite que el navegador empiece a ejecutar secuencias de comandos de forma inmediata.

Todos los documentos deben codificarse en utf-8. Pero el navegador puede aceptar otras codificaciones de caracteres. Si no se especifica la codificación de un recurso, el navegador buscará información en los caracteres de texto para saber cuál es. Para evitar este proceso que le supone un tiempo, lo mejor es enviar una cabecera HTTP Content-Type:text/html; charset=utf-8; con el HTML y no especificar ninguna codificación en cualquier otro recurso. El navegador toma de entrada esa codificación y no realiza más comprobaciones. En todo caso podríamos incluir un elemento <meta charset="utf-8" /> al principio del <head> o un @charset "utf-8" en el inicio de un archivo CSS. El navegador los encontrará y verá que ya coinciden con el Charset de la cabecera HTTP no suponiéndole ningún coste adicional.

Evita un conjunto de caracteres en la metaetiqueta

PageSpeed Docs: charset en http Si el documento tienen un conjunto de caracteres especificado en una metaetiqueta, se inhabilita la descarga anticipada en IE8. Para mejorar la descarga de recursos en paralelo, mueve el conjunto de caracteres al encabezado de respuesta HTTP Content-Type.

La metaetiqueta mencionada es la antigua <meta http-equiv="content-type" content="text/html; charset=UTF-8" />. Enviando una cabecera HTTP Content-Type:text/html; charset=utf-8; ya no hace falta ese elemento obsoleto en HTML5.

Evitar solicitudes incorrectas

PageSpeed Docs: evitar 404's Eliminar "enlaces rotos", o solicitudes que den lugar a errores 404/410, evita solicitudes incorrectas.

Debemos mantener al mínimo el número de peticiones a recursos adicionales en cada página. Hay que recordar que los navegadores pueden manejar unas 6 conexiones en paralelo por dominio. Toda petición que resulte en un 404 es un tiempo perdido y una conexión desaprovechada. Aparte de la mala imagen que le damos al usuario y otros problemas que podría ocasionar.

CSS

El CSS condiciona enormemente la optimización de la página. Y es una de las tareas más díficiles de llevar a cabo porque cada web es diferente y cada CSS actúa de forma distinta en cada página.

Optimizar la entrega de CSS (on-line)

PageSpeed Docs: Optimizar entrega CSS Los navegadores bloquean los archivos CSS externos antes de que apliquen formatos al contenido de la pantalla. Esto conlleva latencia de red adicional y aumenta el tiempo de carga del contenido. Esta regla se activa cuando PageSpeed Insights detecta que una página incluye hojas de estilo externas que demoran la aparición del formato del contenido.

En el tema sobre desbloquear CSS expuse mi experiencia personal cuando traté de arreglar este mensaje que me salía en PageSpeed on-line:

Eliminar el JavaScript que bloquea la visualización y el CSS del contenido de la mitad superior de la página.

Tu página tiene 5 recursos CSS que provocan un bloqueo. Ello causa un retraso en el procesamiento de la página. No se ha podido visualizar el contenido de la mitad superior de la página sin tener que esperar a que se cargara alguno de los recursos. Intenta aplazar o cargar de forma asíncrona los recursos que bloquean la visualización, o bien inserta porciones críticas de dichos recursos directamente en el HTML. Optimizar la entrega de CSS de estas URL: cabeza-pie.css, interior.css, formatos.css, form-emerge.css, canvas-clock.css.

Se estaba refiriendo a un bloqueo de CSS, no de JavaScript en este caso. Tenía cinco archivos CSS externos que necesitaban descargarse para pintar la parte superior de la página. Esa descarga estaba bloqueando el pintado de la página. En ese tema explico con detalle como desbloquear esta situación y optimizar la entrega CSS. Una solución pasa por poner todo el CSS inline al HTML con lo que el navegador no tendrá que descargar ningún archivo CSS externo. Pero esto podría dar lugar a otro mensaje de PageSpeed:

Prioriza el contenido visible.

Tu página requiere indicaciones completas de red adicionales para procesar el contenido destacado en la parte superior de la página. Para obtener un rendimiento óptimo, reduce la cantidad de HTML necesario para procesar dicho contenido. Se ha precisado 42,8 KB de la respuesta para poder mostrar contenido en la mitad superior de la página. Prioriza el contenido visible necesario para poder mostrar contenido en la mitad superior de la página.

Esto significa que hay demasiado CSS inline. Para arreglarlo hay que priorizar el contenido visible above-the-fold. Mi solución se basa en inyectar con PHP el CSS necesario para cada página en dos elementos <style>. El primero css-before va con el HTML para pintar la parte superior de la página. El segundo css-after va también con el HTML, pero dentro de un elemento <noscript> y al final de la página. En el window.onload inyectaremos este segundo <style> cuando el usuario empiece a interacturar con la página haciendo click o usando el scroll.

Evitar la directiva "@import" en CSS

PageSpeed Docs: Evitar CSS @import Utilizar la norma "CSS @import" en una hoja de estilo externa puede añadir retrasos adicionales durante la carga de una página web.

Un <link rel="stylesheet" href="uno.css" /> supone una conexión con el servidor. Si dentro del archivo hay un @import url("dos.css") estaremos en un bloqueo del primer CSS pues tendrá que desacargar el segundo para luego seguir con el primero. Lo mejor es usar dos link uno tras otro para permitir la descarga en paralelo.

Para priorizar el contenido visible he unificado todo el CSS del sitio que afecta a más de una página en un único archivo base.css. El resto de archivos CSS que utilizo en este sitio se usan solamente en alguna única página. Y en ningún caso uso @import.

Insertar pequeños recursos CSS

PageSpeed Docs: hojas estilo pequeñas Insertar hojas de estilo pequeñas en la página HTML principal reduce los tiempos de retorno y los retrasos relacionados con la descarga de otros recursos.

Ya comenté que no estoy usando archivos CSS externos sino inyectándolo inline con PHP. A excepción de aquellas páginas con CSS específico para esa página. Pero en caso de usar archivos CSS hemos de tener en cuenta no tener muchos <link rel="stylesheet">. En todo caso debemos poner en primer lugar uno que no sea muy grande para poder pintar la parte superior de la página. Si en un archivo CSS está el estilo de la parte superior y es demasiado grande, puede requerir varios RTT, lo que bloqueará ese pintado de página pues no se llevara a cabo hasta que se reciba todo el archivo.

Introducir recursos CSS en el encabezado del documento

PageSpeed Docs: Poner CSS arriba Mover bloques de estilo en línea y elementos link del cuerpo al encabezado del documento mejora el rendimiento de la representación.

Un elemento <style> o un <link rel="stylesheet"> se puede ubicar en cualquier parte del documento. Pero es muy ineficiente si se encuentra en el <body>. Como mencionamos en otra regla más arriba, todo el CSS debería ir lo más arriba posible del <head>.

JavaScript

JavaScript es un soporte del desarrollo web que tiene un gran potencial. Podemos hacer muchas cosas con JavaScript. Podemos modificar el DOM creando o eliminando elementos, modificar el CSS, gestionar eventos, acceder al dispositivo, etc. Pero al igual que CSS, también JavaScript puede bloquear la carga de la página si se ejecuta algo en pleno proceso de carga.

Quitar el JavaScript que bloquea la visualización de contenido (on-line)

PageSpeed Docs: Evitar bloqueos JS Para que un navegador pueda mostrar una página al usuario, antes debe analizarla. Si encuentra un script externo que bloquee la visualización del contenido durante el análisis, debe detenerse y descargar el archivo JavaScript. Cada vez que sucede esto, se crea un nuevo flujo de datos en la red que retrasa la carga de la página. Esta regla se activa cuando PageSpeed Insights detecta que el código HTML hace referencia a algún archivo JavaScript externo que bloquea la visualización de contenido en la mitad superior de la página.

De forma similar a lo comentado en la regla de optimizar la entrega de CSS, PageSpeed puede darnos un mensaje de Quitar el JavaScript que bloquea la visualización. Cuando el navegador renderiza el documento encontrará vínculos a archivos de JavaScript externo o bien scripts en línea. En cualquier caso debe descargar y analizar esos scripts porque en ellos pueden haber cosas que modifiquen el DOM o árbol de elementos, árbol que precisamente está construyendo en ese momento el navegador.

Insertar pequeños recursos JavaScript

PageSpeed Docs: Usar pequeños archivos JS Insertar archivos JavaScript pequeños en la página HTML principal reduce los tiempos de retorno y los retrasos relacionados con la descarga de otros recursos.

Hay varias formas de evitar que JavaScript bloquee la carga. La más simple es descargarlo desde un archivo usando el atributo async, como veremos en la siguiente regla. Pero esto se fundamenta en que no necesitaremos que JavaScript ejecute algo durante la carga y que vamos a postergar esa ejecución. Pero sí aún necesitamos que JavaScript haga algo antes del window.onload (como por ejemplo creando algún elemento HTML), hemos de ponerlo en un un <script> inline en el <head>, tratando que la ejecución sea la mínima indispensable. Hay que evitar llenar el <body> de scripts inline porque cada uno supone un bloqueo del renderizado.

Aplazar el análisis de JavaScript

PageSpeed Docs: Aplazar JS Al minimizar la cantidad de código JavaScript necesario para mostrar la página y aplazar el análisis de código JavaScript innecesario hasta que se deba ejecutar, puede reducir el tiempo de carga inicial de su página.

Vemos que durante la carga, es decir, antes del window.onload no debemos ejecutar JavaScript que interaccione con el DOM, CSS o con tareas complejas que bloquearán el proceso de carga. En la regla sobre optimizar el orden de los estilos y de las secuencias de comandos dije que yo había optado por poner al final de la página el <script> inline que porta el window.onload y a continuación todos los vínculos a los archivos JavaScript externos con el atributo async. Poner todo esto al final de la página no quiere decir que el navegador no lo analice y ejecute cuando llegue a ellos.

Cuando llegue al script inline lo analizará, esto quiere decir que lo parseará para almacenar sus variables, funciones, eventos, etc. Y a continuación lo ejecutará, por lo que tendrá que bloquear el proceso de carga del resto (HTML y CSS), pues esa ejecución puede dar lugar a cambios en el DOM. Si quieres probarlo puedes poner en una página algo como <script>alert("head");</script> al principio del <head> y en otros lugares como al principio del <body>, a mitad de página y al final. Así verás como la construcción de la página se va deteniendo en esos puntos.

Por lo tanto hemos de conseguir que el JavaScript inline sea parseado, pero que la ejecución no afecte al proceso de carga. Vea este ejemplo:

Ejemplo:

A este botón se le adjudica un evento CLICK desde el window.onload. Al pulsarlo sale un mensaje alert().

El HTML de este botón y lo scripts al final de esta página son los siguientes:

...
<body>
    ...
    <input type="button" id="boton-ejemplo" value="ejemplo" />
    ...
    <script>
        var wxG, wxR;
        window.onload = function(){
            wxG = Wextensible.general;
            wxG.adjudicarEventosGenerales();
            //Carga módulo para el resaltador de código
            wxG.cargarModulos(["resaltador.js"]);
            wxR = Wextensible.resaltador;
            //Agrega evento al botón del ejemplo de Aplazar análisis de JavaScript
            var botonEjemplo = document.getElementById("boton-ejemplo");
            wxG.agregarEventListener(null, [[botonEjemplo, "outer"]]);
            botonEjemplo.addEventListener("click", hacerAlgo, false);
        };
    //Función para el ejemplo de la regla de Aplazar análisis de JavaScript
        function hacerAlgo(){
            alert("Hacer algo para la función hacerAlgo()");
        }
    </script>
    <script src="/res/inc/general.js" async></script>
    <script src="/res/inc/resaltador.js" async></script>
    ...
</body>
</html>

En el botón no declaramos el evento con el atributo onclick, lo que agilizará el proceso de carga. Veáse que en el script inline sólo se declaran cosas. Un par de variables wxG, wxR y otro par de declaraciones de funciones. Al llegar ahí el navegador parsea el código y lo ejecuta, lo que hasta ahora sólo supone crear variables y funciones. Pero la ejecución efectiva sólo se hará con el window.onload, después de finalizar el proceso de renderizado y pintado de la página. Es entonces cuando adjudica el evento onclick del elemento.

Utilizar preferentemente recursos asíncronos

PageSpeed Docs: Usar async Recuperar recursos de forma asíncrona evita que estos recursos bloqueen la carga de páginas.

En el código anterior verá que después del script inline hay dos archivos JS externos que se recuperan con async. Esto significa que el navegador los descargará de forma asíncrona y los ejecutará cuando finalice el proceso de renderizado o bien en algún momento intermedio si no tiene otras tareas en ejecución que bloqueen la carga. El JS general.js contiene el JavaScript básico para el funcionamiento del sitio. Pero sólo es un conjunto de funciones que en la ejecución del script no supone sino declarar variables y funciones.

Una variable Wextensible definirá el espacio de nombres para la página, donde irá el objeto Wextensible.general con el conjunto de funciones básicas descargadas de general.js. Un acortador wxG nos permitirá acceder a ellas más cómodamente. La función wxG.adjudicarEventosGenerales() realiza todas las adjudicaciones de eventos de la página, con lo que evitamos eventos en atributos de elementos como los onclick.

El resto de los archivos JS externos se ejecutarán con un cargador de módulos. En esta página está el script resaltador.js que dota de funcionalidad al componente de código resaltado anterior. Así podemos pulsar los botones de la barra de ese componente y numerar el código o imprimirlo. Pero la ejecución de resaltador.js se hace desde el window.onload con la función wxG.cargarModulos(), no interfiriendo en el proceso de carga.

Imágenes

Las imágenes son un recurso del que muchas veces no podemos prescindir. Pero el peso de las mismas y su número puede retrasar la carga de la página. Hay cuatro mínimas reglas que tenemos que acometer en PageSpeed para mejorar este aspecto.

Combinar las imágenes en objetos móviles CSS

PageSpeed Docs: CSS sprites Combinar imágenes en el menor número de archivos posible mediante objetos móviles CSS reduce el número de tiempos de retorno y retrasos relacionados con la descarga de otros recursos, así como los gastos generales de solicitudes, y puede disminuir el número total de bytes descargados por una página web.

Combinar imágenes en sprites CSS

La combinación de imágenes en objetos móviles CSS conocidos generalmente como sprites CSS reduce el número de peticiones mejorando sensiblemente la velocidad de carga de la página. Hay muchas herramientas con las que construir nuestros sprites, incluso la que presento en este sitio, un compositor de imágenes que puede servir para esto.

Los sprites CSS se utilizan generalmente para disponer de una colección de imágenes a modo de iconos para botones. En la imagen anterior están todos los que estoy actuamente usando en este sitio. Así con una única petición podemos disponer de muchas pequeñas imágenes ahorrándonos otros tantas peticiones.

Optimizar imágenes

PageSpeed Docs: Optimizar imágenes Comprimir imágenes y aplicarles un formato adecuado puede ahorrar una gran cantidad de bytes de datos.

Optimizar imágenes

Las imágenes son recursos muy importantes en una página web. Pero limitan y condicionan la velocidad de carga de la página. Lo mínimo que podemos hacer es optimizar el tamaño del archivo. Hay muchas herramientas que podemos usar. Yo estoy usando una propia, el optimizador de imágenes, donde también explico algo más sobre este aspecto. También PageSpeed nos ofrece la imagen optimizada, como puede ver en la captura de pantalla adjunta.

Especificar dimensiones de la imagen

PageSpeed Docs: Dimensiones imágenes Especificar un ancho y una altura para todas las imágenes permite una representación más rápida al eliminar la necesidad de utilizar reflujos (reflows) y repintados (repaints) innecesarios.

El proceso de renderizado en los navegadores se hace en dos hilos de ejecución. Por un lado el CSS y por el otro el HTML. Cuando el navegador encuentra un elemento <img> sin especificar el ancho y alto lo ubica sin tamaño y sigue renderizando. Cuando se descargue la imagen el navegador procedera a redimensionar el elemento, lo que provocará un repintado de toda la página. Esto ralentiza la carga de la página por lo que hay que evitarlo a toda costa. Si le damos unas dimensiones el navegador dejará ese hueco en blanco y cuando reciba la imagen sólo pintará ese trozo.

Ofrecer imágenes a escala

PageSpeed Docs: Ajustar tamaño imágenes Ajustar el tamaño de las imágenes de forma adecuada puede ahorrar una gran cantidad de bytes de datos.

Sabemos que <img> ajusta la imagen a las dimensiones del elemento. Podríamos tener una imagen de 500×500 px y meterla en un <img> de 100×100 px. La imagen se ajustará pues las dimensiones son proporcionales. Pero estaríamos manejando un archivo de imagen demasiado grande del que sólo usaríamos un 4% de todos sus píxeles, pues 100×100/(500×500) = 0,04.