June 21, 2025

Server-Sent Events (SSE): Reatividade Simples e Eficiente no Frontend

Como usar Server-Sent Events em microfrontends para fluxos de dados unidirecionais, notificações em tempo real, feature flags dinâmicas e comunicação entre Shell e MFEs.

Server-Sent Events (SSE): Reatividade Simples e Eficiente no Frontend

O Server-Sent Events (SSE) continua sendo uma opção sólida para fluxos de dados unidirecionais no frontend — especialmente em contextos modernos de Micro Frontends (MFEs), onde simplicidade, performance e escalabilidade são essenciais.

Hoje, o SSE vem sendo utilizado em combinações como:

  • Shells orquestradores (Single-SPA, Module Federation, Zephyr)
  • Feature Flags dinâmicas baseadas em AI
  • Edge Functions (Cloudflare Workers, Vercel Functions)
  • Streams de eventos (Redis Streams, Kafka, RabbitMQ)

✅ Vantagens do SSE no Cenário Atual

Simplicidade e Compatibilidade

  • Baseado em HTTP — sem handshakes adicionais
  • Compatível com infraestruturas modernas (HTTP/2, CDN, Serverless)
  • Código leve, sem dependências externas
  • Reconexão automática nativa (EventSource)

Performance e Escalabilidade

  • Ideal para notificações, logs, atualizações contextuais
  • Baixo overhead de conexão
  • Suporte nativo a HTTP/2 multiplexing
  • Cache-friendly com CDNs

🏗️ Aplicações em Arquiteturas de Micro Frontends

Em estruturas com MFEs desacoplados, o SSE pode ser mantido no Shell e os eventos propagados aos MFEs por meio de CustomEvent, mantendo o isolamento dos times e a reatividade em tempo real.

Exemplo com Single-SPA:

// Shell (root-app)
const source = new EventSource('/api/sse');
source.onmessage = ({ data }) => {
  const event = JSON.parse(data);
  window.dispatchEvent(new CustomEvent('sse:event', { detail: event }));
};

// MFE (qualquer framework)
useEffect(() => {
  const handler = (e) => handleIncomingData(e.detail);
  window.addEventListener('sse:event', handler);
  return () => window.removeEventListener('sse:event', handler);
}, []);

🎯 Exemplos de Uso Práticos

1. Atualização em Tempo Real de Feature Flags

// Shell - Feature Flags dinâmicas
const featureFlagsSource = new EventSource('/api/feature-flags/sse');

featureFlagsSource.onmessage = ({ data }) => {
  const flags = JSON.parse(data);
  
  // Broadcast para todos os MFEs
  window.dispatchEvent(new CustomEvent('feature-flags:update', {
    detail: flags
  }));
};

// MFE - Consumir feature flags
useEffect(() => {
  const handleFeatureFlags = (event) => {
    const flags = event.detail;
    setFeatureFlags(flags);
    
    // Renderização condicional baseada em flags
    if (flags.newCheckoutFlow) {
      renderNewCheckout();
    }
  };
  
  window.addEventListener('feature-flags:update', handleFeatureFlags);
  return () => window.removeEventListener('feature-flags:update', handleFeatureFlags);
}, []);

2. Notificações de Deploy para Atualização de MFEs

// Shell - Monitoramento de deploys
const deploySource = new EventSource('/api/deployments/sse');

deploySource.onmessage = ({ data }) => {
  const deployment = JSON.parse(data);
  
  if (deployment.status === 'completed') {
    // Notificar MFEs sobre nova versão disponível
    window.dispatchEvent(new CustomEvent('deployment:completed', {
      detail: {
        mfe: deployment.mfe,
        version: deployment.version,
        timestamp: deployment.timestamp
      }
    }));
  }
};

