Optimización de la prioridad de JavaScript para cargas de página más rápidas

Aprende a priorizar scripts de manera efectiva para mejorar los Core Web Vitals.

Arjen Karel Core Web Vitals Consultant
Arjen Karel - linkedin
Last update: 2024-12-12

Gestión de prioridades de JavaScript para un mejor rendimiento web

Una cosa siempre ha estado clara: no todo el JavaScript es igual. Algunos scripts manejan interacciones críticas como 'interacción del menú' o 'añadir al carrito', mientras que otros scripts son mucho menos importantes. Tomemos tu script de popup de 'exit intent' que invita a los visitantes que están a punto de abandonar tu sitio a rellenar un cuestionario. Estoy seguro de que todos podríamos vivir sin los últimos, pero sería muy difícil navegar por un sitio web sin los primeros.

Sin embargo, en  'tu sitio web promedio' a nivel técnico esta distinción casi nunca se hace. Todos los JavaScripts simplemente 'se añaden' a la página y se deja que el navegador se las arregle. Eso es un problema porque tu navegador no tiene idea de qué es importante y qué no. Nosotros, como desarrolladores, sí lo sabemos. ¡Así que arreglémoslo!

Cómo la prioridad de JavaScript puede impactar los Core Web Vitals

Simplemente añadir scripts a la página sin la consideración adecuada puede impactar los 3 Core Web Vitals. El Largest Contentful Paint, el Interaction to Next Paint y el Cumulative Layout Shift. 

Ejemplo: el recurso de red del LCP se retrasa por JavaScripts que bloquean el renderizado

El Largest contentful Paint es propenso a la competencia de ancho de banda y CPU. Cuando demasiados scripts compiten por los recursos de red tempranos, se retrasará el recurso de red del Largest Contentful Paint y el trabajo temprano de CPU retrasará el LCP al bloquear el hilo principal.

El Interaction to Next Paint puede verse afectado por scripts que se ejecutan justo antes de una interacción. Cuando los scripts se ejecutan, bloquean el hilo principal y retrasarán cualquier interacción durante ese tiempo de ejecución.

Los scripts también pueden causar un Cumulative Layout Shift si los scripts 'cambian cómo se ve la página'. Los scripts de anuncios que inyectan banners en la página y los sliders son conocidos por hacer esto.

5 tipos de prioridades de JavaScript

Me gusta distinguir entre 5 tipos de prioridades de JavaScript. Discutámoslos rápidamente antes de profundizar.

  • Render Critical: estos scripts son de los peores que puedes tener. Cambian el diseño de la página y sin cargar estos scripts el diseño será completamente diferente. Ejemplo: algunos scripts de sliders o un test A/B.
  • Scripts Críticos: Estos scripts manejan funcionalidades críticas de la página y sin ellos, tareas críticas como añadir un producto al carrito, búsqueda del sitio o navegación no son posibles.
  • Scripts importantes. Estos scripts manejan lógica importante (de negocio) y tu sitio depende de ellos. Por ejemplo: Analytics
  • Scripts deseables. Estos scripts son buenos de tener pero si llega el momento no los necesitamos realmente para que la página funcione. Por ejemplo, un widget de chat o un exit intent
  • Scripts futuros. Estos scripts pueden ser críticos o deseables pero no los necesitamos ahora mismo porque 'otros pasos' deben completarse antes de poder usar estos scripts. Por ejemplo, un script de checkout de múltiples pasos.
Ahora que tenemos una idea de las prioridades de scripts, ¡desglosémoslo!

1. Scripts Render-Critical

Estos son los scripts más disruptivos, ya que impactan directamente cómo se muestra la página. Sin ellos, el diseño puede romperse o aparecer drásticamente diferente del diseño previsto. Los ejemplos incluyen scripts para sliders o frameworks de tests A/B que alteran el diseño temprano en el proceso de carga. 

El problema con estos tipos de scripts es que no pueden diferirse ni retrasarse. Cualquier retraso hará que el diseño del sitio web se desplace causando una mala UX y que los Core Web Vitals fallen.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Page Title</title>
    <link href="styles.css" rel="stylesheet" />
    <script src="render-critical.js"></script>
  </head>
  <body></body>
</html>

Mejores prácticas:

  • Evita scripts render critical como estos siempre que sea posible. Reescribe tu código para evitar la dependencia de este tipo de scripts.
  • Si no hay forma de evitarlo, incluye en línea o carga solo las partes absolutamente necesarias de estos scripts. 
  • No uses defer o async en estos scripts y colócalos en la parte superior del head para activar una descarga 'lo antes posible'.

2. Scripts Críticos

Estos scripts habilitan interacciones fundamentales. Sin ellos, tareas críticas como la navegación del sitio, añadir artículos al carrito, aviso de cookies o realizar una búsqueda se vuelven imposibles. Son indispensables para la funcionalidad principal del sitio.

Estos scripts deben colocarse en el head de la página con el atributo async o defer.

<script defer src="critical.js"></script>
<script async src="critical.js"></script>

Mejores prácticas:

  • Mantén scripts como estos al mínimo y no combines esta funcionalidad con otra funcionalidad menos crítica.
  • Carga estos scripts temprano usando async o defer, dependiendo de sus dependencias.
  • Usa herramientas de Real User Monitoring (RUM), como Coredash, para identificar cuellos de botella en la ejecución y asegurar que su rendimiento se alinea con las necesidades del usuario.

3. Scripts Importantes

