Onde devemos implementar a lógica e a renderização nos nossos aplicativos? Devemos usar a renderização do lado do servidor? E a reidratação? Vamos encontrar algumas respostas!
Como desenvolvedores, muitas vezes nos deparamos com decisões que afetam toda a arquitetura dos nossos aplicativos. Uma das principais decisões que os desenvolvedores Web precisam tomar é onde implementar a lógica e a renderização nos aplicativos. Isso pode ser difícil, já que há várias maneiras diferentes de criar um site.
Nosso entendimento dessa área depende do nosso trabalho no Chrome com relação a sites grandes nos últimos anos. Em termos gerais, recomendamos que os desenvolvedores considerem a renderização no lado do servidor ou a renderização estática em vez de uma abordagem de reidratação completa.
Para entender melhor as arquiteturas que escolhemos quando tomamos essa decisão, precisamos ter uma compreensão sólida de cada abordagem e terminologia consistente a ser usada ao falar sobre elas. As diferenças entre essas abordagens ajudam a ilustrar as vantagens e desvantagens da renderização na Web pela perspectiva do desempenho.
Terminologia
Renderização
- Renderização do lado do servidor (SSR, na sigla em inglês): renderização de um app universal ou do lado do cliente para HTML no servidor.
- Renderização do lado do cliente (CSR, na sigla em inglês): renderiza um app em um navegador usando JavaScript para modificar o DOM.
- Reidratação: "inicializar" visualizações JavaScript no cliente para que elas reutilizem a árvore e os dados DOM do HTML renderizado pelo servidor.
- Pré-renderização:executar um aplicativo do lado do cliente durante a criação para capturar o estado inicial dele como HTML estático.
Desempenho
- Tempo até o primeiro byte (TTFB, na sigla em inglês):é o tempo entre o clique em um link e a primeira parte do conteúdo que chega.
- Primeira exibição de conteúdo (FCP, na sigla em inglês):o momento em que o conteúdo solicitado (corpo do artigo etc.) fica visível.
- Interação com a próxima exibição (INP, na sigla em inglês):vista como uma métrica representativa que avalia se uma página está respondendo de forma consistente às entradas do usuário.
- Tempo total de bloqueio (TBT, na sigla em inglês):uma métrica de proxy para INP, que calcula o tempo total de bloqueio da linha de execução principal durante o carregamento da página.
Renderização do lado do servidor
A renderização no servidor gera o HTML completo para uma página no servidor em resposta à navegação. Isso evita idas e voltas adicionais para a busca de dados e a criação de modelos no cliente, já que isso é feito antes de o navegador receber uma resposta.
A renderização do lado do servidor geralmente produz uma FCP rápida. A execução da lógica e da renderização da página no servidor evita o envio de muitos JavaScripts ao cliente. Isso ajuda a reduzir o TBT de uma página, o que também pode levar a um INP mais baixo, já que a linha de execução principal não é bloqueada com tanta frequência durante o carregamento da página. Quando a linha de execução principal é bloqueada com menos frequência, as interações do usuário têm mais oportunidades de serem executadas mais rapidamente. Isso faz sentido, já que, com a renderização do lado do servidor, você está apenas enviando texto e links para o navegador do usuário. Essa abordagem funciona bem em um grande espectro de condições de rede e dispositivo e abre otimizações interessantes de navegadores, como a análise de documentos de streaming.

