Renderização na Web

Uma das principais decisões que os desenvolvedores da Web precisam tomar é onde implementar a lógica e a renderização no aplicativo. Isso pode ser difícil porque há maneiras muito maduras de criar um site.

Nosso entendimento desse espaço é baseado no nosso trabalho no Chrome ao interagir com grandes sites nos últimos anos. De um modo geral, recomendamos que os desenvolvedores considerem a renderização pelo servidor ou a estática em vez da abordagem completa de reidratação.

Para entender melhor as arquiteturas escolhidas ao tomar essa decisão, precisamos de uma compreensão sólida de cada abordagem e da terminologia consistente a ser usada ao falar sobre elas. As diferenças entre as abordagens de renderização ajudam a ilustrar as vantagens e desvantagens da renderização na Web da perspectiva do desempenho da página.

Terminologia

Renderização

Renderização do lado do servidor (SSR, na sigla em inglês)
Renderizar um app universal ou do lado do cliente para HTML no servidor
Renderização do lado do cliente (CSR)
Renderizar um app em um navegador usando JavaScript para modificar o DOM.
Reidratação
"Como inicializar" visualizações JavaScript no cliente para reutilizar a árvore e os dados DOM do HTML renderizados pelo servidor.
Pré-renderização
Executar um aplicativo do lado do cliente no tempo de build para capturar o estado inicial como HTML estático.

Desempenho

Tempo para primeiro byte (TTFB)
O tempo entre o clique em um link e o primeiro byte do conteúdo sendo carregado na nova página.
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)
Uma métrica representativa que avalia se uma página responde rapidamente às entradas do usuário de maneira consistente.
Tempo total de bloqueio (TBT)
Uma métrica de proxy para INP que calcula por quanto tempo a linha de execução principal ficou bloqueada durante o carregamento da página.

Renderização do lado do servidor

A renderização do lado do servidor gera o HTML completo de uma página no servidor em resposta à navegação. Isso evita mais idas e voltas para a busca de dados e a modelagem no cliente, porque o renderizador processa essas ações antes que o navegador receba uma resposta.

A renderização do lado do servidor geralmente produz uma FCP rápida. A execução da lógica da página e da renderização no servidor permite evitar o envio de muito JavaScript ao cliente. Isso ajuda a reduzir o TBT de uma página, o que também pode levar a um INP menor, 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 antes. Isso faz sentido porque, com a renderização do lado do servidor, você está apenas enviando texto e links para o navegador do usuário. Essa abordagem pode funcionar bem para várias condições de dispositivo e rede e abre otimizações interessantes do navegador, como a análise de documentos por streaming.

Diagrama
    mostrando a renderização do lado do servidor e a execução de JS afetando FCP e TTI.
FCP e TTI com renderização do lado do servidor.

Com a renderização do lado do servidor, é menos provável que os usuários precisem esperar pela execução do JavaScript vinculado à CPU antes de poderem usar seu site. Mesmo quando não é possível evitar o JS de terceiros, usar a renderização do lado do servidor para reduzir os custos do JavaScript próprio 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 aumentar o TTFB da sua página.

A renderização no servidor é suficiente para o aplicativo depende em grande parte do tipo de experiência que você está criando. Há um debate antigo sobre as aplicações corretas da renderização do lado do servidor e da renderização no lado do cliente, mas você sempre pode optar por usar a renderização pelo servidor em algumas páginas e não em outras. Alguns sites adotaram técnicas de renderização híbrida com sucesso. Por exemplo, a Netflix renderiza as páginas de destino relativamente estáticas, enquanto faz a pré-busca do JS para páginas com muita interação. Isso aumenta as chances de carregamento rápido dessas páginas mais pesadas renderizadas pelo cliente.