Aunque no están directamente vinculados a la usabilidad del sitio, los scripts importantes respaldan funciones empresariales clave. Los scripts de Analytics, por ejemplo, proporcionan datos esenciales pero no necesitan cargarse antes que elementos visuales más importantes. Obviamente, la distinción entre scripts críticos e importantes puede ser motivo de debate, ¡así que asegúrate de consultar a todas las partes interesadas antes de establecer esta prioridad!

Hay 3 formas de reducir la prioridad de estos tipos de scripts.

<html>
<head>
<!-- method 1: low fetchpriority -->
<script fetchpriority="low" defer src="important.js"></script>

<!-- method 2: inject after DOMContentLoaded -->
<script>
  document.addEventListener('DOMContentLoaded', function() {
    var script = document.createElement('script');
    script.src = 'important.js';
    document.body.appendChild(script);
  });
</script>
</head>
<body>

<!-- method 3: place at the bottom of the page -->
<script defer src="important.js"></script>
</body>
</html>

1. Low fetchpriority. 

Establecer el fetchpriority reducirá la prioridad relativa del script. Otros scripts con defer o async probablemente se pondrán en cola con una prioridad alta, mientras que los scripts con fetchprioriy="low" se pondrán en cola con una prioridad baja. Dependiendo de tu página (o tu ruta de renderizado) esto podría ser suficiente para priorizar otros recursos como tu imagen de Largest Contentful Paint y fuentes importantes. 

2: Inyectar después del DOMContentLoaded

Al inyectar el script después del evento DOMContentLoaded, te aseguras de que el script comience a descargarse directamente después de que el HTML haya sido completamente analizado. Esto permite que los recursos descubribles, como imágenes y fuentes, tengan prioridad. Este método proporciona un equilibrio: el script comienza a cargarse lo suficientemente temprano para evitar retrasos en la funcionalidad pero no compite con los recursos tempranos que son cruciales para el renderizado inicial de la página.

3: Colocar al final de la página

Esta técnica clásica difiere la carga del script hasta después de que el navegador haya procesado todo el documento y logra aproximadamente el mismo resultado que la técnica anterior. La única diferencia es que la técnica 2 omite el preload scanner de tu navegador mientras que esta técnica no.  El preload scanner es un escáner rápido y ligero que tu navegador usa para identificar y poner en cola rápidamente los recursos críticos. Omitir el preload scanner puede ser una buena idea si hay posibilidad de imágenes con carga diferida en el viewport, mientras que usar el preload scanner acelerará la carga de este script.

4. Scripts Deseables

Estos scripts mejoran la UX pero no son necesarios para que el sitio funcione. Los ejemplos incluyen widgets de chat, popups de comentarios de clientes o animaciones opcionales. Aunque son beneficiosos, no deberían interferir con la UX principal.

Estos scripts son un candidato ideal para cargar con un patrón llamado 'lazy on load'. Esto significa esperar al evento load de la página y luego, durante el tiempo de inactividad, inyectar el script.  Esperar al evento load asegura que el script no compita por ancho de banda y CPU  con recursos tempranos más importantes. Esperar a un momento de inactividad asegura que el navegador no esté manejando tareas más importantes como la entrada del usuario.

Aquí tienes un ejemplo funcional:

window.addEventListener("load", () => {
  window.requestIdleCallback(() => {
    const script = document.createElement("script");
    script.src = "/path/to/script.js";
    document.head.appendChild(script);
  });
});

Mejores prácticas:

  • Carga estos scripts de forma diferida después de que la página haya cargado y espera a un momento de inactividad.
  • Entiende que los scripts cargados con este patrón no tienen garantía de cargarse rápido

5. Scripts Futuros

Los scripts futuros son aquellos que no serán necesarios hasta que se cumplan condiciones específicas. Por ejemplo, un script de checkout de múltiples pasos se vuelve relevante solo después de que un usuario haya añadido artículos a su carrito. Estos scripts a menudo pueden esperar hasta mucho más tarde en el recorrido del usuario.

Echa un vistazo a este ejemplo. Utiliza el intersection observer para cargar la lógica JS necesaria para el script de registro solo cuando el formulario está en el viewport visible.

<!DOCTYPE html>
<html>
  <head>
    <script>
      document.addEventListener("DOMContentLoaded", function () {
        const form = document.querySelector("form");
        const observer = new IntersectionObserver(function (entries) {
          entries.forEach((entry) => {
            if (entry.isIntersecting) {
              const script = document.createElement("script");
              script.src = "/sign-up.js";
              document.head.appendChild(script);
              observer.unobserve(form);
            }
          });
        });
        observer.observe(form);
      });
    </script>
  </head>
  <body>
    <form action="/sign-up" method="post">
      <label for="email">Email:</label>
      <input type="email" id="email" name="email" required />
      <button type="submit">Sign Up</button>
    </form>
  </body>
</html>

Mejores prácticas:

  • Carga estos scripts bajo demanda, activados por acciones del usuario.
  • Usa técnicas de code-splitting para entregar solo las partes necesarias en cada paso.
  • Inyéctalos dinámicamente solo cuando sea necesario, como cuando un usuario hace scroll hasta una sección específica.

Your dev team is busy.

Delegate the performance architecture to a specialist. I handle the optimization track while your team ships the product.

Discuss Resource Allocation >>

  • Parallel Workflows
  • Specialized Expertise
  • Faster Delivery
Optimización de la prioridad de JavaScript para cargas de página más rápidas Core Web Vitals Optimización de la prioridad de JavaScript para cargas de página más rápidas