Guía detallada

Tbt Optimization Guide

Ver contenido

Guía Completa de Optimización de TBT: Eliminando el Bloqueo del Hilo Principal

Introducción

Total Blocking Time (TBT) mide el tiempo acumulado de bloqueo causado por Long Tasks entre First Contentful Paint (FCP) y Time to Interactive (TTI). Un TBT alto significa que tu página se siente poco responsiva durante la fase crítica de carga.

Esta guía proporciona estrategias avanzadas de optimización para reducir TBT, basadas en la documentación oficial de Chrome Developers y las mejores prácticas de web.dev.

¿Nuevo en TBT? Comienza con nuestra guía para principiantes: 📖 Lee: TBT Explicado

Entendiendo los Long Tasks en Profundidad

¿Qué Hace que una Tarea sea “Larga”?

El hilo principal del navegador maneja todo: parsear HTML, ejecutar JavaScript, manejar entrada del usuario y pintar píxeles. Cuando una sola tarea toma más de 50 milisegundos, se convierte en un “Long Task” que bloquea el hilo principal.

Línea de Tiempo del Hilo Principal:
┌────────────────────────────────────────────────────────────────┐
│ [Parse HTML] [Ejecutar JS - 150ms] [Paint] [Clic Ignorado]     │
│              └─── Long Task ───┘           └─ Input bloqueado ─┘│
└────────────────────────────────────────────────────────────────┘

Cálculo de TBT en Detalle

Para cada Long Task, la porción de bloqueo es el tiempo que excede los 50ms:

// Ejemplo: Calculando TBT de duraciones de tareas
const tareas = [
  { duracion: 250 },  // 200ms bloqueo (250 - 50)
  { duracion: 40 },   // 0ms bloqueo (< 50ms umbral)
  { duracion: 90 },   // 40ms bloqueo (90 - 50)
  { duracion: 155 },  // 105ms bloqueo (155 - 50)
];

const tbt = tareas.reduce((total, tarea) => {
  const bloqueo = Math.max(0, tarea.duracion - 50);
  return total + bloqueo;
}, 0);

// TBT = 200 + 0 + 40 + 105 = 345ms

Impacto de TBT en la Puntuación de Lighthouse

Según la documentación de Chrome Developers, TBT contribuye 30% a tu puntuación general de rendimiento en Lighthouse:

Métrica Peso Umbral Bueno
FCP 10% < 1.8s
Speed Index 10% < 3.4s
LCP 25% < 2.5s
TBT 30% < 200ms
CLS 25% < 0.1

Una mejora de TBT de 600ms a 200ms puede aumentar significativamente tu puntuación general.

Estrategia 1: Dividir Long Tasks

La forma más efectiva de reducir TBT es dividir tareas grandes en chunks más pequeños que cedan el control al hilo principal.

Usando setTimeout para Ceder Control

// ❌ Mal: Una tarea larga sincrónica
function procesarItems(items) {
  items.forEach(item => {
    computacionPesada(item);  // Bloquea hilo principal
  });
}

// ✅ Bien: Cediendo entre chunks
async function procesarItemsCediendo(items) {
  const TAMANO_CHUNK = 10;

  for (let i = 0; i < items.length; i += TAMANO_CHUNK) {
    const chunk = items.slice(i, i + TAMANO_CHUNK);

    chunk.forEach(item => computacionPesada(item));

    // Ceder al hilo principal
    await new Promise(resolve => setTimeout(resolve, 0));
  }
}

Usando requestIdleCallback

Para trabajo no crítico, usa requestIdleCallback para ejecutar durante tiempo inactivo del navegador:

function procesarEnTiempoInactivo(tareas) {
  function procesarSiguiente(deadline) {
    while (deadline.timeRemaining() > 0 && tareas.length > 0) {
      const tarea = tareas.shift();
      procesarTarea(tarea);
    }

    if (tareas.length > 0) {
      requestIdleCallback(procesarSiguiente);
    }
  }

  requestIdleCallback(procesarSiguiente);
}

Usando scheduler.yield() (Navegadores Modernos)

La nueva Scheduler API proporciona yield explícito:

async function procesarConScheduler(items) {
  for (const item of items) {
    computacionPesada(item);

    // Ceder para permitir procesamiento de input del usuario
    if ('scheduler' in window) {
      await scheduler.yield();
    }
  }
}

Estrategia 2: Optimizar Carga de JavaScript

Code Splitting

Carga solo el JavaScript necesario para el renderizado inicial:

// webpack.config.js
module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all',
        },
      },
    },
  },
};

Imports Dinámicos

Difiere características no críticas:

// ❌ Mal: Importar todo al inicio
import { caracteristicaPesada } from './caracteristicaPesada';