Com a renderização do lado do servidor, é menos provável que os usuários esperem que o JavaScript vinculado à CPU seja executado antes de usar seu site. Mesmo quando o JS de terceiros não pode ser evitado, o uso da renderização do lado do servidor para reduzir seus próprios custos de JavaScript primários pode aumentar o orçamento para o restante. No entanto, há uma desvantagem em potencial com essa abordagem: gerar páginas no servidor leva tempo, o que pode resultar em um TTFB maior.
O fato de a renderização do lado do servidor ser suficiente para seu aplicativo depende em grande parte do tipo de experiência que você está criando. Há um debate antigo sobre os aplicativos corretos de renderização do lado do servidor em comparação com a renderização do lado do cliente, mas é importante lembrar que você pode optar por usar a renderização do lado do servidor em algumas páginas e não em outras. Alguns sites adotaram técnicas de renderização híbrida e tiveram sucesso. A Netflix renderiza as páginas de destino relativamente estáticas, enquanto faz a pré-busca do JS para páginas com interação intensa. Isso proporciona a essas páginas mais pesadas renderizadas pelo cliente uma chance maior de carregar rapidamente.
Muitos frameworks, bibliotecas e arquiteturas modernas permitem renderizar o mesmo aplicativo no cliente e no servidor. Essas técnicas podem ser usadas para renderização do lado do servidor. No entanto, é importante observar que as arquiteturas em que a renderização ocorre tanto no servidor quanto no cliente são a própria classe de solução com características de desempenho e compensações muito diferentes. Os usuários do React podem usar APIs DOM do servidor ou soluções criadas com eles, como Next.js, para renderização do lado do servidor. Os usuários do Vue podem consultar o guia de renderização do lado do servidor ou o Nuxt do Vue. O Angular tem Universal. No entanto, as soluções mais populares empregam alguma forma de hidratação. Portanto, esteja ciente da abordagem em uso antes de selecionar uma ferramenta.
Renderização estática
A renderização estática ocorre durante a compilação. Essa abordagem oferece uma FCP rápida e também um TBT e um INP mais baixos, supondo que a quantidade de JS do lado do cliente seja limitada. Ao contrário da renderização pelo servidor, ela também consegue alcançar um TTFB rápido e consistente, já que o HTML de uma página não precisa ser gerado dinamicamente no servidor. Geralmente, renderização estática significa produzir um arquivo HTML separado para cada URL antecipadamente. Com respostas HTML geradas com antecedência, as renderizações estáticas podem ser implantadas em várias CDNs para aproveitar o armazenamento em cache de borda.

