
Reducir el tiempo de ejecución de JavaScript: enviar menos, luego diferir, luego dividir
- vuetelemetry
- Guías
- 8 min de lectura
Lighthouse da la cifra, pero la solución casi nunca está en tu propio código. La mayor parte del tiempo de ejecución corresponde a dependencias que se ejecutan antes de que haya nada visible. Tres pasos, en el orden que de verdad funciona, y la única medición que hay que hacer primero.
Lighthouse te dice que la ejecución de JavaScript tardó 3,4 segundos y te ofrece una lista de archivos. El instinto es abrir tu propio código y optimizar bucles. Casi siempre es el sitio equivocado, y la razón es aritmética: en una aplicación típica, tu código es una pequeña parte de los bytes que el navegador analiza, compila y ejecuta.
El tiempo de ejecución son tres costes distintos que las herramientas muestran como uno. El análisis convierte el texto en un árbol sintáctico abstracto, la compilación lo convierte en bytecode y la evaluación lo ejecuta de verdad. Los tres crecen con la cantidad de JavaScript que envías, y por eso la primera palanca no es un código más rápido, sino menos código.
Medir primero, nunca adivinar

Mide antes de tocar nada. Abre el panel Performance, graba una carga de página con la CPU ralentizada a 4x y lee la vista Bottom-Up agrupada por URL. Atribuye los milisegundos a los archivos. Una suposición sobre qué dependencia es cara falla con la frecuencia suficiente como para perder un día.
Enviar menos, antes que nada
Primer paso: enviar menos. La pestaña Coverage de las DevTools de Chrome muestra qué porcentaje de cada archivo nunca se ejecutó. Cifras por encima del 70 % sin usar son habituales, y suelen señalar una biblioteca importada para una sola función, un paquete de polyfills para navegadores que ya no admites, o una biblioteca entera de fechas o de iconos donde bastaban tres importaciones.
- Grabar con la CPU ralentizada a 4x y leer Bottom-Up por URL antes de cambiar nada
- Pestaña Coverage: los archivos con más del 70 % sin usar son los primeros candidatos a eliminar
- Sustituir una biblioteca por una función nativa del navegador cuando exista, Intl antes que una biblioteca de fechas
- defer para los scripts que tocan el DOM, etiquetas de terceros cargadas después del evento load
- Dividir por rutas solo después de quitar lo que no debería enviarse en absoluto
La sustitución rara vez es espectacular. Una biblioteca de formato de fechas de 70 kB puede sustituirse a menudo por `Intl.DateTimeFormat`, que no cuesta nada porque ya está en el navegador. Esa sola sustitución elimina de una vez el análisis, la compilación y la evaluación, en lugar de acelerarlos.
Diferir lo que el primer pintado no necesita
Segundo paso: diferir lo que no hace falta para el primer pintado. La analítica, los widgets de chat, los banners de consentimiento y las bibliotecas de carrusel se ejecutan antes de que la página sea usable sin aportarle nada. `defer` en una etiqueta script mantiene la descarga en paralelo y lleva la ejecución a después del análisis; `async` la ejecuta en cuanto llega, lo que es peor para todo lo que toca el DOM.
En el caso concreto de las etiquetas de terceros, cargarlas después del evento load, o en la primera interacción, saca todo su coste de la ventana que se mide y, sobre todo, de la ventana durante la que el usuario espera.
Dividir el bundle, y en este orden
Tercer paso: dividir el bundle. La división del código por rutas significa que un visitante de la página de inicio no analiza el proceso de pago. En una aplicación Vue, `defineAsyncComponent` y el `import()` dinámico se encargan de ello, y los bundlers modernos dividen automáticamente por ruta cuando las rutas se importan de forma diferida.
El orden de estos tres pasos importa. Dividir un bundle que contiene 200 kB de biblioteca sin usar solo retrasa el momento en que la pagas. Quitar primero la biblioteca hace la división más pequeña y la ganancia mayor.
Lo que el número no dice
Una advertencia sobre la propia cifra. El Total Blocking Time y el Interaction to Next Paint son lo que vive el visitante; el tiempo de ejecución de JavaScript es un componente de ellos, no un sinónimo. Reducir el tiempo de ejecución en una página que ya era rápida no cambia nada perceptible, y una página puede puntuar mal en ejecución y sentirse fluida porque el trabajo ocurre después del pintado.
Los datos de campo zanjan la discusión. Una prueba de laboratorio en un portátil rápido oculta justo a la población que sufre: móviles Android de gama media en una red saturada, donde un análisis de 300 milisegundos se convierte en dos segundos. Si las cifras de campo son buenas y las de laboratorio feas, cree al campo.
Antes de optimizar los scripts, conviene comprobar que los bytes se comprimen en absoluto: gzip vs Brotli, y lo que encontramos al medir 336 organismos públicos.
FAQ
¿Qué causa un tiempo de ejecución de JavaScript alto?
El volumen de JavaScript que el navegador tiene que analizar, compilar y evaluar, dominado en la mayoría de los proyectos por las dependencias más que por el código de la aplicación. Las tareas largas en el hilo principal durante el arranque, normalmente la inicialización del framework y las etiquetas de terceros, son lo que convierte ese volumen en una cifra que el usuario nota.
¿El tiempo de ejecución es lo mismo que el Total Blocking Time?
No. El tiempo de ejecución es cuánto duran los scripts; el Total Blocking Time mide cuánto tiempo estuvo bloqueado el hilo principal durante más de 50 milisegundos seguidos entre el First Contentful Paint y la interactividad. El tiempo de ejecución es un componente de él, y el trabajo que ocurre después del pintado puede sumar tiempo de ejecución sin sumar tiempo de bloqueo.
¿defer o async reducen el tiempo de ejecución?
Ninguno de los dos reduce el trabajo; ambos cambian cuándo ocurre. defer ejecuta el script después del análisis del HTML, en el orden del documento, lo que no bloquea el renderizado y es la opción por defecto correcta para todo lo que toca el DOM. async lo ejecuta en cuanto se descarga, en un orden impredecible.
¿Cuánto JavaScript es demasiado?
No hay un umbral universal, y cualquier cifra presentada como tal es una regla empírica. La prueba útil son los datos de campo: si el Interaction to Next Paint y el Largest Contentful Paint están dentro de sus umbrales para usuarios reales en dispositivos reales, la cantidad que envías es defendible diga lo que diga el número de bytes.
¿Debo abandonar mi framework para resolverlo?
Rara vez, y no como primer paso. Quitar dependencias sin usar, diferir las etiquetas de terceros y dividir por rutas suele recuperar más tiempo que una migración de framework, con una fracción del riesgo. Plantéate la migración solo si la medición señala la propia inicialización del framework después de esos tres pasos.



El orden de estos tres pasos importa. Dividir un bundle que contiene 200 kB de biblioteca sin usar solo retrasa el momento en que la pagas. Quitar primero la biblioteca hace la división más pequeña y la ganancia mayor.