Capítulo 28 de 35

Conexões Supabase Realtime: Evitando Memory Leaks com WebSockets no React

A Mágica e o Perigo do Tempo Real

O Supabase Realtime permite que você escute mudanças no banco de dados (PostgreSQL) instantaneamente via WebSockets. Se uma nova mensagem for inserida no chat ou o status de um pagamento mudar para "Concluído", a tela do usuário atualiza sozinha em milissegundos — sem a necessidade de recarregar a página ou fazer consultas repetitivas (long-polling).

Apesar de ser uma ferramenta incrivelmente poderosa, ela possui uma armadilha fatal para desenvolvedores inexperientes: O Vazamento de Memória (Memory Leak).


O Erro Amador: Sockets Zumbis

Quando você manda o navegador do cliente abrir um canal WebSocket, ele cria uma conexão TCP contínua, pesada e persistente com o servidor do Supabase.

O maior erro em aplicações Single Page (SPA) como Next.js ou React é assinar o canal e não avisar o navegador que ele deve ser destruído quando o usuário sai da página.

Como NÃO Fazer ❌

Se você escrever o código de escuta sem uma estratégia de limpeza, o vazamento acontece instantaneamente:

// ARQUITETURA PROIBIDA: Sem Cleanup
useEffect(() => {
  // O usuário entra na página e abre um Socket.
  supabase
    .channel('room_1')
    .on('postgres_changes', { event: 'INSERT', schema: 'public', table: 'messages' }, (payload) => {
      console.log('Nova mensagem', payload);
    })
    .subscribe();
    
    // Se o usuário clicar em "Voltar" e depois "Entrar" novamente,
    // um SEGUNDO socket é aberto, rodando em paralelo com o primeiro.
}, []);

O Resultado: Se o usuário navegar por 10 telas diferentes que assinam esse canal, o computador dele ficará com 10 conexões abertas em paralelo, processando e duplicando os mesmos dados até travar o navegador (Out of Memory) e esgotar os limites de conexão simultânea da sua conta Supabase.


A Arquitetura Padrão Ouro: Cleanups no React

No React, o useEffect possui um mecanismo de autodefesa chamado Cleanup Function. Sempre que o componente for destruído (ou seja, quando o usuário sair da página ou a tela mudar), o React vai rodar a função que você retornar lá no final.

Para que sua aplicação escale sem bugs e vazamentos de memória, é obrigatório desinscrever e remover o canal do Supabase removeChannel() antes que a tela suma.

Como FAZER ✅

Copie esta estrutura para qualquer implementação de Realtime. É a única abordagem permitida em nossa engenharia:

import { useEffect, useState } from 'react';
import { createClient } from '@/utils/supabase/client';

export function RealtimeChat() {
  const [messages, setMessages] = useState([]);
  const supabase = createClient();

  useEffect(() => {
    // 1. Criamos a referência para a conexão
    const channel = supabase
      .channel('chat_publico')
      .on('postgres_changes', 
        { event: 'INSERT', schema: 'public', table: 'messages' }, 
        (payload) => {
          // Atualiza o estado com o novo dado do banco
          setMessages((prev) => [...prev, payload.new]);
      })
      .subscribe();

    // 2. A Regra de Ouro: Função de Limpeza (Cleanup)
    return () => {
      // Quando o componente for desmontado (usuário mudar de página),
      // matamos a conexão TCP WebSocket instantaneamente.
      supabase.removeChannel(channel);
    };
  }, []); // Array vazio garante que o setup e o cleanup rodem 1 vez.

  return (
    <div>
      {/* Renderiza a UI... */}
    </div>
  );
}

Resumo Operacional

  1. Abra o Canal: Use supabase.channel().subscribe().
  2. Guarde na Memória: Armazene esse canal em uma variável (const channel).
  3. Mate a Conexão: Retorne a função de cleanup do useEffect executando supabase.removeChannel(channel).

Com essa disciplina arquitetural, seu SaaS pode ter 100 mil usuários simultâneos usando Realtime, pois cada cliente fechará a porta ao sair, mantendo a memória do navegador imaculada e os servidores aliviados.