Performance

Métricas e Monitoramento

Como medir e monitorar performance em produção.

Por que Medir Performance?

Medir é essencial para:

  • Identificar problemas: Encontrar gargalos reais
  • Validar otimizações: Confirmar que mudanças melhoram performance
  • Monitorar degradação: Detectar quando performance piora
  • Tomar decisões: Baseadas em dados, não suposições

Core Web Vitals

Largest Contentful Paint (LCP)

Mede quando o maior elemento de conteúdo é renderizado.

// Medir LCP
new PerformanceObserver((entryList) => {
  const entries = entryList.getEntries();
  const lastEntry = entries[entries.length - 1];
  console.log('LCP:', lastEntry.renderTime || lastEntry.loadTime);
}).observe({ entryTypes: ['largest-contentful-paint'] });

First Input Delay (FID)

Mede latência da primeira interação do usuário.

// Medir FID
new PerformanceObserver((entryList) => {
  const entries = entryList.getEntries();
  entries.forEach((entry) => {
    console.log('FID:', entry.processingStart - entry.startTime);
  });
}).observe({ entryTypes: ['first-input'] });

Cumulative Layout Shift (CLS)

Mede instabilidade visual durante carregamento.

// Medir CLS
let clsValue = 0;
let clsEntries = [];

new PerformanceObserver((entryList) => {
  for (const entry of entryList.getEntries()) {
    if (!entry.hadRecentInput) {
      clsValue += entry.value;
      clsEntries.push(entry);
    }
  }
  console.log('CLS:', clsValue);
}).observe({ entryTypes: ['layout-shift'] });

Performance API

// Tempos de navegação
const perfData = performance.timing;
const pageLoadTime = perfData.loadEventEnd - perfData.navigationStart;
const domReadyTime = perfData.domContentLoadedEventEnd - perfData.navigationStart;
const connectTime = perfData.responseEnd - perfData.requestStart;

console.log('Page Load:', pageLoadTime);
console.log('DOM Ready:', domReadyTime);
console.log('Connect:', connectTime);

Resource Timing

// Tempos de recursos
performance.getEntriesByType('resource').forEach((resource) => {
  console.log({
    name: resource.name,
    duration: resource.duration,
    size: resource.transferSize,
    type: resource.initiatorType
  });
});

User Timing

// Marcações customizadas
performance.mark('inicio-processamento');
// ... código ...
performance.mark('fim-processamento');

performance.measure(
  'tempo-processamento',
  'inicio-processamento',
  'fim-processamento'
);

const medida = performance.getEntriesByName('tempo-processamento')[0];
console.log('Tempo:', medida.duration);

Web Vitals Library

Instalação e Uso

npm install web-vitals
import { onLCP, onFID, onCLS } from 'web-vitals';

// LCP
onLCP((metric) => {
  console.log('LCP:', metric.value);
  // Enviar para analytics
  sendToAnalytics('LCP', metric.value);
});

// FID
onFID((metric) => {
  console.log('FID:', metric.value);
  sendToAnalytics('FID', metric.value);
});

// CLS
onCLS((metric) => {
  console.log('CLS:', metric.value);
  sendToAnalytics('CLS', metric.value);
});

Enviar para Analytics

function sendToAnalytics(metricName, value) {
  // Google Analytics
  gtag('event', metricName, {
    value: Math.round(value),
    event_category: 'Web Vitals',
    event_label: metricName,
    non_interaction: true
  });
  
  // Ou enviar para seu backend
  fetch('/api/metrics', {
    method: 'POST',
    body: JSON.stringify({
      name: metricName,
      value: value,
      url: window.location.href
    })
  });
}

Real User Monitoring (RUM)

Coletar Métricas de Usuários Reais

// Coletor de métricas
class PerformanceCollector {
  constructor() {
    this.metrics = {};
    this.collect();
  }
  
  collect() {
    // Core Web Vitals
    this.collectWebVitals();
    
    // Métricas customizadas
    this.collectCustomMetrics();
    
    // Enviar periodicamente
    this.sendMetrics();
  }
  
  collectWebVitals() {
    import('web-vitals').then(({ onLCP, onFID, onCLS }) => {
      onLCP((metric) => this.metrics.lcp = metric.value);
      onFID((metric) => this.metrics.fid = metric.value);
      onCLS((metric) => this.metrics.cls = metric.value);
    });
  }
  
  collectCustomMetrics() {
    // Tempo até interatividade
    window.addEventListener('load', () => {
      this.metrics.ttl = performance.now();
    });
  }
  
  sendMetrics() {
    // Enviar quando página for fechada
    window.addEventListener('beforeunload', () => {
      navigator.sendBeacon('/api/metrics', JSON.stringify(this.metrics));
    });
  }
}

new PerformanceCollector();

Ferramentas de Monitoramento

Google Analytics

// Enviar métricas para GA
function sendToGA(metricName, value) {
  gtag('event', metricName, {
    value: Math.round(value),
    event_category: 'Performance',
    non_interaction: true
  });
}

Custom Dashboard

// Enviar para seu backend
async function sendMetrics(metrics) {
  try {
    await fetch('/api/performance', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        ...metrics,
        timestamp: Date.now(),
        url: window.location.href,
        userAgent: navigator.userAgent
      })
    });
  } catch (error) {
    console.error('Erro ao enviar métricas:', error);
  }
}

