Renderização no servidor x no cliente: SSR, CSR e hidratação
SSR, CSR e hidratação sem jargão: onde o HTML é montado, o que o usuário vê primeiro e por que a escolha muda velocidade, SEO e interatividade.
Neste artigo
Existe uma decisão de arquitetura que molda o comportamento inteiro de um site moderno, e muita gente a toma sem perceber que a está tomando: onde o HTML é montado. Ele pode ser gerado no servidor, chegando pronto ao navegador, ou pode ser gerado no próprio navegador, a partir de um arquivo praticamente vazio que o JavaScript preenche. Essa escolha aparentemente técnica determina o que o usuário vê nos primeiros instantes, como os mecanismos de busca enxergam o conteúdo, quão rápido a página fica interativa e quanto trabalho o dispositivo do usuário precisa fazer. Entender as duas abordagens e o ponto intermediário que as costura — a hidratação — é fundamental para quem constrói para a web.
A confusão em torno do tema vem de tratá-lo como uma guerra entre times, em que uma abordagem é "melhor" que a outra. Não é assim. Renderizar no servidor e renderizar no cliente resolvem problemas diferentes e têm custos diferentes, e a maioria das aplicações reais acaba usando uma combinação das duas. O que importa não é escolher um lado, e sim entender a mecânica de cada uma com precisão suficiente para saber qual usar em qual parte da aplicação.
Neste artigo vamos separar as duas na origem — o que exatamente acontece na rede e no navegador em cada caso — e depois entender a hidratação, o processo que tenta pegar o melhor dos dois mundos e que carrega uma armadilha específica de performance que vale conhecer de perto.
Renderização no cliente: o HTML vazio que se preenche#
Na renderização no cliente, o servidor entrega ao navegador um documento HTML mínimo — pouco mais que um contêiner vazio e uma referência a um pacote de JavaScript. Não há conteúdo real nesse HTML inicial; se você olhar o que veio da rede, verá uma casca. O trabalho de montar a interface acontece depois, no navegador: o JavaScript baixa, executa, eventualmente busca dados de uma API e, só então, constrói os elementos da página e os insere no documento.
O fluxo, portanto, é: o navegador recebe a casca, baixa o JavaScript, executa, talvez faça requisições de dados, monta a interface. Cada uma dessas etapas leva tempo, e durante todas elas o usuário vê uma tela vazia ou um indicador de carregamento. A página só se torna útil no fim dessa cadeia. A vantagem é que, uma vez montada, a aplicação é altamente interativa e as navegações seguintes podem ser instantâneas, porque tudo já está no navegador e trocar de "página" vira apenas trocar o que está na tela, sem nova ida ao servidor.
Os custos da renderização no cliente#
Essa abordagem carrega dois custos importantes. O primeiro é o tempo até o conteúdo aparecer: como nada é visível antes de o JavaScript rodar, o usuário espera mais para ver a primeira informação útil, especialmente em conexões lentas ou dispositivos modestos, onde baixar e executar um pacote grande de JavaScript demora. O segundo é a visibilidade para robôs. Um mecanismo de busca ou um serviço que gera pré-visualização de links pode receber apenas a casca vazia se não executar o JavaScript, ou executá-lo de forma limitada, o que compromete a indexação do conteúdo. Para conteúdo que precisa ser encontrado e compartilhado, isso é um problema sério.
Renderização no servidor: o HTML já vem pronto#
Na renderização no servidor, o servidor faz o trabalho de montar o HTML completo antes de responder. Ele busca os dados necessários, constrói a marcação com o conteúdo já dentro dela e envia ao navegador um documento pronto para exibir. O navegador recebe HTML de verdade, com o texto, as imagens referenciadas e a estrutura, e pode pintá-lo imediatamente, sem esperar JavaScript nenhum para mostrar o conteúdo.
A consequência mais visível é que o usuário vê o conteúdo muito mais cedo. O primeiro pixel útil aparece assim que o HTML chega e o navegador o processa, sem depender do download e da execução de um pacote de scripts. E os robôs recebem exatamente o mesmo HTML rico, com todo o conteúdo presente, o que torna a indexação e a geração de pré-visualizações confiáveis. Para páginas de conteúdo — artigos, produtos, qualquer coisa que precise ser encontrada — essa é uma vantagem decisiva.
O que a renderização no servidor não resolve sozinha#
Mas há uma limitação. O HTML que o servidor manda é estático no sentido de que não tem comportamento: os botões não reagem, os formulários não validam em tempo real, nada é interativo, porque interatividade exige JavaScript rodando no navegador. Se a aplicação precisa de interações ricas — e a maioria precisa —, o HTML renderizado no servidor é só o ponto de partida. É preciso, de alguma forma, ligar o comportamento a essa marcação que já veio pronta. E é exatamente essa costura que a hidratação faz.
Hidratação: costurando os dois mundos#
A hidratação é o processo de pegar o HTML que o servidor já renderizou e "dar vida" a ele no navegador, anexando os manipuladores de eventos e o estado que o tornam interativo. A ideia é elegante: o servidor manda o HTML pronto para o usuário ver rápido, e depois o navegador baixa o mesmo código que gerou aquele HTML e o executa, não para recriar a marcação do zero, mas para reconhecer a marcação existente e conectar a interatividade a ela.
Na prática, o navegador recebe o HTML completo e o exibe imediatamente — o usuário já vê tudo. Enquanto isso, em segundo plano, o JavaScript da aplicação baixa e executa. Quando termina, ele percorre a marcação que já está na tela, associa a cada elemento os comportamentos correspondentes e assume o controle da página. A partir desse momento, a página está totalmente interativa, e navegações seguintes podem funcionar no modo de cliente, sem novas requisições completas ao servidor. É a tentativa de ter o conteúdo rápido do servidor e a interatividade rica do cliente.
A armadilha do vale sem interatividade#
A hidratação carrega um problema que é fonte de muita frustração de usuário e que todo desenvolvedor deveria conhecer. Existe uma janela de tempo em que o conteúdo já está visível na tela — porque o servidor o renderizou —, mas o JavaScript ainda não terminou de hidratar. Durante essa janela, a página parece pronta e interativa, mas não é: cliques em botões não fazem nada, campos não respondem, porque os manipuladores de eventos ainda não foram anexados. O usuário vê algo que parece funcionar, tenta interagir e nada acontece. Essa dissonância entre parecer pronto e estar pronto é especialmente irritante porque quebra a expectativa que a própria velocidade do conteúdo criou.
O tamanho dessa janela é proporcional à quantidade de JavaScript que precisa ser baixado e executado para hidratar. Quanto mais pesada a aplicação, maior o vale. É por isso que reduzir o JavaScript enviado, e hidratar apenas as partes que realmente precisam de interatividade em vez de a página inteira, virou uma preocupação central. Técnicas que enviam pouco ou nenhum JavaScript para partes estáticas da página, e que priorizam a hidratação das partes com que o usuário provavelmente vai interagir primeiro, atacam diretamente esse problema.
Geração estática e o meio-termo dos dados#
Há ainda uma variação da renderização no servidor que vale distinguir. Em vez de montar o HTML a cada requisição, é possível montá-lo uma vez, no momento em que o site é construído, e servir o mesmo HTML pronto para todos. Isso funciona lindamente para conteúdo que não muda a cada visita — um artigo, uma página institucional — porque combina a velocidade máxima de servir um arquivo pronto com todos os benefícios de visibilidade do HTML completo. A diferença em relação a renderizar a cada requisição está em quando o trabalho é feito: antecipadamente, uma vez, ou no instante do pedido, para cada usuário.
A escolha entre gerar antecipadamente e gerar por requisição depende de quão dinâmico e personalizado é o conteúdo. Conteúdo igual para todos e que muda pouco pede geração antecipada. Conteúdo personalizado por usuário ou que reflete dados que mudam a cada instante pede geração por requisição. E muitas aplicações misturam os dois, gerando estaticamente o que dá e renderizando por requisição o que precisa ser fresco ou personalizado.
Escolhendo com intenção#
Com a mecânica clara, a decisão deixa de ser ideológica. Se o conteúdo precisa aparecer rápido e ser encontrado por buscadores — o caso de praticamente todo conteúdo público —, renderizar no servidor ou gerar estaticamente é o ponto de partida certo, porque entrega HTML completo e visível de imediato. Se a aplicação é uma ferramenta rica atrás de login, onde SEO não importa e a interatividade domina, renderizar no cliente pode ser perfeitamente adequado, evitando a complexidade da hidratação. E a maioria dos casos reais fica no meio: HTML do servidor para o conteúdo, hidratação para a interatividade, com cuidado para manter o pacote de JavaScript enxuto e o vale de hidratação curto.
O erro a evitar é escolher por moda em vez de por necessidade. Renderizar tudo no cliente uma página de conteúdo por hábito prejudica velocidade e descoberta sem ganho real. Carregar a complexidade da hidratação em uma ferramenta interna simples é custo sem benefício. A pergunta certa não é qual abordagem é melhor, e sim onde o HTML precisa ser montado para atender ao que aquela parte específica da aplicação exige — e a resposta costuma variar de página para página dentro do mesmo produto. Quem entende a mecânica das três — cliente, servidor e a hidratação que as une — consegue fazer essa escolha com precisão, extraindo velocidade onde o conteúdo importa e interatividade onde o usuário age, sem pagar o custo de nenhuma das duas onde ela não é necessária.