// ✅ Bien: Import dinámico cuando se necesita
async function cargarCaracteristica() {
  const { caracteristicaPesada } = await import('./caracteristicaPesada');
  caracteristicaPesada();
}

// Solo cargar cuando el usuario interactúa
boton.addEventListener('click', cargarCaracteristica);

Atributos Defer y Async

<!-- Crítico: Cargar y ejecutar en orden -->
<script src="critico.js"></script>

<!-- Defer: Descargar en paralelo, ejecutar después del parsing HTML -->
<script src="app.js" defer></script>

<!-- Async: Descargar en paralelo, ejecutar cuando esté listo -->
<script src="analytics.js" async></script>

Estrategia 3: Usar Web Workers para Computación Pesada

Mueve trabajo intensivo de CPU fuera del hilo principal completamente:

Creando un Web Worker

// worker.js
self.onmessage = function(e) {
  const resultado = computacionPesada(e.data);
  self.postMessage(resultado);
};

function computacionPesada(datos) {
  // Cálculos complejos aquí
  return datosProcesados;
}

Usando el Worker

// main.js
const worker = new Worker('worker.js');

worker.onmessage = function(e) {
  mostrarResultados(e.data);
};

// Enviar trabajo al worker - no bloquea hilo principal
worker.postMessage(conjuntoDatosGrande);

Cuándo Usar Web Workers

Caso de Uso ¿Web Worker? Razón
Procesamiento de imágenes ✅ Sí CPU-intensivo, no necesita DOM
Parsing de datos (JSON) ✅ Sí Puede ser lento para archivos grandes
Ordenar arrays grandes ✅ Sí O(n log n) puede bloquear
Manipulación del DOM ❌ No Workers no pueden acceder al DOM
Cálculos pequeños ❌ No Overhead no vale la pena

Estrategia 4: Optimizar Scripts de Terceros

Los scripts de terceros frecuentemente son los mayores contribuyentes al TBT.

Auditar Impacto de Terceros

Usa el panel de Performance de Chrome DevTools:

  1. Abre DevTools → pestaña Performance
  2. Graba la carga de la página
  3. Busca Long Tasks (esquinas rojas)
  4. Identifica qué scripts los causan

Lazy Load de Scripts de Terceros

// Cargar widget de chat solo cuando el usuario hace scroll cerca del footer
const observer = new IntersectionObserver((entries) => {
  if (entries[0].isIntersecting) {
    cargarWidgetChat();
    observer.disconnect();
  }
});

observer.observe(document.querySelector('#footer'));

function cargarWidgetChat() {
  const script = document.createElement('script');
  script.src = 'https://chat-widget.example.com/widget.js';
  document.body.appendChild(script);
}

Usar Resource Hints

<!-- Establecer conexiones tempranas -->
<link rel="preconnect" href="https://analytics.example.com">
<link rel="dns-prefetch" href="https://ads.example.com">

Self-Host de Recursos Críticos de Terceros

<!-- En lugar de CDN externo -->
<script src="https://cdn.example.com/library.js"></script>

<!-- Self-host para mejor control -->
<script src="/js/vendor/library.js"></script>

Estrategia 5: Optimizar Aplicaciones React/Vue/Angular

Optimizaciones React

// Usar React.lazy para code splitting
const ComponentePesado = React.lazy(() => import('./ComponentePesado'));

function App() {
  return (
    <Suspense fallback={<Cargando />}>
      <ComponentePesado />
    </Suspense>
  );
}

// Usar useDeferredValue para actualizaciones costosas
function ResultadosBusqueda({ query }) {
  const queryDiferido = useDeferredValue(query);
  return <ListaCostosa query={queryDiferido} />;
}

Optimizaciones Vue

// Componentes async
const ComponentePesado = defineAsyncComponent(() =>
  import('./ComponentePesado.vue')
);

// Diferir actualizaciones reactivas
import { computed, shallowRef } from 'vue';

const datosGrandes = shallowRef(null);
const datosProcesados = computed(() => {
  // Solo ejecuta cuando explícitamente se necesita
  return procesoPesado(datosGrandes.value);
});

Optimizaciones Angular

// Lazy load de rutas
const routes: Routes = [
  {
    path: 'dashboard',
    loadChildren: () => import('./dashboard/dashboard.module')
      .then(m => m.DashboardModule)
  }
];

