Assinar
Voltar
Frontend

Tunelamento em tempo real no React: SSE e SignalR na prática

Precisa empurrar dados do servidor para a tela sem ficar batendo em polling? Entenda a diferença entre Server-Sent Events e SignalR e veja como consumir cada um no React, com código real e confirmado na fonte.

λ

Interfaces modernas raramente ficam paradas esperando o usuário apertar F5. Notificações, cotações, placares, logs de deploy, tokens de um LLM chegando aos poucos — tudo isso pede que o servidor empurre os dados para o navegador assim que eles existem. É o que chamo informalmente de “tunelamento”: abrir um canal por onde o backend joga eventos direto na tela. Neste artigo comparo duas formas maduras de fazer isso no React — Server-Sent Events (SSE) e SignalR — e mostro código prático de cada uma.

Server-Sent Events: streaming unidirecional sobre HTTP

SSE é um padrão do próprio navegador. O cliente abre uma conexão HTTP persistente com a API EventSource, e o servidor mantém essa conexão aberta enviando mensagens no formato text/event-stream. É um fluxo de mão única: só do servidor para o cliente.

Dois pontos que a documentação da MDN e do web.dev deixam claros e que fazem o SSE brilhar:

  • Reconexão automática. Se a conexão cai, o navegador tenta reabrir sozinho (por volta de 3 segundos por padrão). O servidor pode ajustar esse intervalo com o campo retry: e retomar de onde parou usando o id: de cada evento.
  • Zero protocolo novo. Roda sobre HTTP comum, passa por proxies e infraestrutura HTTP sem cerimônia.

Atenção a um limite histórico: sobre HTTP/1.1, os navegadores permitem no máximo ~6 conexões SSE simultâneas por domínio (contando todas as abas). Sob HTTP/2 esse teto sobe bastante, porque as requisições são multiplexadas.

Consumindo um SSE no React

O padrão é criar o EventSource dentro de um useEffect e sempre limpar a conexão no retorno. Use onmessage para eventos sem nome e addEventListener para eventos nomeados (o campo event: enviado pelo servidor).

import { useEffect, useState } from "react";

function Cotacoes() {
  const [precos, setPrecos] = useState([]);

  useEffect(() => {
    const source = new EventSource("/api/cotacoes");

    // Evento padrão (sem campo "event:" no servidor)
    source.onmessage = (e) => {
      const dado = JSON.parse(e.data);
      setPrecos((atual) => [dado, ...atual].slice(0, 20));
    };

    // Evento nomeado: servidor envia "event: alerta"
    source.addEventListener("alerta", (e) => {
      console.warn("Alerta:", e.data);
    });

    source.onerror = (err) => {
      console.error("Falha no EventSource:", err);
      // O navegador já tenta reconectar sozinho.
    };

    return () => source.close(); // fecha ao desmontar
  }, []);

  return (
    <ul>
      {precos.map((p, i) => (
        <li key={i}>{p.ativo}: {p.valor}</li>
      ))}
    </ul>
  );
}

export default Cotacoes;

Repare que não há lógica de reconexão manual: o EventSource cuida disso. Você só fecha explicitamente com source.close() quando o componente sai de cena.

SignalR: canal bidirecional com fallback automático

SignalR é a biblioteca de tempo real do ASP.NET Core. Diferente do SSE, ela é bidirecional: o cliente chama métodos no servidor (o “hub”) e o servidor chama métodos no cliente. Por baixo, o SignalR escolhe o melhor transporte disponível — de preferência WebSockets, com fallback para Server-Sent Events e, por último, Long Polling. Ou seja: o SSE pode ser até o encanamento interno do SignalR quando WebSocket não está disponível.

No cliente JavaScript/TypeScript você usa o pacote oficial @microsoft/signalr:

npm install @microsoft/signalr

Conectando a um hub no React

Monte a conexão com HubConnectionBuilder, registre os handlers com connection.on(...) antes de chamar start() (boa prática recomendada pela Microsoft) e habilite a reconexão com withAutomaticReconnect().

import { useEffect, useRef, useState } from "react";
import * as signalR from "@microsoft/signalr";

function Chat() {
  const [mensagens, setMensagens] = useState([]);
  const conexaoRef = useRef(null);

  useEffect(() => {
    const connection = new signalR.HubConnectionBuilder()
      .withUrl("/chatHub")
      .withAutomaticReconnect() // 0, 2, 10 e 30s por padrão
      .configureLogging(signalR.LogLevel.Information)
      .build();

    // Servidor chama: Clients.All.SendAsync("ReceiveMessage", user, msg)
    connection.on("ReceiveMessage", (user, message) => {
      setMensagens((atual) => [...atual, `${user}: ${message}`]);
    });

    connection.onreconnecting(() => console.log("Reconectando..."));
    connection.onreconnected(() => console.log("Reconectado!"));

    connection
      .start()
      .then(() => console.log("SignalR conectado."))
      .catch((err) => console.error(err));

    conexaoRef.current = connection;
    return () => { connection.stop(); };
  }, []);

  const enviar = async (user, texto) => {
    try {
      // Chama um método público do hub no servidor
      await conexaoRef.current.invoke("SendMessage", user, texto);
    } catch (err) {
      console.error(err);
    }
  };

  return (
    <ul>
      {mensagens.map((m, i) => (
        <li key={i}>{m}</li>
      ))}
    </ul>
  );
}

export default Chat;

Do lado do cliente há duas formas de falar com o servidor: invoke, que retorna uma Promise resolvida com o valor de retorno do método do hub, e send, que apenas dispara a mensagem sem esperar resposta. Para receber, sempre connection.on("NomeDoMetodo", ...).

Sobre reconexão: sem parâmetros, withAutomaticReconnect() tenta reconectar aos 0, 2, 10 e 30 segundos e desiste após quatro falhas. Você pode passar um array de intervalos ou um IRetryPolicy customizado. E atenção: ele não retenta falhas do start() inicial — isso você trata na mão.

SSE x SignalR: qual escolher?

Critério Server-Sent Events SignalR
Direção Unidirecional (servidor → cliente) Bidirecional (cliente ↔ servidor)
Transporte HTTP (text/event-stream) WebSockets, com fallback para SSE e Long Polling
Cliente Nativo (EventSource), sem lib Pacote @microsoft/signalr
Reconexão Automática pelo navegador withAutomaticReconnect() (opt-in)
Servidor Qualquer stack HTTP Acoplado ao ASP.NET Core / Azure SignalR
Melhor para Feeds, notificações, streaming de texto Chat, colaboração, jogos, RPC em tempo real

A regra prática: se o fluxo é só do servidor para a tela e você quer simplicidade máxima sem depender de uma stack específica, SSE resolve com elegância — e sem instalar nada no front. Se você precisa de via dupla, chamadas RPC entre cliente e servidor, grupos, presença e um degradê robusto de transporte, especialmente num backend .NET, o SignalR entrega tudo pronto. Não são rivais absolutos: o SignalR inclusive usa SSE como um de seus transportes internos.

Comece pelo mais simples que atende ao caso de uso. Muita gente sobe um WebSocket completo para o que um humilde EventSource resolveria em dez linhas.

Fontes

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *