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
0 frames
0ms total
Third-party
0 frames
0ms total
0 frames
0ms total
Ratio
0%
third-party
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 });