Muitos frameworks, bibliotecas e arquiteturas modernas permitem renderizar o mesmo aplicativo no cliente e no servidor. Você pode usar essas técnicas para renderização do lado do servidor. No entanto, as arquiteturas em que a renderização ocorre no servidor e no cliente são uma classe própria 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 nelas, como Next.js, para renderização do lado do servidor. Os usuários do Vue podem usar o guia de renderização do lado do servidor ou o Nuxt do Vue. O Angular tem Universal. No entanto, as soluções mais conhecidas usam alguma forma de hidratação. Portanto, esteja ciente das abordagens usadas pela ferramenta.

Renderização estática

A renderização estática ocorre durante o build. Essa abordagem oferece uma FCP rápida e também um TBT e INP menores, desde que você limite a quantidade de JS do lado do cliente nas suas páginas. Ao contrário da renderização do lado do servidor, ela também alcança um TTFB rápido de modo consistente, porque o HTML de uma página não precisa ser gerado dinamicamente no servidor. Em geral, a renderização estática significa produzir um arquivo HTML separado para cada URL antecipado. Com respostas HTML geradas com antecedência, é possível implantar renderizações estáticas em várias CDNs para aproveitar o armazenamento em cache de borda.

Diagrama
    mostrando renderização estática e execução opcional de JS afetando FCP e TTI.
FCP e TTI com renderização estática.

Soluções para renderização estática existem em todas as formas e tamanhos. Ferramentas como o Gatsby foram projetadas para fazer com que os desenvolvedores sintam que o aplicativo está sendo renderizado dinamicamente, não gerado como uma etapa de build. As ferramentas de geração de sites estáticos, como 11ty, Jekyll e Metalsmith, usam a natureza estática delas, oferecendo uma abordagem mais orientada a modelos.

Uma das desvantagens da renderização estática é que ela precisa gerar arquivos HTML individuais para cada URL possível. Isso pode ser desafiador ou até mesmo inviável quando você não consegue prever quais serão esses URLs com antecedência ou para sites com um grande número de páginas únicas.

Os usuários do React podem já estar familiarizados com o Gatsby, a exportação estática do Next.js ou o Navi, o que facilita a criação de páginas usando componentes. No entanto, a renderização estática e a pré-renderização se comportam de maneira diferente: as páginas renderizadas estaticamente são interativas sem precisar executar muito JavaScript do lado do cliente. Já a pré-renderização melhora a FCP de um aplicativo de página única que precisa ser inicializado no cliente para tornar as páginas realmente interativas.

Se você não tem certeza se uma determinada solução é de 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 dos recursos interativos ainda existe sem o JavaScript. As páginas pré-renderizadas ainda podem ter alguns recursos básicos, como links com JavaScript desativado, mas a maior parte é inerte.

Outro teste útil é usar a limitação de rede no Chrome DevTools e verificar a quantidade de downloads do JavaScript antes que uma página se torne interativa. A pré-renderização geralmente precisa de mais JavaScript para se tornar interativo, e que o JavaScript tende a ser mais complexo do que a abordagem de aprimoramento progressivo usada na renderização estática.

Comparação entre a renderização do lado do servidor e a renderização estática

A renderização no servidor não é a melhor solução para tudo, porque a natureza dinâmica dela pode ter custos significativos de sobrecarga de computação. Muitas soluções de renderização no servidor não fazem a limpeza antecipada, atrasam TTFB ou dobram os dados que estão sendo enviados (por exemplo, estados inline usados pelo JavaScript no cliente). No React, o renderToString() pode ser lento porque é síncrono e tem uma única linha de execução. As APIs DOM mais recentes do servidor React são compatíveis com streaming, que pode receber a parte inicial de uma resposta HTML ao navegador mais cedo, enquanto o restante ainda está sendo gerado no servidor.

Para conseguir a renderização "correta" do lado do servidor, é possível encontrar ou criar uma solução para o armazenamento em cache de componentes, gerenciar o consumo de memória, usar técnicas de memoização e outras questões. Muitas vezes, você está processando ou recriando o mesmo app duas vezes: uma no cliente e outra no servidor. A renderização do lado do servidor que mostra conteúdo logo no futuro não significa necessariamente 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 você colocar a outra legwork, a renderização pelo servidor e o armazenamento em cache HTML poderão reduzir significativamente o tempo de renderização do servidor. A vantagem em relação à renderização do lado do 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 precisam de personalização são um exemplo concreto do tipo de solicitação que não funciona bem com a 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 do 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, a busca de dados, a modelagem e o roteamento são processados no cliente e não no servidor. O resultado efetivo é que mais dados são transmitidos do servidor para o dispositivo do usuário e isso vem com um conjunto próprio de vantagens.

A renderização no lado do cliente pode ser difícil de criar e manter rápida em dispositivos móveis. Com um pouco de trabalho para manter um orçamento limitado de JavaScript e agregar valor com o mínimo de viagens de ida e volta possível, é possível fazer a renderização do lado do cliente quase replicar o desempenho da renderização pura do lado do servidor. Faça com que o analisador trabalhe para você com mais rapidez, fornecendo scripts e dados essenciais com <link rel=preload>. Também recomendamos usar padrões como PRPL para garantir que as navegações inicial e subsequente sejam instantâneas.

Diagrama
    mostrando a renderização do lado do cliente afetando FCP e TTI.
FCP e TTI com renderização do lado do cliente.

A principal desvantagem da renderização no lado do cliente é que a quantidade de JavaScript necessária tende a aumentar à medida que o aplicativo cresce, o que pode afetar o INP de uma página. Isso se torna especialmente difícil com a adição de novas bibliotecas JavaScript, polyfills e código de terceiros, que competem pelo poder de processamento e geralmente precisam ser processados antes que o conteúdo de uma página possa ser renderizado.

As experiências que usam a renderização do lado do cliente e dependem de grandes pacotes JavaScript precisam considerar a divisão de código agressiva para reduzir o TBT e o INP durante o carregamento da página, bem como o JavaScript de carregamento lento para exibir somente o que o usuário 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 as pessoas que criam aplicativos de página única, a identificação das partes principais da interface do usuário compartilhada pela maioria das páginas permite 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, porque a página pode carregar o HTML do shell do aplicativo e as dependências do CacheStorage muito rapidamente.

A reidratação combina a renderização do lado do servidor e do lado do cliente

A reidratação é uma abordagem que tenta suavizar as compensações entre a renderização do lado do cliente e do lado do servidor fazendo as duas coisas. Solicitações de navegação, como carregamentos de página completa 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 gera uma FCP rápida, como a renderização do lado do servidor, e depois começa a renderização ao renderizar novamente no cliente. Essa é uma solução eficaz, mas pode ter desvantagens consideráveis de desempenho.

A principal desvantagem da renderização do lado do servidor com a reidratação é que ela pode ter um impacto negativo significativo no TBT e no INP, mesmo que melhore a FCP. As páginas renderizadas do lado do servidor podem parecer carregadas e interativas, mas não podem responder à entrada até que os scripts para componentes do lado do cliente sejam executados e os manipuladores de eventos foram anexados. Em dispositivos móveis, isso pode levar minutos, confundindo e frustrando o usuário.

Um problema de reidratação: um app pelo preço de dois

Para que o JavaScript do lado do cliente "continue" de forma precisa de onde o servidor parou, sem solicitar novamente todos os dados que o servidor usou para renderizar o HTML, a maioria das soluções de renderização do lado do servidor serializa a resposta das dependências de dados da interface como tags de script no documento. Como isso duplica uma grande quantidade de HTML, a reidratação pode causar mais problemas do que apenas a interatividade atrasada.

Documento HTML
    com interface serializada, dados inline e um script bundle.js
Código duplicado no documento HTML.

O servidor está retornando 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, além de uma cópia completa da implementação da interface que é inicializada no cliente. A IU não se torna interativa até que bundle.js termine de carregar e executar.

