Qué es el CSS Crítico y cómo puede optimizar el FCP ?

Author
Por Darío Rivera
Publicado el en HTML

Los navegadores web tratan los archivos CSS como recursos que bloquen la presentación (Render-blocking CSS). Esto quiere decir, que estos archivos se descargan, parsean y ejecutan antes de renderizar la página web al usuario. Pero por qué es importante esto?, la respuesta es obvia. Demasiados recursos que bloquean la presentación suponen un espacio de tiempo en el que el usuario verá la página en blanco. Cinco segundos podrían ser suficientes para que un usuario de clic al botón Atrás y se dirija hacia otra página.

Si bien las recomendaciones dadas en nuestro post anterior Optimizar tiempos de carga web en archivos CSS mejoran un poco el panorama de la carga de recursos CSS, ¿qué hay de aquellos CSS que son demasiado grandes? ¿Cómo podemos identificar el CSS que sí es necesario del que no?. Para esto, podemos utilizar una técnica llamada critical rendering path o ruta de acceso de representación crítica. Este método consiste básicamente en identificar el CSS mínimo que se necesita para que se pueda mostrar el primer pantallazo al usuario. Básicamente queremos cargar solo el CSS de aquella sección de la página visible cuando el usuario carga la página.

En este momento tal vez te estés imaginando un trabajo tedioso previo a la implementación de esta técnica. En teoría es cierto, identificar el CSS crítico de cada elemento del DOM para obtener un CSS previo puede ser una tarea  bastante engorrosa. Es por esto que existen herramientas automatizadas como Critical que hacen de este proceso más rápido extrayendo el CSS realmente crítico.

Veamos un ejemplo sencillo en una página que utilice bootstrap e imprima un mensaje sencillo al usuario como la siguiente.

<!DOCTYPE html>
<html>
<head>
    <title>Critical CSS</title>
    <link rel="stylesheet" type="text/css" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css">
</head>
<body>
    <div class="jumbotron">
      <h1 class="display-4">Hello, world!</h1>
      <p class="lead">This is a simple hero unit, a simple jumbotron-style ...</p>
      <hr class="my-4">
      <p>It uses utility classes for typography and spacing to space content out within the larger container.</p>
      <a class="btn btn-primary btn-lg" href="#" role="button">Learn more</a>
    </div>
</body>
</html>

Es obvio que la librería de bootstrap es un recurso que bloquea la presentación, veamos en un servidor regular cómo se expresa esto en términos de performance.

First Contentful Paint en Google

Como puedes darte cuenta el FCP (First Contentful Paint) está al rededor de los 950 ms. El FCP es el primer pantallazo que se muestra al usuario. Este valor puede variar dependiendo del server en donde tengas tu página web y tu conexión. Lo que quiero mostrarte es el cambio en performance dadas las condiciones actuales.

Lo que vamos a hacer ahora es utilizar Critical para obtener el CSS crítico. Para esto, he de suponer que trabajas en un framework que ya utiliza npm como gestor de paquetes. En caso de que no, puedes agregar el siguiente contenido en un archivo llamada package.json.

{
  "description": "critical css example",
  "dependencies": {
    "critical": "^1.3.4"
  },
  "engines": {
    "node": "8.x"
  },
  "license": "MIT",
  "private": true
}

Posterior a esto, debes instalar las dependencias que en est caso es solamente Critical.

npm install

El penúltimo paso será crear el script de Critical en el cuál indicaremos en la key src el nombre del archivo que hemos creado al comienzo de este post. En la key dest podríamos colocar el mismo nombre de archivo si deseas que se sobreescriba, en caso contrario puedes indicar el nombre de un nuevo archivo. El parámetro inline indica si el CSS generado se coloca en el mismo archivo fuente HTML. Es altamente recomandado que el CSS generado esté en línea en el mismo archivo, con esto se evitan viajes de ida y vuelta para solicitar el CSS crítico.

const critical = require('critical');

critical.generate({
  base: './',
  src: './index.html',
  dest: './index-optimizado.html',
  inline: true,
  dimensions: [
    {
      height: 500,
      width: 300,
    },
    {
      height: 720,
      width: 1280,
    },
  ]
}, (err, output) => {
  if (err) {
    console.error(err);
  } else if (output) {
    console.log('Generated critical CSS');
  }
});

La key dimensions define un array de  tamaños de pantalla objetivos para los cuales se generará el CSS crítico. En este caso hemos especificado dos tamaños de pantalla, 300x500 para dispositivos con pantallas pequeñas y 1280x720 para pantallas de escritorio y laptops. Definido esto, vamos a correr el comando para generar el CSS crítico.

node critical.js

Si no ha ocurrido ningún error deberías obtener el texto Generated critical CSS en la terminal. Si observas el archivo generado será similar al siguiente:

<!DOCTYPE html>
<html>
<head>
    <title>Critical CSS</title>
    <style>
        :root{--blue:#007bff;--indigo:#6610f2;--purple:#6f42c1;--pink:#e83e8c;--red:#dc3545;--orange:#fd7e14;--yellow:#ffc107;--green:#28a745;--teal:#20c997;--cyan:#17a2b8;--white:#fff;--gray:#6c757d;--gray-dark:#343a40;--primary:#007bff;--secondary:#6c757d;--success:#28a745;--info:#17a2b8;--warning:#ffc107;--danger:#dc3545;--light:#f8f9fa;--dark:#343a40;--breakpoint-xs:0;--breakpoint-sm:576px;--breakpoint-md:768px;--breakpoint-lg:992px;--breakpoint-xl:1200px;--font-family-sans-serif:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-family-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}hr{box-sizing:content-box;height:0;overflow:visible}h1{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}a{color:#007bff;text-decoration:none;background-color:transparent}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}h1{margin-bottom:.5rem;font-weight:500;line-height:1.2}h1{font-size:2.5rem}.lead{font-size:1.25rem;font-weight:300}.display-4{font-size:3.5rem;font-weight:300;line-height:1.2}hr{margin-top:1rem;margin-bottom:1rem;border:0;border-top:1px solid rgba(0,0,0,.1)}.btn{display:inline-block;font-weight:400;color:#212529;text-align:center;vertical-align:middle;background-color:transparent;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;line-height:1.5;border-radius:.25rem}.btn-primary{color:#fff;background-color:#007bff;border-color:#007bff}.btn-lg{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.jumbotron{padding:2rem 1rem;margin-bottom:2rem;background-color:#e9ecef;border-radius:.3rem}@media (min-width:576px){.jumbotron{padding:4rem 2rem}}.my-4{margin-top:1.5rem!important}.my-4{margin-bottom:1.5rem!important}
    </style>
    <link rel="preload" type="text/css" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
    <noscript><link rel="stylesheet" type="text/css" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css"></noscript>
    <script>!function(n){"use strict";n.loadCSS||(n.loadCSS=function(){});var t,o=loadCSS.relpreload={};o.support=function(){var e;try{e=n.document.createElement("link").relList.supports("preload")}catch(t){e=!1}return function(){return e}}(),o.bindMediaToggle=function(t){var e=t.media||"all";function a(){t.addEventListener?t.removeEventListener("load",a):t.attachEvent&&t.detachEvent("onload",a),t.setAttribute("onload",null),t.media=e}t.addEventListener?t.addEventListener("load",a):t.attachEvent&&t.attachEvent("onload",a),setTimeout(function(){t.rel="stylesheet",t.media="only x"}),setTimeout(a,3e3)},o.poly=function(){if(!o.support())for(var t=n.document.getElementsByTagName("link"),e=0;e<t.length;e++){var a=t[e];"preload"!==a.rel||"style"!==a.getAttribute("as")||a.getAttribute("data-loadcss")||(a.setAttribute("data-loadcss",!0),o.bindMediaToggle(a))}},o.support()||(o.poly(),t=n.setInterval(o.poly,500),n.addEventListener?n.addEventListener("load",function(){o.poly(),n.clearInterval(t)}):n.attachEvent&&n.attachEvent("onload",function(){o.poly(),n.clearInterval(t)})),"undefined"!=typeof exports?exports.loadCSS=loadCSS:n.loadCSS=loadCSS}("undefined"!=typeof global?global:this);</script>
</head>
<body>
    <div class="jumbotron">
      <h1 class="display-4">Hello, world!</h1>
      <p class="lead">This is a simple hero unit, a simple jumbotron-style component for calling extra attention to featured content or information.</p>
      <hr class="my-4">
      <p>It uses utility classes for typography and spacing to space content out within the larger container.</p>
      <a class="btn btn-primary btn-lg" href="#" role="button">Learn more</a>
    </div>
</body>
</html>

Si te das cuenta en este archivo generado se está utilizando la precarga de bootstrap con rel=preload. Esto ya lo habíamos explicado en nuestro artículo Optimizar tiempos de carga web en archivos CSS. Sin embargo, se agrega una forma más estándar que incluye los navegadores que aún no soportan esta característica con loadCSS.

Una vez hecho esto veamos cómo se expresa en términos de performance.

First Contentful Paint en Google

El FCP ha pasado a posicionarse al rededor de los 660 ms, lo cual supone una diferencia de casi 300 ms para un ejemplo tan sencillo como el que hemos planteado. Imagínate la diferencia que podrías lograr en aplicaciones que utilizan varias librería y recursos CSS.

Cómo medir el performance ?

Todo esto no estaría completo si no hubiera una forma de medir el perfomance de la página. Ya hemos visto que las herramientas de desarrollo de Google nos permiten ver el FCP y previsualizaciones en miniatura de la página en cuestión. También puedes utilizar el PageSpeed Insights de Google o WebPagetests para completar estos análisis y tener un panorama más completo del perfomance de tu web.


Acerca de Darío Rivera

Author

Application Architect at Elentra Corp . Quality developer and passionate learner with 10+ years of experience in web technologies. Creator of EasyHttp , an standard way to consume HTTP Clients.

LinkedIn Twitter Instagram

Sólo aquellos que han alcanzado el éxito saben que siempre estuvo a un paso del momento en que pensaron renunciar.