Alertas e Thresholds

Configurar Alertas

// Alertar se métricas estiverem ruins
function checkMetrics() {
  const thresholds = {
    lcp: 2500, // 2.5s
    fid: 100,  // 100ms
    cls: 0.1   // 0.1
  };
  
  if (this.metrics.lcp > thresholds.lcp) {
    console.warn('LCP acima do threshold:', this.metrics.lcp);
    // Enviar alerta
  }
  
  if (this.metrics.fid > thresholds.fid) {
    console.warn('FID acima do threshold:', this.metrics.fid);
  }
  
  if (this.metrics.cls > thresholds.cls) {
    console.warn('CLS acima do threshold:', this.metrics.cls);
  }
}

Performance Budget

Definir Orçamentos

const budget = {
  lcp: 2500,
  fid: 100,
  cls: 0.1,
  jsSize: 200 * 1024, // 200KB
  cssSize: 50 * 1024,  // 50KB
  imageSize: 500 * 1024 // 500KB
};

function checkBudget() {
  // Verificar tamanhos
  const resources = performance.getEntriesByType('resource');
  const jsSize = resources
    .filter(r => r.name.endsWith('.js'))
    .reduce((sum, r) => sum + r.transferSize, 0);
  
  if (jsSize > budget.jsSize) {
    console.warn('JS excede orçamento:', jsSize);
  }
}

Lighthouse CI

Integração Contínua

# .github/workflows/lighthouse.yml
name: Lighthouse CI
on: [push, pull_request]
jobs:
  lighthouse:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v2
      - run: npm install
      - run: npm run build
      - uses: treosh/lighthouse-ci-action@v7
        with:
          urls: |
            http://localhost:3000
          uploadArtifacts: true
          temporaryPublicStorage: true

Dashboards

Visualizar Métricas

// Dashboard simples
function createDashboard(metrics) {
  const dashboard = document.createElement('div');
  dashboard.innerHTML = `
    <h2>Performance Metrics</h2>
    <div>
      <p>LCP: ${metrics.lcp}ms</p>
      <p>FID: ${metrics.fid}ms</p>
      <p>CLS: ${metrics.cls}</p>
    </div>
  `;
  document.body.appendChild(dashboard);
}

Checklist de Monitoramento

  • Core Web Vitals implementados
  • Métricas enviadas para analytics
  • RUM configurado
  • Alertas configurados
  • Performance budget definido
  • Dashboard de métricas
  • Lighthouse CI integrado
  • Monitoramento contínuo

Exemplo Completo

// Sistema completo de monitoramento
import { onLCP, onFID, onCLS } from 'web-vitals';

class PerformanceMonitor {
  constructor() {
    this.metrics = {};
    this.init();
  }
  
  init() {
    // Coletar Web Vitals
    onLCP((metric) => this.recordMetric('lcp', metric));
    onFID((metric) => this.recordMetric('fid', metric));
    onCLS((metric) => this.recordMetric('cls', metric));
    
    // Coletar métricas customizadas
    this.collectCustomMetrics();
    
    // Enviar métricas
    this.setupReporting();
  }
  
  recordMetric(name, metric) {
    this.metrics[name] = {
      value: metric.value,
      rating: metric.rating,
      delta: metric.delta
    };
    
    // Verificar thresholds
    this.checkThresholds(name, metric.value);
  }
  
  checkThresholds(name, value) {
    const thresholds = {
      lcp: { good: 2500, needsImprovement: 4000 },
      fid: { good: 100, needsImprovement: 300 },
      cls: { good: 0.1, needsImprovement: 0.25 }
    };
    
    const threshold = thresholds[name];
    if (value > threshold.needsImprovement) {
      console.error(`${name} está ruim: ${value}`);
      this.sendAlert(name, value);
    }
  }
  
  collectCustomMetrics() {
    // TTFB
    const ttfb = performance.timing.responseStart - 
                  performance.timing.requestStart;
    this.metrics.ttfb = ttfb;
    
    // Tamanho total
    const resources = performance.getEntriesByType('resource');
    const totalSize = resources.reduce((sum, r) => 
      sum + r.transferSize, 0);
    this.metrics.totalSize = totalSize;
  }
  
  setupReporting() {
    // Enviar quando página fechar
    window.addEventListener('beforeunload', () => {
      this.sendMetrics();
    });
    
    // Ou enviar periodicamente
    setInterval(() => {
      this.sendMetrics();
    }, 60000); // A cada minuto
  }
  
  sendMetrics() {
    const data = {
      ...this.metrics,
      url: window.location.href,
      timestamp: Date.now(),
      userAgent: navigator.userAgent
    };
    
    // Usar sendBeacon para garantir envio
    navigator.sendBeacon('/api/metrics', JSON.stringify(data));
  }
  
  sendAlert(name, value) {
    // Enviar alerta crítico
    fetch('/api/alerts', {
      method: 'POST',
      body: JSON.stringify({ name, value, url: window.location.href })
    });
  }
}

// Inicializar
new PerformanceMonitor();
Monitore métricas reais de usuários (RUM) além de testes sintéticos. Métricas reais mostram a experiência real dos usuários em diferentes condições.