// Usar OnPush change detection
@Component({
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class ComponenteOptimizado {}

Midiendo Mejoras de TBT

Panel de Performance de Chrome DevTools

  1. Abre DevTools (F12)
  2. Ve a la pestaña Performance
  3. Haz clic en Record, luego recarga la página
  4. Detén la grabación después de que cargue la página
  5. Busca:
    • Long Tasks (tareas con esquinas rojas)
    • Actividad del hilo principal (amarillo = JavaScript)
    • Total blocking time en el resumen

Lighthouse CI

Automatiza el tracking de TBT en CI/CD:

# .github/workflows/lighthouse.yml
- name: Run Lighthouse
  uses: treosh/lighthouse-ci-action@v9
  with:
    urls: |
      https://example.com/
    budgetPath: ./budget.json
// budget.json
{
  "timings": [
    {
      "metric": "total-blocking-time",
      "budget": 200
    }
  ]
}

Monitoreo de Usuarios Reales

Rastrea el proxy de TBT (Long Tasks) en producción:

// Observar Long Tasks
const observer = new PerformanceObserver((lista) => {
  for (const entry of lista.getEntries()) {
    if (entry.duration > 50) {
      console.log('Long Task:', entry.duration, 'ms');
      // Enviar a analytics
      analytics.track('long_task', {
        duracion: entry.duration,
        tiempoInicio: entry.startTime
      });
    }
  }
});

observer.observe({ entryTypes: ['longtask'] });

Errores Comunes y Soluciones

Error 1: Procesamiento de Datos Sincrónico

// ❌ Problema: Parsear JSON grande bloquea el hilo principal
const datos = JSON.parse(jsonGrande);

// ✅ Solución: Usar streaming o Web Worker
const worker = new Worker('json-parser-worker.js');
worker.postMessage(jsonGrande);

Error 2: Renders Costosos en React

// ❌ Problema: Re-renderizar componente costoso en cada cambio de estado
function Dashboard() {
  const [filtro, setFiltro] = useState('');
  return <GraficoCostoso datos={todosLosDatos} filtro={filtro} />;
}

// ✅ Solución: Memoizar componentes costosos
const GraficoMemoizado = React.memo(GraficoCostoso);

function Dashboard() {
  const [filtro, setFiltro] = useState('');
  const datosFiltrados = useMemo(() =>
    filtrarDatos(todosLosDatos, filtro), [filtro]
  );
  return <GraficoMemoizado datos={datosFiltrados} />;
}

Error 3: Imágenes No Optimizadas Causando Layout

<!-- ❌ Problema: Decodificación de imagen grande bloquea hilo principal -->
<img src="imagen-enorme.jpg">

<!-- ✅ Solución: Usar decode() y dimensiones apropiadas -->
<img src="optimizada.webp"
     width="800" height="600"
     decoding="async"
     loading="lazy">

Checklist de Optimización de TBT

Antes de desplegar, verifica:

  • [ ] Ningún Long Task > 150ms durante carga de página
  • [ ] Scripts de terceros cargados asincrónicamente
  • [ ] Bundles de JavaScript bajo 200KB (comprimidos)
  • [ ] Computaciones pesadas movidas a Web Workers
  • [ ] Code splitting implementado para rutas/características
  • [ ] React/Vue/Angular usando lazy loading
  • [ ] TBT < 200ms en auditoría de Lighthouse

Artículos Relacionados

Continúa optimizando el rendimiento de tu sitio:

📚 Volver al Hub de Performance SEO - Explora todos los temas de rendimiento


Referencias

  1. Chrome Developers - Total Blocking Time
  2. web.dev - Total Blocking Time (TBT)
  3. Chrome Developers - Lighthouse Performance Scoring
  4. web.dev - Optimize Long Tasks

Pruébalo Tú Mismo

¿Listo para optimizar tu TBT?

🔧 Descarga UXR SEO Analyzer (Gratis, análisis 100% local)


Disclaimer: Los analizadores en esta extensión son guías de referencia basadas en documentación oficial de Chrome Developers y web.dev. No representan verdades absolutas sobre cómo los motores de búsqueda evalúan tu contenido—solo los motores de búsqueda conocen sus algoritmos internos. Usa estas recomendaciones como punto de partida para mejorar tu sitio.

Última actualización: 14 de diciembre de 2025

Artículos relacionados

Versión relacionada

Introducción

Tbt Explained

Total Blocking Time (TBT) es una métrica de rendimiento crítica que mide cuánto tiempo el hilo principal de tu página está bloqueado durante la carga

Hub de categoría

Hub

Performance Seo Hub

El rendimiento es un factor de ranking crítico e impacta directamente la experiencia del usuario

En la misma categoría

Guía detallada

Inp Optimization Guide

Interaction to Next Paint (INP) mide qué tan rápido responde tu página a las interacciones del usuario

Guía detallada

Lcp Optimization Guide

Largest Contentful Paint (LCP) mide cuándo el elemento de contenido más grande se vuelve visible para los usuarios