Você está pagando o imposto da SPA por páginas que basicamente movimentam formulários e tabelas. Enquanto isso, sua equipe está lutando contra bugs de hidratação e casos de borda da reatividade que não têm nada a ver com o seu negócio. Há um caminho mais simples que de repente voltou a soar moderno: HTML sobre WebSockets — UI renderizada no servidor, com diffs e streaming para o navegador com quase nenhum JavaScript.
A ideia não é nova. O que mudou foi a viabilidade em produção. Balanceadores de carga, CDNs e WAFs hoje lidam bem com conexões persistentes. Frameworks como Phoenix LiveView, Rails Turbo Streams (Hotwire), Laravel Livewire, htmx e o Blazor Server do .NET amadureceram. Um novo thread no HN nesta semana — “HTML over WebSockets: real-time SPAs with barely any JavaScript” — captura por que as equipes estão revisitando isso. Se você opera um produto B2B em que “tempo real” significa notificações, dashboards, cursores colaborativos e streams de tokens de IA, você deveria avaliá-lo.
A decisão: quando HTML-over-the-wire vence
Use isto como um framework de go/no-go. Quanto mais itens você marcar, mais forte é o caso.
1) Sua UI é muito de leitura, de formulários ou de dashboards
- Consoles de administração CRUD, dashboards de operações, ferramentas de back-office, SaaS B2B com interação moderada.
- Indicadores colaborativos (presença, cursores), notificações ou atualizações abaixo de 10 Hz (p.ex., atualizações de tabela, luzes de status) — não jogos em canvas a 60 fps.
- SEO importa? HTML renderizado no servidor é SEO nativo. Sem dança de hidratação.
2) Sua equipe é forte em back-end ou é regulada
- Engenheiros de back-end conseguem entregar features de UI sem passar por um calvário de React/GraphQL.
- Segurança/compliance prefere o servidor como fonte da verdade; menos lógica no cliente, menos segredos no navegador.
3) Você não precisa de offline-first robusto
- HTML-over-the-wire assume presença online. Modos offline além de reenvio otimista de formulários são dolorosos. Se offline é core, fique com SPA/nativo.
4) Você tem necessidades de tempo real além de streaming unidirecional
- Se você só precisa de streaming do servidor para o cliente (p.ex., saída de tokens de IA), SSE é mais simples. Se também precisa de pings do cliente, formulários ao vivo ou presença, WebSockets consolida os protocolos.
Se isso soa como o seu produto, você provavelmente pode remover 70–90% do código de front-end nessas superfícies. Em nossos pods de entrega, features B2B com muito CRUD passaram a ser entregues 25–40% mais rápido após migrar de um shell React/GraphQL/SPA para HTML dirigido pelo servidor com um cliente fino. Menos dependências, menos bugs de estado, menos artefatos de build para quebrar.
Como é “HTML over WebSockets” na prática
Você mantém a renderização no servidor, mas o servidor envia fragmentos incrementais de HTML por um socket persistente sempre que o estado muda. O navegador aplica diffs ao DOM vivo. Código mínimo no cliente, normalmente uma biblioteca de morph/patch:
- Phoenix LiveView: o servidor rastreia um processo por view do cliente; envia diffs eficientes via WebSockets; o cliente aplica patches no DOM.
- Rails Hotwire/Turbo Streams: o servidor emite fragmentos de HTML e diretivas; o navegador faz morph no DOM por ID ou insere via streams.
- Laravel Livewire: o PHP renderiza componentes no servidor; diffs e eventos fluem via AJAX/WebSockets.
- htmx: HTML dirigido por hipermídia com atributos; adicione um endpoint WebSocket ou SSE; htmx troca trechos do DOM.
- Blazor Server: componentes renderizam no servidor; SignalR sincroniza diffs de componentes com o cliente.
Na prática: você escreve templates/componentes no servidor; emite eventos quando o modelo muda; o framework distribui os menores patches de DOM possíveis para os clientes conectados. Você evita duplicar validação e lógica de negócio no cliente. Mantém um único modelo mental.
Arquitetura e escala sem surpresas
Orçamentos de conexão
Sockets persistentes não são de graça. Planeje com orçamentos conservadores e testáveis:
- Memória por conexão: 2–10 KB no app mais overhead de kernel/TLS. Números reais dependem do framework e do estado por view. Phoenix LiveView é notoriamente enxuto; estados pesados em componentes no servidor não são.
- Concorrência por nó: 20k–100k conexões WebSocket por instância de médio porte (8–16 vCPU, 16–64 GB RAM) é realista com Elixir/Go/Java. Node/.NET também podem performar bem com tuning. Não adivinhe — faça teste de carga.
- Timeouts de inatividade: defina 5–20 minutos no LB e no app para evitar sessões fantasma. AWS ALB/NLB, GCP HTTPS LB, Cloudflare e Fastly suportam WebSockets; aumente os padrões (muitas vezes 60s) para combinar com sua UX.
Escala stateless com uma camada de canais
Não prenda todo o estado do app a um único processo. Use uma camada de canais/pub-sub para broadcast de updates e para sobreviver a failovers:
- Elixir: Phoenix.PubSub (Redis/NATS opcionais), Presence para rastreamento.
- Rails/Laravel: Redis pub/sub; fan-out com ActionCable/AnyCable (Rails) ou Soketi/serviços compatíveis com Pusher (Laravel/Node).
- .NET: Azure SignalR Service ou backplane via Redis.
Com fan-out via Redis/NATS, você pode rodar múltiplas réplicas do app atrás de um LB L7 normal, sem exigir sticky sessions. Se seu framework armazena estado por conexão no servidor (p.ex., LiveView), use registries distribuídos e torne reconexões baratas.
Backpressure e coalescência de atualizações
- Faça fila de mensagens de saída por conexão e descarte ou coalesça se a fila acumular. É melhor entregar o estado mais recente do que todo estado intermediário.
- Agrupe patches de DOM em intervalos de 16–50 ms sob rajadas, para reduzir tempestades de patches e thrash de layout no navegador.
- Aplique limitação de taxa (throttling) em fontes de alta frequência (p.ex., métricas a 10 Hz); faça downsample para 1–2 Hz por widget visível ao usuário, a menos que o usuário solicite explicitamente taxas maiores.
Autenticação e segurança
- Autentique o handshake do WebSocket com um token assinado de curta duração derivado da sessão do usuário. Faça rotação a cada poucos minutos. Revalide ao retomar.
- CSRF ainda importa: valide a origem e inclua tokens de CSRF em eventos cliente→servidor que mutam estado.
- Validação de entrada: trate eventos do socket como HTTP POSTs. Mesmos esquemas, mesmos rate limits, mesma trilha de auditoria.
- WAF: permita o caminho de upgrade do WebSocket e defina limites sensatos de tamanho de frame/mensagem (p.ex., 64–256 KB). Registre rejeições.
Observabilidade de que você realmente precisa
- Contagem de sockets conectados e distribuição por nó.
- Taxa de reconexões por minuto e causas (timeout de inatividade vs. rede vs. falha de autenticação).
- Métricas de patch: tamanho médio e p95 de patch, patches/s, RTT do cliente p50/p95.
- Tempo de render de servidor p50/p95 por componente/template.
- Contadores de backpressure: updates descartados/coalescidos.
Alerta para tempestades de reconexão, crescimento do p95 de tamanho de patch ou regressões de tempo de render — isso se correlaciona diretamente com engasgos percebidos.
Escolha de protocolo: WebSockets vs SSE vs “futures”
- Server-Sent Events (SSE): unidirecional, text/event-stream sobre HTTP/2. Ótimo para saída de tokens de IA ou notificações simples. Barato e muito simples, mas sem eventos do cliente. Limites de conexão por origem do navegador podem afetar usuários avançados com muitas abas.
- WebSockets: túnel TCP full-duplex e de longa duração. Melhor para entradas colaborativas, presença, formulários ao vivo e tempo real unificado. Um pouco mais de overhead operacional, mas ainda mainstream em CDNs/LBs.
- WebTransport/QUIC: promissor, mas ainda não ubíquo em redes/WAFs corporativos. Hoje, você gastará mais tempo lutando com middleboxes do que entregando features.
Regra prática: se você precisa de eventos cliente→servidor além de fetch/POST triviais, escolha WebSockets e mantenha seu modelo mental consistente entre features.
Performance: o que os usuários realmente sentem
A maior parte da UX “em tempo real” corporativa não é sobre atingir 16,7 ms por frame; é sobre renderizar a coisa certa em ~200 ms e nunca perder edições do usuário. Na prática:
- Um diff renderizado no servidor abaixo de 1 KB + 50–100 ms de render + 50–100 ms de RTT de rede = 100–250 ms end-to-end. Isso parece instantâneo para tabelas, formulários e contadores.
- O navegador faz menos trabalho: sem VDOM grande no cliente, menos incompatibilidades de hidratação, menos megabytes de JS enviados/em cache/invalidado.
- Você paga esse trabalho no servidor, onde o código é mais simples de testar, fazer profile e proteger. CPUs são mais baratas do que engenheiros.
Vimos dashboards administrativos reduzirem o bundle de JS de 1–3 MB para menos de 200 KB, eliminar o roteamento de SPA por completo e cortar o TTI em >50%. Seu resultado pode variar, mas a direção é consistente.
Custos e trade-offs (sem óculos cor-de-rosa)
O que você vai economizar
- Complexidade de front-end: 70–90% menos JS de aplicação nas superfícies elegíveis. Menos quebras de build, árvore de dependências menor, superfície de ataque menor.
- Velocidade de entrega: em nossos pods nearshore, features com muito CRUD andaram 25–40% mais rápido pós-migração; menos handoffs, um único caminho de código para validação e efeitos colaterais.
- Depuração: uma única stack trace. Logs de servidor correlacionam diretamente com o estado da UI.
O que você vai pagar
- Pegada de conexões persistentes: você precisa fazer capacity planning para WebSockets. Bugs de memória agora custam RAM por conexão.
- Acoplamento ao framework: LiveView/Turbo/Livewire/Blazor codificam semânticas de protocolo. Você fica “opinionated by default”. Migrar depois não é trivial.
- Tolerância a offline/latência: redes móveis de alta latência e modo avião são desfavoráveis. Você precisará de retries cuidadosos e reconciliação de estado — ou aceitar fluxos apenas online.
- Formato do QA muda: você escreverá mais testes de integração que verificam patches de DOM ou fluxos ponta a ponta, em vez de unit tests em uma máquina de estados no cliente.
Se você opera uma suíte de design de alta interação, um data grid com edição local complexa ou canvases a 60 fps, permaneça com SPA/nativo onde o cliente precisa ser a autoridade. Para todo o resto, HTML dirigido pelo servidor é competitivo ou superior em 2026.
Escolhendo a stack (shortlist opinativa)
- Phoenix LiveView (Elixir): se você pode rodar no BEAM, faça. Você ganha WebSockets de primeira classe, processos baratos, Presence e um protocolo de diff maduro. Relatos em produção de 100k+ sockets por VM são normais com estado sensato. Excelente para apps colaborativos.
- Rails + Hotwire (Turbo Streams): o caminho de menor atrito para equipes Rails. Ótima DX, baterias incluídas. Emparelhe com Redis para broadcast. AnyCable melhora performance em escala.
- Laravel Livewire (+ Alpine): equipes PHP andam rápido aqui. Use Soketi ou um serviço gerenciado compatível com Pusher para fan-out. Mantenha o estado de componentes enxuto.
- htmx (+ seu servidor): agnóstico de linguagem, super incremental. Comece trocando um único widget para SSE/WebSockets. Perfeito para migrações cirúrgicas em stacks legadas.
- .NET Blazor Server: amigável ao enterprise e bem suportado. Fique atento à memória por conexão; faça profile agressivamente. Você ganha SignalR e ferramentas fortes.
Não persiga frameworks exóticos para economizar 20 ms. Escolha aquele que sua equipe consegue depurar às 2 da manhã.
Playbook de migração: construa amplo, entregue de forma focada
- Identifique uma superfície de alto churn: um dashboard interno, um console de operações ou uma página de cliente com muita leitura e atualizações frequentes.
- Instrumente hoje: meça p50/p95 de TTI, tamanho do bundle de JS, taxa de erros e tempo para entregar pequenas mudanças de UI (p.ex., adicionar uma coluna, adicionar um filtro).
- Reconstrua apenas essa superfície com HTML-over-the-wire (htmx ou a UI dirigida pelo servidor do seu framework). Mantenha o resto da sua SPA intacto.
- Rode em dual por 2–4 semanas sob um feature flag. Compare métricas A/B: velocidade de entrega, taxas de erro, custo de infra e latência percebida pelo usuário (monitoramento real do usuário).
- Escale o padrão para superfícies semelhantes. Evite tentar “replataformar” tudo. Dashboards e formulários primeiro; mantenha editores complexos no mundo SPA.
Checklist operacional (copiar/colar)
- Load balancer: aumente timeouts para 300–1200 s em endpoints /ws. Confirme suporte a WebSocket no caminho CDN/WAF.
- Autoscaling: escale por sockets conectados, CPU e profundidade de filas — não apenas por RPS HTTP.
- Camada de canais: Redis/NATS para fan-out. Monitore lag de pub/sub.
- Segurança: tokens de auth de curta duração, checks de origem, CSRF em eventos mutantes, limites de tamanho de mensagem.
- Backpressure: filas por conexão com coalescência e políticas de descarte. Nunca bloqueie o event loop por um cliente lento.
- Métricas: contagem de sockets, reconexões/min, tamanho de patch p95, render p95, RTT p95, drops por coalescência.
- Chaos: mate um nó durante uma demo. Seu comportamento de reconexão é sua UX.
E os streams de tokens de IA?
A maioria das equipes transmite a saída do modelo para o navegador como tokens de texto. Se essa é sua única necessidade de tempo real, SSE continua sendo o caminho mais simples. Mas muitos produtos também querem entrada ao vivo (tool calls, streaming de funções, sugestões de código com contexto de teclas) e presença compartilhada. Um WebSocket por sessão permite multiplexar as duas direções sob uma única política de backpressure e uma única história de autenticação. Você ainda pode tunelar streams de tokens como eventos delimitados por linha dentro de um frame do socket e renderizá-los em snippets de HTML no servidor.
Bônus: você mantém prompts do modelo, redação e formatação no servidor. Sem tokens de prompt ou contexto sensível no cliente. Isso importa para compradores enterprise.
Números para o CFO
- Infra: espere um aumento modesto em CPU/RAM de servidor para render + conexões. Para um app típico de dashboard B2B com 5–20k usuários concorrentes, os custos incrementais de infra costumam ficar na casa dos poucos milhares por mês — bem menos do que um headcount sênior de front-end.
- Velocidade: equipes relatam entregas 25–40% mais rápidas em CRUD/features onde a lógica de negócio migra para o servidor. Nossos pods mistos US–Brazil viram ciclos de duas semanas cair para 1,2–1,5 semana para escopo similar, assim que o scaffolding de SPA sai do caminho.
- Confiabilidade: menos dependências no cliente significa menos patches de emergência por quebras de pacote. Seu risco migra para capacidade de servidor, que você já sabe gerenciar.
Estes são números de planejamento, não garantias. Faça um piloto de duas sprints e meça suas próprias bases.
Conclusão
HTML sobre WebSockets não é nostalgia. Em 2026, é uma forma pronta para produção de entregar apps em tempo real com menos peças móveis. Se seu produto é feito de formulários, tabelas e dashboards com interatividade moderada, você não precisa de uma mega-SPA. Mantenha o cérebro no servidor. Faça streaming da UI. Meça os resultados. Expanda onde vencer.
Pontos-chave
- Use HTML-over-the-wire quando seu app for pesado em formulários/tabelas, sensível a SEO e apenas moderadamente interativo.
- Escolha WebSockets em vez de SSE quando precisar de eventos bidirecionais; use SSE para streams somente de saída.
- Planeje capacidade: 20k–100k sockets por nó são alcançáveis; preveja alguns KB por conexão mais estado do framework.
- Adote incrementalmente: comece com um dashboard ou página de admin; A/B de métricas antes de expandir.
- Meça os sinais certos: sockets, reconexões, tamanho de patch, render p95, RTT p95, quedas por backpressure.
- Espere menos bugs de front-end e entregas 25–40% mais rápidas em features elegíveis; dê mais atenção à capacidade do servidor.
Referências: htmx and WebSockets, Phoenix LiveView, Turbo Streams, Blazor Server