// MFE - Atualização automática
useEffect(() => {
  const handleDeployment = (event) => {
    const { mfe, version } = event.detail;
    
    if (mfe === 'current-mfe') {
      // Mostrar notificação de atualização
      showUpdateNotification(version);
    }
  };
  
  window.addEventListener('deployment:completed', handleDeployment);
  return () => window.removeEventListener('deployment:completed', handleDeployment);
}, []);

3. Log de Ações em Sistemas Multitenant

// Shell - Logs de auditoria
const auditSource = new EventSource('/api/audit/sse');

auditSource.onmessage = ({ data }) => {
  const auditLog = JSON.parse(data);
  
  // Filtrar por tenant atual
  if (auditLog.tenantId === getCurrentTenant()) {
    window.dispatchEvent(new CustomEvent('audit:log', {
      detail: auditLog
    }));
  }
};

// MFE - Exibir logs em tempo real
useEffect(() => {
  const handleAuditLog = (event) => {
    const log = event.detail;
    
    // Adicionar ao feed de atividades
    addToActivityFeed({
      user: log.user,
      action: log.action,
      timestamp: log.timestamp,
      details: log.details
    });
  };
  
  window.addEventListener('audit:log', handleAuditLog);
  return () => window.removeEventListener('audit:log', handleAuditLog);
}, []);

4. Monitoramento de Estados por Usuário

// Shell - Estados de sessão
const sessionSource = new EventSource('/api/session/sse');

sessionSource.onmessage = ({ data }) => {
  const sessionUpdate = JSON.parse(data);
  
  // Atualizar estado global da sessão
  window.dispatchEvent(new CustomEvent('session:update', {
    detail: sessionUpdate
  }));
};

// MFE - Sincronização de estado
useEffect(() => {
  const handleSessionUpdate = (event) => {
    const session = event.detail;
    
    // Sincronizar estado local com servidor
    setUserStatus(session.status);
    setLastActivity(session.lastActivity);
    
    // Renderização condicional baseada em permissão
    if (session.permissions.includes('admin')) {
      showAdminPanel();
    }
  };
  
  window.addEventListener('session:update', handleSessionUpdate);
  return () => window.removeEventListener('session:update', handleSessionUpdate);
}, []);

📊 Comparativo Técnico (Cenário 2025)

Tecnologia Indicado para Considerações
SSE Stream unidirecional leve e confiável Sem suporte a binários
WebSocket Bidirecional em tempo real Requer mais infraestrutura e estado
WebTransport Baixa latência sobre HTTP/3 Suporte ainda limitado nos browsers
Polling Simplicidade extrema Ineficiente e pouco reativo

🔧 Implementação Avançada

Classe SSE Manager

class SSEManager {
  constructor(baseUrl) {
    this.baseUrl = baseUrl;
    this.connections = new Map();
    this.eventHandlers = new Map();
  }
  
  connect(endpoint, options = {}) {
    const url = `${this.baseUrl}${endpoint}`;
    const source = new EventSource(url);
    
    source.onopen = () => {
      console.log(`🔗 SSE conectado: ${endpoint}`);
    };
    
    source.onmessage = ({ data }) => {
      try {
        const event = JSON.parse(data);
        this.broadcastEvent(endpoint, event);
      } catch (error) {
        console.error('Erro ao processar evento SSE:', error);
      }
    };
    
    source.onerror = (error) => {
      console.error(`❌ Erro SSE em ${endpoint}:`, error);
    };
    
    this.connections.set(endpoint, source);
    return source;
  }
  
  broadcastEvent(endpoint, data) {
    const eventName = `sse:${endpoint.replace('/', ':')}`;
    window.dispatchEvent(new CustomEvent(eventName, { detail: data }));
  }
  
  disconnect(endpoint) {
    const source = this.connections.get(endpoint);
    if (source) {
      source.close();
      this.connections.delete(endpoint);
    }
  }
  
  disconnectAll() {
    this.connections.forEach((source, endpoint) => {
      source.close();
    });
    this.connections.clear();
  }
}

// Uso global
window.sseManager = new SSEManager('/api');

Hook React Personalizado

import { useEffect, useState } from 'react';

export function useSSE(endpoint, options = {}) {
  const [data, setData] = useState(null);
  const [isConnected, setIsConnected] = useState(false);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    if (!window.sseManager) {
      setError('SSE Manager não inicializado');
      return;
    }
    
    const eventName = `sse:${endpoint.replace('/', ':')}`;
    
    const handleEvent = (event) => {
      setData(event.detail);
      setIsConnected(true);
      setError(null);
    };
    
    const handleError = () => {
      setIsConnected(false);
      setError('Erro na conexão SSE');
    };
    
    // Conectar ao endpoint
    const source = window.sseManager.connect(endpoint);
    
    // Configurar listeners
    window.addEventListener(eventName, handleEvent);
    source.addEventListener('error', handleError);
    
    return () => {
      window.removeEventListener(eventName, handleEvent);
      source.removeEventListener('error', handleError);
      window.sseManager.disconnect(endpoint);
    };
  }, [endpoint]);
  
  return { data, isConnected, error };
}

🔍 Integração com Observabilidade

Logging com Last-Event-ID

// Suporte a reconexão com Last-Event-ID
const source = new EventSource('/api/sse', {
  withCredentials: true
});

let lastEventId = null;

source.onmessage = ({ data, lastEventId: eventId }) => {
  lastEventId = eventId;
  
  // Log para observabilidade
  console.log('SSE Event:', {
    data: JSON.parse(data),
    eventId,
    timestamp: new Date().toISOString()
  });
};

// Reconexão com Last-Event-ID
source.addEventListener('error', () => {
  setTimeout(() => {
    const newSource = new EventSource(`/api/sse?lastEventId=${lastEventId}`);
    // ... reconectar
  }, 5000);
});

Monitoramento com Datadog/New Relic

// Métricas customizadas
function trackSSEMetrics(endpoint, eventType, latency) {
  // Datadog
  if (window.DD_RUM) {
    window.DD_RUM.addAction('sse_event', {
      endpoint,
      eventType,
      latency
    });
  }
  
  // New Relic
  if (window.newrelic) {
    window.newrelic.addPageAction('sse_event', {
      endpoint,
      eventType,
      latency
    });
  }
}

🚀 Benefícios em Produção

Para Microfrontends

  • Comunicação desacoplada entre Shell e MFEs
  • Reatividade em tempo real sem complexidade
  • Isolamento de responsabilidades mantido
  • Escalabilidade horizontal facilitada

Para Infraestrutura

  • Baixo custo operacional comparado ao WebSocket
  • Compatibilidade com CDNs e edge computing
  • Suporte nativo a HTTP/2
  • Facilita auditoria e rastreabilidade

Para Desenvolvedores

  • Implementação simples sem dependências
  • Debugging facilitado com logs nativos
  • Fallback automático para reconexão
  • Integração fácil com observabilidade

📚 Referências e Materiais Recomendados

🎯 Conclusão

O uso de SSE em 2025 se mostra coerente com a busca por arquiteturas desacopladas, reativas e de baixo custo operacional. Quando a comunicação bidirecional não é necessária, o SSE é uma solução leve, resiliente e facilmente auditável — especialmente em ambientes com múltiplos MFEs e shell centralizado.

Quando Usar SSE:

  • Notificações em tempo real
  • Feature flags dinâmicas
  • Logs de auditoria em tempo real
  • Atualizações de estado unidirecionais
  • Monitoramento de sistemas

Quando Evitar SSE:

  • Comunicação bidirecional necessária
  • Dados binários para transmitir
  • Latência ultra-baixa crítica
  • Browsers muito antigos sem suporte

Este artigo apresenta uma solução prática para reatividade em tempo real em arquiteturas de microfrontends. Para discussões técnicas, implementações ou dúvidas sobre SSE e comunicação entre MFEs, me encontre no LinkedIn ou GitHub.