Mejorando la carga del CSS

El CSS puede es un factor clave a la hora de “adelantar contenido” a los usuarios para que puedan ir viendo algo mientras se carga el sitio, en lugar de una pantalla en blanco que no dice nada. Si todavía no sabes bien como medir la velocidad de tu sitio, el siguiente artículo puede ayudarte:

Midiendo la velocidad de tu sitio

CSS

El CSS es lo que menos impacto tiene comparado con las imágenes y el JS, pero no deja de ser importante.

Así como el JS cargado antes del HTML bloquea la carga, el CSS hace lo mismo y a diferencia del JS, que podés optar por cargarlo al final del body(evitando que sea tan bloqueante), con el CSS no se puede hacer lo mismo porque tendrías una vista del HTML sin estilos (es decir todo roto) hasta que a lo último se cargue el CSS.

Por eso tenés que pensar en CSS crítico y CSS asincrónico (no crítico), que podés cargar de forma asincrónica.

CSS Crítico

El CSS crítico es básicamente el que necesitás que si o si bloquee la carga del HTML para mostrar el contenido más relevante sin que se vea roto, por ejemplo: la navegación, el logo, un banner que esté arriba de todo en la página, una grilla de productos, etc.


Gráfico de “above the fold”, del artículo “Below the fold: What does below the fold mean?”

El CSS que consideres crítico va a ser cargado inline en el documento, ¿Porque tan desprolijo?, porque necesitás que se cargue lo más rápido posible sin tener que hacer un pedido de un archivo CSS al servidor y demorar en cargarlo. Si bien no es lo que te enseñaron (meter estilos inline) en este caso se justifica en base al objetivo que queremos lograr.

En Tienda Nube tenemos este CSS insertado con Twig de la siguiente forma:

{# Critical CSS to improve the percieved performance on first load #}
<style>
  {% snipplet 'css/critical-css.tpl' %}
</style>

Hacer esto es lo mismo que insertarlo inline solo que queda en un archivo aparte de una forma mucho más prolija.

Si trabajás con Bootstrap, vas a tener que considerar dividirlo en 2 partes también, una crítica y una no crítica. Pensá qué elementos estás usando above the fold, (la nav, los botones, las utilities classes como pull-left, pull-right, etc) y que cosas pueden quedar en un segundo plano (modals, tables, algunos forms, etc).

Algo en lo que podés apoyarte a riesgo de mostrar contenido “roto” hasta que se cargue el resto del CSS es ocultar los elementos no críticos con una class que los mantenga invisibles hasta que tengamos todo el CSS.

Para hacer esto podés pensar en dos clases de CSS:

Por un lado agregás en tu CSS crítico las siguientes clases al final del mismo (una para ocultar elementos pero que sigan ocupando un espacio en la página y otra para directamente no se vea y no ocupe espacio):

.visible-when-content-ready{
  visibility: hidden!important;
}
.display-when-content-ready{
  display: none!important;
}

Luego en el CSS asincrónico (te voy a contar sobre este más adelante) agregá lo siguiente, también al final:

.visible-when-content-ready{
  visibility: visible!important;
}
.display-when-content-ready{
  display: block!important;
}

Ahora podés agregarle cualquiera de estas clases a los elementos que no son críticos pero corren riesgo de verse rotos hasta tener todo el CSS cargado, por ejemplo puede ser el footer de la página haciendo que se vea lo siguiente:

A la izquierda el footer oculto pero ocupando un espacio y a la derecha el mismo footer ya con su CSS listo para ser mostrado



Y evitás mostrar esto hasta que no este listo su CSS


Así se vería el footer si no usaras las clases de visible-when-content-ready o display-when-content-ready

CSS asincrónico

Este CSS básicamente es todo lo que no considerás como crítico, debería ser bastante ya que es todo el CSS que estás removiendo para no bloquear tanto tiempo la carga de la página.

¿Cómo cargás el CSS asincrónico? Usá el plugin llamado loadCss hecho con Javascript puro y te permite “demorar” la carga de CSS que no necesitás de inmediato. La carga la va a definir de la misma forma que lo decide el navegador con el tag de javascript async.

Para usar este plugin simplemente en el tag head del HTML llamálo de la siguiente forma:

<script>
!function(a){"use strict";var b=function(b,c,d){function j(a){return e.body?a():void setTimeout(function(){j(a)})}function l(){f.addEventListener&&f.removeEventListener("load",l),f.media=d||"all"}var g,e=a.document,f=e.createElement("link");if(c)g=c;else{var h=(e.body||e.getElementsByTagName("head")[0]).childNodes;g=h[h.length-1]}var i=e.styleSheets;f.rel="stylesheet",f.href=b,f.media="only x",j(function(){g.parentNode.insertBefore(f,c?g:g.nextSibling)});var k=function(a){for(var b=f.href,c=i.length;c--;)if(i[c].href===b)return a();setTimeout(function(){k(a)})};return f.addEventListener&&f.addEventListener("load",l),f.onloadcssdefined=k,k(l),f};"undefined"!=typeof exports?exports.loadCSS=b:a.loadCSS=b}("undefined"!=typeof global?global:this);
</script>

Y justo debajo haces los llamados asincrónicos a nuestro CSS;

<script id="style-scss">
loadCSS( '{{ 'style.scss.tpl' | static_url }}', document.getElementById("style-scss"));
</script>

Recordá que la ruta del ejemplo anterior está armada con Twig pero podés agregar la ruta que necesites. Es importante que coincidan el id del tag script con el que usas en el getElementByID, porque lo que va a hacer el script es insertar justo antes del tag script con el id “X” (en el DOM), ese mismo CSS con ese id “X”. En este caso el id es “style-css”.

En nuestras tiendas tenemos el CSS divido, como vimos anteriormente (asincrónico y crítico), pero también tenemos un SASS con los colores y tipografías guardados en la personalización de cada tienda. Hasta el momento ese SASS es cargado de la forma convencional bloqueando la carga del documento a propósito para asegurarnos de tener todos los colores y tipografías aplicados antes de mostrar algo y evitando un salto visual en estos estilos específicos (si los aplicáramos como algo asincrónico).

Fonts

En las fonts hay mucho potencial para mejorar la velocidad. Los cambios que aplicamos en las tiendas estuvieron relacionados a:

  • Llamar solo las fonts que fueron guardadas en la configuración en lugar de llamar a todas en el documento. Llamar más de 3 o 4 fonts distintas (aún siendo de google fonts) puede comenzar perjudicar la velocidad de carga, por eso siempre mantengamos la cantidad de fonts al mínimo en nuestro sitio.
  • Usar solo los pesos de fonts que necesitamos sin cargar de más, por lo general son 300, 400 y 700.
  • Llamar las fonts de Google con un display: swap; , evitando que las fonts bloqueen la carga del sitio mientras son cargadas y haciendo que mientras tanto se muestre una font de fallback o default.
    Esto puede generar un pequeño salto entre la font inicial y la final pero adelanta el tiempo en que se puede mostrar texto para que el usuario ya pueda consumir el contenido.

{% if params.preview %}


    {# If page is loaded from customization page on the admin, load all fonts #}


    {{ '//fonts.googleapis.com/css?family=PT+Sans+Narrow:400,700|Open+Sans:400,300,700|Slabo+27px|Oswald:400,300,700|Lora:400,700|Montserrat:400,500,700|Roboto+Condensed:400italic,700italic,300,400,700|Droid+Sans:400,700|Playfair+Display:400,700&display=swap' | css_tag }}


{% else %}


    {# If page is NOT loaded from customization only load saved fonts #}


    {# Get only the saved fonts on settings #}


    {{ [settings.font_headings, 
    settings.font_navigation, 
    settings.font_product_title, 
    settings.font_buttons, 
    settings.font_rest] | google_fonts_url('300, 400, 700') | css_tag }}


{% endif %}

En el ejemplo anterior cargamos todas las fonts si el usuario esta en la página de configuración de su tienda ya que las fonts se cambian en tiempo real a medida que se personaliza el diseño y necesitamos tener todo cargado. Por otro lado (en el else) cargamos solamente las fonts relacionadas a cada configuración o cada “setting”.

¿Qué sigue?

Si ya pudiste optimizar todo lo que tiene que ver con CSS te recomiendo seguir leyendo los cambios que aplicamos sobre los otros dos frentes para mejorar la velocidad:

Mejorando la carga de imágenes y SVGs

Mejorando la carga del CSS