Existem soluções para renderização estática de todos os formatos e tamanhos. Ferramentas como o Gatsby foram criadas para que os desenvolvedores sintam que os aplicativos estão sendo renderizados dinamicamente, e não como uma etapa de criação. As ferramentas de geração de sites estáticos, como 11ty, Jekyll e Metalsmith, adotam sua natureza estática, fornecendo uma abordagem mais orientada a modelos.
Uma das desvantagens da renderização estática é que os arquivos HTML individuais precisam ser gerados para todos os URLs possíveis. Isso pode ser desafiador ou até mesmo inviável quando não é possível prever quais serão os URLs com antecedência ou para sites com um grande número de páginas únicas.
Os usuários do React talvez já conheçam o Gatsby, a exportação estática do Next.js ou o Navi. Tudo isso facilita a criação de páginas usando componentes. No entanto, é importante entender a diferença entre a renderização estática e a pré-renderização: as páginas renderizadas estáticas são interativas sem precisar executar muito JavaScript no lado do cliente, enquanto a pré-renderização melhora o FCP de um aplicativo de página única, que precisa ser inicializado no cliente para que as páginas sejam realmente interativas.
Se você não tiver certeza se uma determinada solução é renderização estática ou pré-renderização, desative o JavaScript e carregue a página que você quer testar. Em páginas renderizadas estaticamente, a maioria das funcionalidades continuará existindo sem o JavaScript ativado. No caso de páginas pré-renderizadas, ainda pode haver algumas funcionalidades básicas, como links, mas a maior parte da página ficará inerte.
Outro teste útil é usar a limitação de rede no Chrome DevTools e observar quanto JavaScript foi transferido por download antes que a página se torne interativa. A pré-renderização geralmente exige que mais JavaScript se torne interativo. Além disso, o JavaScript tende a ser mais complexo do que a abordagem de aprimoramento progressivo usada pela renderização estática.
Comparação entre a renderização do lado do servidor e a renderização estática
A renderização do lado do servidor não é uma solução perfeita, já que a natureza dinâmica dela pode ter custos significativos de sobrecarga de computação. Muitas soluções de renderização no lado do servidor não são transferidas antecipadamente, podem atrasar o TTFB ou dobrar os dados que estão sendo enviados (por exemplo, estado in-line usado pelo JavaScript no cliente). No React, a renderToString()
pode ser lenta, porque é síncrona e com uma única linha de execução. As APIs DOM do servidor React mais recentes oferecem suporte a streaming, que podem receber a parte inicial de uma resposta HTML ao navegador mais cedo, enquanto o restante ainda está sendo gerado no servidor.
Conseguir a renderização correta do lado do servidor pode envolver encontrar ou criar uma solução para armazenamento em cache de componentes, gerenciamento do consumo de memória, aplicação de técnicas de memoização e outras questões. Geralmente, você está processando/recriando o mesmo aplicativo várias vezes: uma no cliente e outra no servidor. Só porque a renderização do lado do servidor pode fazer algo aparecer mais cedo, não significa que você terá menos trabalho a fazer. Se você tiver muito trabalho no cliente depois que uma resposta HTML gerada pelo servidor chegar ao cliente, isso ainda poderá gerar mais TBT e INP para seu site.
A renderização do lado do servidor produz HTML sob demanda para cada URL, mas pode ser mais lenta do que apenas a veiculação de conteúdo renderizado estático. Se for possível colocar o trabalho adicional, a renderização no servidor e o armazenamento em cache HTML poderão reduzir significativamente o tempo de renderização do servidor. A vantagem para a renderização pelo servidor é a capacidade de extrair mais dados "ativos" e responder a um conjunto mais completo de solicitações do que é possível com a renderização estática. As páginas que exigem personalização são um exemplo concreto do tipo de solicitação que não funcionaria bem com renderização estática.
A renderização do lado do servidor também pode apresentar decisões interessantes ao criar um PWA: é melhor usar o armazenamento em cache de service worker de página inteira ou apenas renderizar partes individuais do conteúdo pelo servidor?
Renderização do lado do cliente
Renderização do lado do cliente significa renderizar páginas diretamente no navegador com JavaScript. Toda a lógica, busca de dados, modelos e roteamento são tratados no cliente e não no servidor. O resultado é que mais dados são transmitidos do servidor para o dispositivo do usuário, com vantagens e desvantagens próprias.
Pode ser difícil adquirir e manter a renderização rápida no lado do cliente em dispositivos móveis. Se um trabalho mínimo for feito, a renderização do lado do cliente poderá se aproximar do desempenho da renderização pura do lado do servidor, mantendo um orçamento limitado de JavaScript e entregando valor no menor número possível de viagens de ida e volta. Scripts e dados críticos podem ser entregues mais rapidamente usando <link rel=preload>
, que faz com que o analisador funcione para você mais rapidamente. Também vale a pena avaliar padrões como o PRPL para garantir que as navegações iniciais e subsequentes sejam instantâneas.