Métricas de performance coletadas de sites reais usando a renderização e a reidratação do lado do servidor indicam que raramente é a melhor opção. O motivo mais importante é o efeito dela na experiência do usuário, quando uma página parece pronta, mas nenhum dos recursos interativos funciona.

Diagrama
    mostrando a renderização do cliente afetando negativamente o TTI.
Os efeitos da renderização do lado do cliente no TTI.

No entanto, há esperança para a renderização do lado do servidor com reidratação. A curto prazo, o uso apenas da 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, de forma progressiva ou parcial pode ser a chave para tornar essa técnica mais viável no futuro.

Fazer streaming da renderização do lado do servidor e hidratar progressivamente

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 levar a marcação aos seus usuários mais rapidamente, acelerando sua FCP. No React, os streams sendo assíncronos em renderToPipeableStream(), em comparação com renderToString() síncronos, significa que a pressão de retorno é bem tratada.

Também é importante considerar a reidratação progressiva, que foi implementada pelo React. 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 permite adiar o upgrade de partes de baixa prioridade da página no lado do cliente para evitar que bloqueie a linha de execução principal, permitindo que as interações do usuário aconteçam mais cedo depois que o usuário as inicia.

A reidratação progressiva também pode ajudar a evitar uma das armadilhas mais comuns de reidratação da renderização no lado do servidor: uma árvore DOM renderizada pelo servidor é destruída e imediatamente recriada, na maioria das vezes porque a renderização síncrona inicial do lado do cliente exigia dados que não estavam prontos, muitas vezes um Promise que ainda não foi resolvido.

Reidratação parcial

A reidratação parcial tem se mostrado difícil de implementar. Essa abordagem é uma extensão da reidratação progressiva que analisa partes individuais da página (componentes, visualizações ou árvores) e identifica as peças com pouca interatividade ou nenhuma reatividade. Para cada uma dessas partes quase estáticas, o código JavaScript correspondente é transformado em referências inertes e recursos decorativos, reduzindo a pegada do cliente para quase zero.

A abordagem de hidratação parcial tem seus 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 de página completo.

Renderização trisomórfica

Se os service workers forem uma opção para você, considere a renderização trisomórfica. Essa é uma técnica que permite usar a renderização de streaming do lado do servidor para navegação inicial ou não JS e fazer com que o service worker assuma a renderização de HTML para navegações depois de instalado. Isso mantém os componentes e modelos em cache atualizados e permite navegações no estilo SPA para renderizar novas visualizações na mesma sessão. Essa abordagem funciona melhor quando é possível compartilhar o mesmo modelo e código de roteamento entre o servidor, a página do cliente e o service worker.

Diagrama de renderização trisomórfica mostrando um navegador e um service worker se comunicando com o servidor.
Um diagrama de como funciona a renderização trismórfica.

Considerações sobre SEO

Ao escolher uma estratégia de renderização da Web, as equipes geralmente consideram o impacto da SEO. A renderização do lado do servidor é uma escolha comum para proporcionar uma experiência de aparência completa que os rastreadores podem interpretar. Os rastreadores entendem JavaScript, mas geralmente há limitações na renderização. A renderização do lado do cliente pode funcionar, mas geralmente precisa de mais testes e sobrecarga. Mais recentemente, a renderização dinâmica também se tornou uma opção a ser considerada 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 é uma ótima maneira de testar se a abordagem escolhida faz o que você esperava. Ela mostra uma prévia da aparência de uma página para o rastreador do Google, o conteúdo HTML serializado que encontra depois da execução do JavaScript e todos os erros encontrados durante a renderização.

Captura de tela da interface de teste de compatibilidade com dispositivos móveis.
A interface de teste de compatibilidade com dispositivos móveis.

Conclusão

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

Infográfico mostrando o espectro de opções descritas neste artigo.
Opções de renderização e as vantagens e desvantagens.

Créditos

Agradecemos a todos pelas avaliações e inspiração:

Jeffrey Posnick, Houssein Djirdeh, Shubhie Panicker, Chris Harrelson, and Sebastian Markbåge