Ejemplo 09: Scripts de terceros

← Volver al índice

El problema de los scripts third-party

Los scripts de terceros (analytics, ads, widgets sociales, etc.) son una de las principales causas de performance issues en sitios web modernos.

LoAF API nos permite identificar exactamente qué scripts de terceros están causando frames lentos mediante script.sourceURL.

⚠️ Dato importante:

Según HTTP Archive, el sitio web promedio carga scripts de 21 dominios diferentes, y los scripts third-party representan el ~45% del JavaScript total.

Simulación de scripts third-party

📊 Resumen de impacto:
First-party
0 frames
0ms total
Third-party
0 frames
0ms total
Ratio
0%
third-party

Frames detectados:

Cómo detectar scripts third-party

Usando sourceURL

const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    for (const script of entry.scripts) {
      const url = new URL(script.sourceURL || location.href);
      const isThirdParty = url.origin !== location.origin;

      if (isThirdParty) {
        console.log('🔴 Third-party script detected:');
        console.log('  Domain:', url.hostname);
        console.log('  Function:', script.sourceFunctionName);
        console.log('  Duration:', script.duration);
      }
    }
  }
});

observer.observe({ type: 'long-animation-frame', buffered: true });

Identificar por dominio

function categorizeScript(sourceURL) {
  const url = new URL(sourceURL);
  const domain = url.hostname;

  // Known third-party domains
  if (domain.includes('google-analytics') || domain.includes('googletagmanager')) {
    return { type: 'analytics', vendor: 'Google Analytics' };
  }

  if (domain.includes('facebook') || domain.includes('connect.facebook')) {
    return { type: 'social', vendor: 'Facebook' };
  }

  if (domain.includes('doubleclick') || domain.includes('googlesyndication')) {
    return { type: 'ads', vendor: 'Google Ads' };
  }

  if (url.origin === location.origin) {
    return { type: 'first-party', vendor: 'Own code' };
  }

  return { type: 'third-party', vendor: 'Unknown' };
}

Principales categorías de third-party

Categoría Ejemplos Impacto típico
Analytics Google Analytics, Mixpanel, Segment 🟡 Medio (20-50ms)
Ads Google Ads, Facebook Ads, AdSense 🔴 Alto (100-300ms)
Social Facebook SDK, Twitter widgets 🟡 Medio (30-80ms)
A/B Testing Optimizely, VWO, Google Optimize 🟡 Medio (20-60ms)
Chat/Support Intercom, Zendesk, Drift 🟢 Bajo (10-30ms)

Estrategias de optimización

1. Lazy loading de third-party scripts

// ANTES: Carga inmediata
<script src="https://analytics.example.com/script.js"></script>

// DESPUÉS: Carga después de interacción o timeout
function loadAnalytics() {
  const script = document.createElement('script');
  script.src = 'https://analytics.example.com/script.js';
  script.async = true;
  document.head.appendChild(script);
}

// Opción 1: Después de load
window.addEventListener('load', () => {
  setTimeout(loadAnalytics, 2000); // 2s después de load
});

// Opción 2: Después de primera interacción
let loaded = false;
['click', 'scroll', 'keydown'].forEach(event => {
  window.addEventListener(event, () => {
    if (!loaded) {
      loadAnalytics();
      loaded = true;
    }
  }, { once: true });
});

2. Usar facades para widgets

// En lugar de cargar YouTube player inmediatamente
// Mostrar thumbnail y cargar player al hacer click

<div class="youtube-facade" data-video-id="abc123">
  <img src="thumbnail.jpg" />
  <button>Play</button>
</div>

<script>
document.querySelectorAll('.youtube-facade').forEach(facade => {
  facade.addEventListener('click', () => {
    // Solo ahora cargar el iframe pesado de YouTube
    loadYouTubePlayer(facade.dataset.videoId);
  });
});
</script>

3. Monitorear impacto en producción

const thirdPartyImpact = new Map();

const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    for (const script of entry.scripts) {
      const url = new URL(script.sourceURL || location.href);

      if (url.origin !== location.origin) {
        const domain = url.hostname;
        const current = thirdPartyImpact.get(domain) || { count: 0, totalTime: 0 };

        thirdPartyImpact.set(domain, {
          count: current.count + 1,
          totalTime: current.totalTime + script.duration
        });
      }
    }
  }
});

// Enviar a analytics cada minuto
setInterval(() => {
  const report = Array.from(thirdPartyImpact.entries()).map(([domain, data]) => ({
    domain,
    count: data.count,
    avgTime: data.totalTime / data.count
  }));

  sendToAnalytics({ thirdPartyReport: report });
  thirdPartyImpact.clear();
}, 60000);
✅ Objetivo:

Identificar qué scripts third-party causan más impacto y tomar decisiones: ¿vale la pena el costo de performance? ¿Se puede optimizar? ¿Se puede eliminar?

Red flags de third-party scripts

🔴 Problemas comunes:
  • Sincronización en cascade: Script A carga Script B que carga Script C
  • Polling agresivo: Scripts que hacen polling cada 100ms
  • Document.write(): Bloquea el parser completamente
  • Sin async/defer: Bloquea la carga inicial
  • Layout thrashing: Scripts que leen/escriben DOM repetidamente

Detectar scripts problemáticos

const problematicScripts = [];

const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    for (const script of entry.scripts) {
      const url = new URL(script.sourceURL || location.href);
      const isThirdParty = url.origin !== location.origin;

      // Red flags
      const hasHighForcedLayout = script.forcedStyleAndLayoutDuration > 50;
      const isVeryLong = script.duration > 200;
      const recurrent = problematicScripts.filter(
        s => s.domain === url.hostname
      ).length > 5;

      if (isThirdParty && (hasHighForcedLayout || isVeryLong || recurrent)) {
        problematicScripts.push({
          domain: url.hostname,
          duration: script.duration,
          forcedLayout: script.forcedStyleAndLayoutDuration,
          timestamp: Date.now()
        });

        console.warn('🚨 Problematic third-party script:', url.hostname);
      }
    }
  }
});

observer.observe({ type: 'long-animation-frame', buffered: true });