A principal desvantagem da renderização do cliente é que a quantidade de JavaScript necessária tende a aumentar à medida que um aplicativo cresce, o que pode ter efeitos negativos no INP de uma página. Isso se torna especialmente difícil com a adição de novas bibliotecas JavaScript, polyfills e códigos de terceiros, que competem pelo poder de processamento e geralmente precisam ser processados antes que o conteúdo da página possa ser renderizado.
Experiências que usam a renderização no lado do cliente que dependem de grandes pacotes JavaScript precisam considerar a divisão de código agressiva para diminuir o TBT e o INP durante o carregamento da página. Além disso, não se esqueça de fazer o carregamento lento do JavaScript, ou seja, veicular apenas o que você precisa, quando necessário. Para experiências com pouca ou nenhuma interatividade, a renderização pelo servidor pode representar uma solução mais escalonável para esses problemas.
Para aqueles que criam aplicativos de página única, identificar as partes principais da interface do usuário compartilhadas pela maioria das páginas significa que você pode aplicar a técnica de armazenamento em cache do shell do aplicativo. Combinado com os service workers, isso pode melhorar drasticamente o desempenho percebido em visitas repetidas, já que o HTML do shell do aplicativo e as dependências dele podem ser carregados do CacheStorage
muito rapidamente.
Combinar a renderização do lado do servidor e do cliente usando a reidratação
Essa abordagem tenta suavizar as compensações entre a renderização do lado do cliente e a do servidor ao fazer as duas coisas. As solicitações de navegação, como carregamentos de página inteira ou recarregamentos, são processadas por um servidor que renderiza o aplicativo em HTML. Em seguida, o JavaScript e os dados usados para a renderização são incorporados ao documento resultante. Quando feito com cuidado, esse processo atinge uma FCP rápida, assim como a renderização no lado do servidor, e é renderizado novamente no cliente usando uma técnica chamada (re)hidratação. Essa é uma solução eficaz, mas também traz desvantagens consideráveis de desempenho.
A principal desvantagem da renderização do lado do servidor com reidratação é que ela pode ter um impacto negativo significativo sobre o TBT e a INP, mesmo que melhore a FCP. As páginas renderizadas do lado do servidor podem parecer enganosamente carregadas e interativas, mas não podem responder à entrada até que os scripts dos componentes do lado do cliente sejam executados e os manipuladores de eventos sejam anexados. Isso pode levar alguns segundos ou até mesmo minutos em dispositivos móveis.
Talvez você já tenha passado por isso. Por um tempo depois que uma página for carregada, um clique ou um toque não terá efeito. Isso rapidamente se torna frustrante, já que o usuário fica se perguntando por que nada está acontecendo quando tenta interagir com a página.
Um problema de reidratação: um app pelo preço de dois
Os problemas de reidratação podem ser piores do que a interatividade tardia devido ao JavaScript. Para que o JavaScript do lado do cliente possa "continuar" com precisão de onde o servidor parou sem precisar solicitar novamente todos os dados que o servidor usou para renderizar seu HTML, as soluções de renderização do lado do servidor atuais geralmente serializam a resposta das dependências de dados de uma interface do usuário no documento como tags de script. O documento HTML resultante contém um alto nível de duplicação:

Como você pode notar, o servidor retorna uma descrição da interface do aplicativo em resposta a uma solicitação de navegação, mas também retorna os dados de origem usados para compor essa interface e uma cópia completa da implementação da interface, que é inicializada no cliente. Essa interface só se torna interativa depois que bundle.js
termina de carregar e executar.
Métricas de desempenho coletadas de sites reais usando renderização do lado do servidor e reidratação indicam que seu uso deve ser desencorajado. Em última análise, o motivo se resume à experiência do usuário: é extremamente fácil acabar deixando os usuários em um "val estranho", onde a interatividade parece ausente, mesmo que a página pareça estar pronta.

No entanto, há esperança para a renderização do lado do servidor com reidratação. A curto prazo, usar apenas a renderização do lado do servidor para conteúdo altamente armazenável em cache pode reduzir o TTFB, produzindo resultados semelhantes à pré-renderização. A reidratação de forma incremental, progressiva ou parcial pode ser a chave para tornar essa técnica mais viável no futuro.
Renderização no lado do servidor e reidratação progressiva
A renderização do lado do servidor teve uma série de desenvolvimentos nos últimos anos.
A renderização de streaming do lado do servidor permite enviar HTML em partes que o navegador pode renderizar progressivamente à medida que é recebido. Isso pode resultar em uma FCP rápida, já que a marcação chega aos usuários mais rapidamente. No React, quando os streams são assíncronos no [renderToPipeableStream()
], em comparação com o renderToString()
síncrono, a pressão de retorno é bem tratada.
Também vale a pena considerar a reidratação progressiva, e algo que o React acertou. Com essa abordagem, partes individuais de um aplicativo renderizado pelo servidor são "inicializadas" ao longo do tempo, em vez da abordagem comum atual de inicializar todo o aplicativo de uma só vez. Isso pode ajudar a reduzir a quantidade de JavaScript necessária para tornar as páginas interativas, já que o upgrade do lado do cliente de partes de baixa prioridade da página pode ser adiado para evitar o bloqueio da thread principal, permitindo que as interações do usuário ocorram mais cedo após serem iniciadas pelo usuário.
A reidratação progressiva também pode ajudar a evitar uma das armadilhas mais comuns de reidratação de renderização do lado do servidor, em que uma árvore DOM renderizada pelo servidor é destruída e recriada imediatamente. Na maioria das vezes, porque a renderização síncrona inicial do lado do cliente exigia dados que ainda não estavam prontos, talvez aguardando a resolução de um Promise
.
Reidratação parcial
A reidratação parcial tem sido difícil de implementar. Essa abordagem é uma extensão da ideia de reidratação progressiva, em que as peças individuais (componentes/vistas/árvores) a serem reidratadas progressivamente são analisadas, e aquelas com pouca interatividade ou nenhuma reatividade são identificadas. Para cada uma dessas partes quase estáticas, o código JavaScript correspondente é transformado em referências inertes e funcionalidade decorativa, reduzindo o consumo no lado do cliente para quase zero.
A abordagem de hidratação parcial tem os próprios problemas e concessões. Isso apresenta alguns desafios interessantes para o armazenamento em cache, e a navegação do lado do cliente significa que não podemos presumir que o HTML renderizado pelo servidor para partes inertes do aplicativo esteja disponível sem um carregamento completo da página.
Renderização trisomórfica
Se os service workers forem uma opção para você, a renderização "trisomórfica" também poderá ser interessante. É uma técnica em que é possível usar a renderização de streaming do lado do servidor para navegações iniciais/não JS e, em seguida, fazer com que o service worker assuma a renderização de HTML para navegações depois de instalado. Isso mantém atualizados os componentes e modelos armazenados em cache e permite que navegações no estilo SPA renderizem novas visualizações na mesma sessão. Essa abordagem funciona melhor quando você compartilha o mesmo código de modelo e roteamento entre o servidor, a página do cliente e o service worker.

Considerações sobre SEO
As equipes geralmente consideram o impacto da SEO ao escolher uma estratégia de renderização na Web. A renderização do lado do servidor geralmente é escolhida para proporcionar uma experiência de "aparência completa" que os rastreadores podem interpretar com facilidade. Os rastreadores podem entender o JavaScript, mas geralmente há limitações que vale a pena conhecer na renderização. A renderização do lado do cliente pode funcionar, mas geralmente não sem testes e trabalhos extras. Mais recentemente, a renderização dinâmica também se tornou uma opção a considerar se sua arquitetura depende muito do JavaScript do lado do cliente.
Em caso de dúvida, a ferramenta de teste de compatibilidade com dispositivos móveis é inestimável para verificar se a abordagem escolhida faz o que você esperava. Ele mostra uma prévia de como qualquer página é exibida para o rastreador do Google, o conteúdo HTML serializado encontrado (após a execução do JavaScript) e todos os erros encontrados durante a renderização.

Conclusão
Ao decidir uma abordagem de renderização, avalie e entenda quais são os gargalos. Considere se a renderização estática ou a renderização do lado do servidor pode ser mais adequada. Não há problema em enviar HTML com o mínimo de JavaScript para proporcionar uma experiência interativa. Veja um infográfico útil que mostra o espectro servidor-cliente:

Créditos
Agradecemos a todos pelas avaliações e inspiração:
Jeffrey Posnick, Houssein Djirdeh, Shubhie Panicker, Chris Harrelson e Sebastian Markbåge