Estrategias de control de carga y ejecución de scripts

Fecha de publicación: 27/09/2025

Autor: Rosario Aznar

JavaScript hace posible que las webs sean dinámicas, interactivas y cuenten con múltiples funcionalidades. Sin embargo, este poder conlleva una gran responsabilidad: una gestión inadecuada de los scripts puede afectar gravemente al rendimiento. En este artículo te cuento de qué va lo de optimizar scripts para mejorar el INP.

Optimizar INP y scripts de JS

Del FID al INP: Optimizar la experiencia de interacción completa, no solo el primer clic

El panorama del rendimiento web experimentó un cambio de paradigma en 2024. Y esto fue por el lanzamiento de la métrica Interaction to Next Paint (INP) como una de las Core Web Vitals, en sustitución de First Input Delay (FID).

Ya no es suficiente optimizar la capacidad de respuesta a la primera interacción del usuario. INP evalúa la latencia de todas las interacciones a lo largo de la visita de un usuario, desde el clic en un menú hasta el envío de un formulario.

La optimización ya no se limita al tiempo de carga inicial, sino que debe abarcar todo el «ciclo de vida de la responsividad» de la página. La congestión del hilo principal del navegador, causada por tareas de JavaScript prolongadas, se ha convertido en un factor de clasificación para posicionar.

El impacto de JavaScript en el renderizado

Para comprender el impacto de JavaScript, es esencial visualizar el proceso de renderizado del navegador. Este proceso, conocido como el Camino crítico de renderizado (Critical Rendering Path), implica la construcción del Document Object Model (DOM) a partir del HTML, y del CSS Object Model (CSSOM) a partir de los estilos. Ambos se combinan en un «Render Tree» que el navegador utiliza para calcular la disposición (layout) y finalmente «pintar» (paint) los píxeles en la pantalla. Cualquier interrupción en este camino retrasa la visualización del contenido para el usuario.   

El efecto bloqueante de <script> en la construcción del DOM

Por defecto, la etiqueta <script> es síncrona y bloqueante. Cuando el analizador (parser) de HTML encuentra una etiqueta <script src=»…»></script> sin atributos adicionales, el proceso se detiene por completo. El navegador debe pausar la construcción del DOM, solicitar el archivo JavaScript a la red, descargarlo y, finalmente, ejecutarlo. Solo después de que el script ha finalizado su ejecución, el analizador de HTML puede reanudar su trabajo desde el punto donde lo dejó. Este comportamiento, conocido como «parser-blocking», es la causa principal de los cuellos de botella en el rendimiento, ya que supone una latencia significativa en el camino crítico de renderizado.   

Las «Long Tasks», el mayor enemigo del INP

El concepto de «Long Tasks» (tareas largas) es importante para entender el INP. Una Long Task se define como cualquier tarea en el hilo principal del navegador que tarda más de 50 milisegundos en completarse.

Durante la ejecución de una Long Task, el hilo principal está completamente ocupado y no puede atender ninguna otra labor, incluidas las interacciones del usuario. Si un usuario hace clic en un botón mientras se ejecuta una Long Task, su acción queda en cola, y la respuesta visual se retrasa, lo que resulta en una puntuación de INP deficiente y una percepción de que la página está «congelada» o no responde.

A menudo se asocian las Long Tasks con cálculos complejos en manejadores de eventos, pero un culpable frecuente y a menudo subestimado es la propia evaluación inicial de los scripts. El proceso que realiza el navegador para analizar, compilar a bytecode y ejecutar un archivo JavaScript es una tarea que consume tiempo en el hilo principal. Si un script es grande y complejo, esta evaluación inicial puede superar fácilmente los 50 ms, convirtiéndose en una Long Task antes incluso de que el usuario haya tenido la oportunidad de interactuar.

Esto crea una ventana de falta de respuesta justo al inicio de la experiencia del usuario, penalizando el INP desde el primer momento. Por lo tanto, optimizar para INP no solo implica escribir manejadores de eventos eficientes, sino también minimizar el coste de bloqueo inicial de la evaluación de scripts.

Como veis, el proceso de cargar una web es más complejo de lo que parece a simple vista. El usuario hace clic y mágicamente la web aparece y puede interactuar con ella, pero detrás del telón está todo este baile de scripts. Si tenéis dudas sobre cómo verlos descargarse, abrid la consola de Devtools > Red y podréis ver la cascada de scripts en directo.

Async y Defer: Estrategias contra el bloqueo de scripts

ASYNC

El atributo async instruye al navegador para que descargue el script de forma asíncrona mientras continúa analizando el resto del documento HTML.

Async implica que el script se ejecuta tan pronto como finaliza su descarga. Si el análisis del HTML aún no ha concluido en ese momento, se pausará para permitir la ejecución del script.

Una consecuencia crítica de este comportamiento es que no se garantiza el orden de ejecución. Si se incluyen varios scripts con el atributo async, se ejecutarán en el orden en que terminen de descargarse, que puede no coincidir con el orden en que aparecen en el código HTML.

¿Cuándo usarlo?

Async es ideal para scripts completamente independientes que no tienen dependencias con el DOM ni con otros scripts. 

Ejemplos: scripts de analítica y de seguimiento de conversiones o anuncios, donde el orden y el momento exacto de ejecución no son críticos para la funcionalidad principal de la página.

DEFER

Al igual que async, el atributo defer permite que el script se descargue en paralelo sin bloquear el análisis del HTML. La diferencia fundamental está en el momento de la ejecución: los scripts con defer solo se ejecutan después de que el análisis del documento HTML ha finalizado por completo, y justo antes de que se dispare el evento DOMContentLoaded.

A diferencia de async, defer garantiza que los scripts se ejecutarán en el orden en que aparecen en el documento. Si scriptB.js depende de scriptA.js, y ambos tienen defer, se puede estar seguro de que scriptA.js se ejecutará antes que scriptB.js.

¿Cuándo usarlo?

defer es la opción más segura y recomendada para la mayoría de los scripts, especialmente aquellos que necesitan interactuar con el DOM o que tienen dependencias entre sí. Es la mejor práctica para cualquier JavaScript no crítico que mejore la funcionalidad de la página.

 

Optimizar scripts INP Rosario Aznar SEO

Imagen generada con Gemini

Optimización de scripts para mejorar Core Web Vitals (INP, LCP y CLS)

La elección entre un script síncrono (por defecto, sin atributos), o asíncrono (async o defer) tiene un impacto directo y medible en las Core Web Vitals. Una estrategia de carga de scripts bien ejecutada es fundamental para optimizar estas métricas (tan importantes para Google y, por tanto, para nosotros).

Cómo optimizar el INP (Interaction to Next Paint)

El INP mide la latencia de una interacción desde que el usuario la inicia hasta que se pinta el siguiente fotograma. Este proceso consta de tres fases: el retraso de entrada (Input Delay), el tiempo de procesamiento (Processing Time) y el retraso de presentación (Presentation Delay).

La ejecución de JavaScript impacta principalmente en el Input Delay. Si el hilo principal está ocupado ejecutando un script (una Long Task), no puede empezar a procesar la interacción del usuario, aumentando drásticamente este retraso.

INP y Defer

El uso generalizado de defer es la estrategia más efectiva para proteger el INP durante la carga inicial. Al posponer la ejecución de todos los scripts no críticos hasta después del análisis del DOM, se asegura que el hilo principal permanezca libre y disponible para responder inmediatamente a las interacciones tempranas del usuario. 

Para tareas más complejas, dividir scripts monolíticos en fragmentos más pequeños que se ejecutan de forma independiente puede evitar que alguna tarea individual se convierta en una Long Task.

Cómo acelerar el LCP (Largest Contentful Paint)

El LCP mide el tiempo que tarda en renderizarse el elemento de contenido más grande visible en el viewport. Los scripts que bloquean el renderizado son un enemigo directo del LCP. Si un script bloqueante se encuentra en el <head> del documento, el navegador no puede continuar analizando el HTML y, por lo tanto, puede que no descubra el elemento LCP (suele ser la imagen principal) hasta que el script se haya descargado y ejecutado, retrasando significativamente la métrica.

LCP y Defer

Aplicar defer a los scripts en el <head> permite que el navegador continúe el análisis, descubra el recurso LCP y comience a solicitarlo a la red mucho antes, mejorando los tiempos de carga.

LCP y Preload

En casos donde el elemento LCP es descubierto tardíamente por diseño (por ejemplo, una imagen de fondo cargada vía CSS o un componente renderizado por JavaScript), se puede utilizar <link rel=»preload»>. Esto le indica al navegador que comience a descargar un recurso específico con alta prioridad, sin esperar a descubrirlo de forma natural. Esto es especialmente útil para asegurar que la imagen del LCP esté disponible tan pronto como sea posible.

Cómo prevenir el CLS (Cumulative Layout Shift)

El CLS mide la estabilidad visual de una página, cuantificando los desplazamientos inesperados del contenido. Un culpable común de un CLS alto son los scripts, especialmente aquellos cargados con async, que insertan contenido en el DOM después de que la página se haya renderizado inicialmente. 

Un banner de cookies, un anuncio o un widget que aparece de repente puede empujar el contenido visible hacia abajo, creando una experiencia negativa para el usuario y una mala puntuación de CLS.

Reservar espacios en el layout para mejorar el CLS

La estrategia fundamental para mitigar el CLS inducido por scripts es reservar espacio estático para el contenido que se cargará dinámicamente. Utilizando CSS, se puede definir un contenedor con dimensiones fijas (o una relación de aspecto) que actúe como un marcador de posición. De esta manera, cuando el script inserte el contenido, este ocupará el espacio ya asignado sin desplazar otros elementos.

Estrategias avanzadas de carga de scripts

Más allá de async y defer, existen herramientas más avanzadas que ofrecen un control granular sobre el proceso de carga. Os cuento cuáles son algunas de ellas:

  • Priorización de recursos con fetchpriority

El atributo fetchpriority es una herramienta de optimización que avisa al navegador sobre la prioridad relativa de descarga de un recurso. Presenta los valores high, low y auto (por defecto). Es importante destacar que fetchpriority afecta a la prioridad de descarga en la cola de red, pero no altera el momento de ejecución definido por async o defer.

Esta herramienta complementa a lo que hemos visto antes. Mientras defer establece cuándo se ejecutará un script, fetchpriority ayuda a definir con qué urgencia debe descargarse en relación con otros recursos.

Casos de uso:

  • Analíticas no críticas: Un script de seguimiento es ideal para async, pero no debería competir por el ancho de banda con imágenes críticas. Bajar su prioridad de descarga es una optimización inteligente.

<script async src=»analytics.js» fetchpriority=»low»></script>

  • Librería esencial diferida: Si una librería principal es necesaria para la interactividad de la página, se debe usar defer. Aumentar su prioridad de descarga asegura que esté lista para ejecutarse tan pronto como el DOM esté parseado, minimizando la espera.

<script defer src=»main-library.js» fetchpriority=»high»></script>

  • Script de A/B testing: Estos scripts a menudo necesitan ejecutarse muy pronto para evitar el parpadeo de contenido. Usar async con fetchpriority=»high» puede ser una solución viable, siempre que el script sea extremadamente ligero.

<script async src=«ab-test.js» fetchpriority=«high»></script>

  • Carga bajo demanda con import() dinámico

El code splitting es una técnica que consiste en dividir un gran archivo monolítico de JavaScript en fragmentos más pequeños («chunks») que pueden ser cargados bajo demanda. La forma moderna de implementar esto es a través de la función dinámica import().

Esta es la forma más granular de control. En lugar de decidir solo cómo cargar un script al inicio, permite decidir si un script debe cargarse en absoluto durante la sesión del usuario. Cargar un módulo de JavaScript solo cuando es estrictamente necesario (por ejemplo, cuando un usuario hace clic en un botón para abrir una funcionalidad específica) reduce drásticamente el peso inicial de la página, mejora el Time to Interactive (TTI) y libera el hilo principal.

Ejemplo: Cargar un módulo de chat al hacer clic

const chatButton = document.getElementById(‘chat-button’);

chatButton.addEventListener(‘click’, async () => {
  try {
    // El navegador solo descargará y ejecutará este módulo tras el clic.
    const { initializeChat } = await import(‘./modules/chat-widget.js’);
    initializeChat();
    chatButton.style.display = ‘none’; // Ocultar el botón después de la carga
  } catch (error) {
    console.error(‘Failed to load chat module’, error);
  }
});

  • Gestión de scripts de terceros

Los scripts de terceros (widgets de redes sociales, plataformas de anuncios, etc.) son un desafío para los SEOs como nosotros, ya que su contenido y configuración de caché están fuera de nuestro control. Además de usar async o defer, se pueden aplicar sugerencias de recursos para mitigar la latencia de red.

  • preconnect y dns-prefetch: Estas directivas <link> en el <head> le indican al navegador que realice acciones de red de forma anticipada. dns-prefetch solo resuelve el DNS, mientras que preconnect va un paso más allá y completa la búsqueda de DNS, el handshake TCP y la negociación TLS. Usar preconnect para dominios de terceros críticos puede ahorrar cientos de milisegundos en el tiempo de conexión. Podríamos decir que hace la misma función que el preload, pero para recursos externos.
  • Lazy Loading con IntersectionObserver: Para iframes o widgets pesados de terceros que están por debajo del below the fold, se puede retrasar su carga hasta que el usuario se desplace cerca de ellos. La API IntersectionObserver de JavaScript es una forma muy eficiente de detectar cuándo un elemento entra en el viewport, momento en el cual se puede inyectar dinámicamente el script o el iframe del tercero.

Árbol de decisión para la carga de scripts

Nunca olvidemos que el SEO es estrategia. Para aplicar estos conceptos de manera efectiva, es útil seguir un proceso de toma de decisiones. 

1. ¿Es el script absolutamente esencial para el primer renderizado del contenido visible?

  • Sí: Si es muy pequeño, considéralo para ser inline. Si es externo pero crítico, debe cargarse de forma síncrona en el <head> (un caso muy raro y peligroso para el rendimiento que debe evitarse siempre que sea posible).
  • No: Continuar al paso 2.

2. ¿El script depende de que el DOM esté completamente analizado o de que otros scripts se hayan ejecutado primero?

  • Sí: Usa defer. Esto garantiza tanto la disponibilidad del DOM como el orden de ejecución. Continuar al paso 3.
  • No: Usa async. El script es independiente y puede ejecutarse en cualquier momento. Continuar al paso 3.

3. ¿Cuál es la prioridad del script para la experiencia del usuario?

  • Alta (ej. funcionalidad principal de la UI): Añade fetchpriority=»high».
  • Baja (ej. script de seguimiento, analítica): Añade fetchpriority=»low».

4. ¿El script solo es necesario después de una interacción específica del usuario (ej. abrir un modal, usar una calculadora)?

  • Sí: No lo cargues inicialmente. Utiliza import() dinámico para cargarlo solo cuando el usuario lo requiera.

Conclusiones

La gestión de la carga y ejecución de scripts ha evolucionado de ser una optimización técnica de nicho a una disciplina especializada dentro del WPO y siempre con enfoque en la experiencia de los usuarios. 

La transición a INP como Core Web Vital (desde marzo de 2024) consolida esta realidad: un sitio web rápido ya no es solo aquel que carga su contenido inicial velozmente, sino aquel que se mantiene fluido y responsive durante toda la interacción del usuario.

Todas las técnicas que hemos visto en este artículo (defer, async, fetchpriority, import()…) son herramientas que tenemos a nuestra disposición. Es esencial aprender a utilizarlas, o como mínimo, entender cómo funcionan y pedir su implementación a los equipos dev.

Cada decisión sobre cómo y cuándo se carga un script es una decisión que impacta directamente en la percepción del usuario y, por extensión, en el rendimiento SEO. Optimizar la ejecución de JavaScript es sinónimo de optimizar la experiencia del usuario.

Preguntas Frecuentes (o que al menos yo me preguntaba)

  • ¿Qué ocurre si uso async y defer en la misma etiqueta <script>?
    En los navegadores modernos que soportan ambos, async tiene precedencia y el atributo defer es ignorado. El script se comportará como si solo tuviera el atributo async. En navegadores muy antiguos que solo soportan defer, podría actuar como un script diferido.
  • ¿Estos atributos funcionan para scripts inline?
    No. Los atributos async y defer solo tienen efecto en scripts externos que tienen un atributo src. Son ignorados para scripts escritos directamente dentro de las etiquetas <script>…</script>, ya que no hay un proceso de descarga de red que gestionar.11
  • ¿Sigue siendo válido colocar los scripts justo antes de </body>?
    Esta era la mejor práctica antes de la adopción generalizada de async y defer. Sigue siendo una técnica funcional y mucho mejor que colocar scripts bloqueantes en el <head>. Sin embargo, usar defer en el <head> es técnicamente superior. El «preload scanner» del navegador puede descubrir los recursos en el <head> muy temprano y comenzar a descargarlos en segundo plano, incluso mientras el resto del documento HTML se está descargando. Un script al final del </body> solo será descubierto después de que se haya descargado y analizado todo el documento.
  • ¿Cómo afecta type=»module» al comportamiento de carga por defecto?
    Los scripts declarados como módulos de ES (type=»module») se comportan como si tuvieran el atributo defer por defecto. Se descargan de forma asíncrona y se ejecutan después de que el análisis del HTML ha finalizado, manteniendo el orden de aparición. Por lo tanto, añadir defer a un script de tipo módulo es redundante y no tiene efecto adicional.
  • ¿Puede una carga async o defer empeorar el LCP?
    Sí, es una posibilidad importante a considerar. Si el elemento LCP de una página es renderizado por JavaScript (una práctica común en Single-Page Applications o en componentes interactivos), entonces diferir o cargar asíncronamente ese script específico retrasará la aparición del LCP. En tales casos, ese script se convierte en un recurso crítico para el renderizado y debe ser optimizado para cargarse y ejecutarse lo más rápido posible, a menudo sin async ni defer.
Rosario Aznar

Rosario Aznar

Especialista en SEO Técnico. Experta en posicionar webs de todo tipo de tecnologías. Además, es formadora en varias escuelas de Marketing digital, ponente en congresos y redactora de contenidos SEO, IA y el sector tech en diversos medios.

¿Colaboramos?

¿Quieres colaborar conmigo en la redacción de artículos o para dar una ponencia? ¡Escríbeme y nos tomamos un café virtual!

Contacto