Como Gatsby contornou o Next.js

O autor do artigo, cuja tradução publicamos hoje, trabalha como programador na Antler. Esta empresa é um gerador de inicialização global. Há dias de demonstração na Antler várias vezes ao ano, reunindo muitos criadores de startups e investidores de todo o mundo. A situação em torno do COVID-19 forçou o Antler a traduzir seus eventos para um formato online.



A empresa queria garantir que os visitantes de seus eventos virtuais, sem se distraírem com nada e sem ficarem presos em lugar algum, pudessem ver a coisa mais importante. Ou seja, as idéias de startups apresentadas ao público, expressas como o conteúdo das páginas da web. Os dias de demonstração virtual podem ser de interesse para um público bastante amplo. Alguns membros desse público podem participar pela primeira vez em algo assim. Portanto, a empresa teve que fazer tudo da melhor maneira possível e fornecer carregamento em alta velocidade de páginas representando startups. Eles decidiram que esse é apenas o caso quando um aplicativo da web progressivo de alto desempenho (PWA, Progressive Web App) pode ser útil. O principal problema foi encontrar a tecnologia certa para o desenvolvimento do PWA.


Renderização de servidor ou gerador de site estático?


Para começar, apresentarei um pouco ao curso. Todos os nossos projetos são baseados no React e na biblioteca Material-UI. Como resultado, inicialmente decidimos não nos afastar dessa pilha de tecnologias, o que nos permitiria garantir alta velocidade de desenvolvimento e tornar o novo projeto compatível com o que já possuímos. A principal diferença entre este novo projeto e nossos outros aplicativos React foi que o banco de dados para eles foi criado usando o aplicativo create-react-app e que eles foram totalmente renderizados no cliente (CSR, Client-Side Rendering). Isso, em particular, levou ao fato de que, quando o aplicativo foi carregado inicialmente, os usuários foram forçados a observar uma tela branca em branco enquanto o código JavaScript do projeto estava carregando, processando e executando.

Precisávamos de um nível inflexível de desempenho. Portanto, começamos a pensar em usar a renderização do lado do servidor (SSR, renderização do servidor) ou um gerador de site estático (SSG, Static Site Generator) para que o carregamento inicial dos aplicativos fosse o mais rápido possível.

Nossos dados são armazenados no Cloud Firestore e os acessamos usando o Algolia. Isso nos permite controlar, no nível do campo do banco de dados, o acesso público aos dados com chaves de API restritas. Isso também melhora o desempenho da consulta. Por experiência, sabemos que as consultas do Algolia são mais rápidas que o normal e que o SDK JavaScript do Firestore compactado tem 86 KB de tamanho . No caso da Algólia, isso é 7,5 Kb .

Além disso, queríamos tornar os dados que fornecemos aos clientes o mais atualizados possível. Isso nos ajudaria a corrigir rapidamente dados errôneos que poderiam ser publicados acidentalmente. Embora a prática padrão do SSG preveja a implementação de solicitações de dados relevantes durante a montagem do projeto, esperávamos que em nosso banco de dados os dados fossem gravados com bastante frequência. Em particular, estamos falando sobre a gravação de dados iniciada por administradores usando a interface firetable, e por iniciativa dos fundadores dos projetos, usando o portal da web. Isso leva à montagem competitiva do projeto. Além disso, devido aos recursos estruturais do nosso banco de dados, pequenas alterações podem levar a novas operações de montagem do projeto. Isso torna nosso pipeline de CI / CD extremamente ineficiente. Portanto, precisamos que as solicitações para receber dados do repositório sejam executadas toda vez que um usuário solicita o carregamento de uma página. Infelizmente, isso significava que nossa solução não seria um exemplo de um projeto SSG "limpo".

Inicialmente, nosso aplicativo foi criado com base no Gatsby, pois já usamos páginas de entrada criadas no Gatsby e um deles já utilizava a biblioteca Material-UI. A primeira versão do projeto formou uma página que, enquanto os dados estavam sendo carregados, exibia um "esqueleto". Ao mesmo tempo, a primeira tinta de contenção (FCP) estava na região de 1 segundo.


Download do "esqueleto" da página com carregamento de dados subsequente

A solução acabou sendo interessante, mas teve suas desvantagens, porque os dados para a saída da página foram baixados por iniciativa do cliente:

  • Para visualizar o conteúdo da página, os usuários teriam que esperar pelo download desta página e pelos dados exibidos nela, obtidos através de 4 solicitações à Algolia.
  • JS- . , React «» . DOM.
  • . , , .

Como resultado, no fim de semana prolongado, decidi experimentar a versão SSR do projeto criado usando o Next.js. Felizmente para mim, a documentação da Material-UI teve um exemplo de projeto para o Next.js. Portanto, não precisei aprender toda essa estrutura do zero. Eu apenas tive que examinar algumas partes do tutorial e da documentação. Eu converti o aplicativo em um projeto que foi renderizado no servidor. Quando um usuário solicitou o carregamento da página, o servidor executou solicitações de dados necessárias para preencher a página. Esta etapa nos permitiu resolver os três problemas acima. Aqui estão os resultados do teste para duas opções de aplicativo.


Resultados da pesquisa de aplicativos usando o Google PageSpeed ​​Insights . À esquerda, Gatsby (SSG), à direita, o Next.js (SSR) ( imagem original ).

O FCP para a versão do Next.js do projeto era cerca de três vezes maior do que para a versão baseada no Gatsby. A versão do Gatsby do projeto tinha um Índice de velocidade de 3,3 segundos, enquanto a versão Next.js tinha 6,2 segundos. O tempo para o primeiro byte (TTFB, Tempo até o primeiro byte) foi de 2,56 segundos no Next.js e de 10 a 20 ms no Gatsby.

Deve-se notar que a versão Next.js. do site foi implantada em outro serviço (aqui usamos os serviços ZEIT Now e Firebase Hosting - isso também pode afetar o aumento do TTFB). Mas, apesar disso, ficou claro que a transferência de operações de upload de dados para o servidor fazia o site parecer mais lento, apesar do fato de que todos os materiais da página eram carregados aproximadamente ao mesmo tempo. O fato é que, na versão Next.js. do projeto, o usuário por algum tempo vê apenas uma página em branco em branco.


Captura de tela mostrando o carregamento de duas versões de um aplicativo. O download não foi concluído ao mesmo tempo. Os registros são sincronizados no momento em que você pressiona a tecla Enter.

Tudo isso nos fornece uma lição importante do campo do desenvolvimento da Web: você precisa fornecer feedback visual aos usuários. Um estudo descobriu que aplicativos que usam telas esqueléticas parecem carregar mais rapidamente.

Além disso, esse resultado não corresponde ao clima que você pode ter percebido se ler artigos sobre desenvolvimento web nos últimos anos. Ou seja, estamos falando do fato de que não há nada de errado em usar os recursos do cliente e que o SSR não é uma solução abrangente para problemas de desempenho.

Desempenho de geração de site estático: comparando Gatsby e Next.js


Enquanto as duas estruturas em consideração, Gatsby e Next.js, são conhecidas, respectivamente, por sua capacidade de gerar sites estáticos e renderização de servidor, o suporte ao SSG foi aprimorado no Next.js 9.3 , o que o torna um concorrente do Gatsby.

No momento da redação deste artigo, a capacidade do Next.js. de gerar sites estáticos ainda era muito recente. Ela tinha pouco mais de um mês. Ela ainda é reportada na primeira página do projeto. Agora, não há muitas comparações dos recursos SSG do Gatsby e Next.js (ou talvez ainda não existam tais comparações). Como resultado, decidi realizar meu próprio experimento.

Retornei a versão Gatsby do projeto ao estado em que os dados foram baixados no cliente e fiz com que as duas versões do aplicativo tivessem exatamente o mesmo conjunto de recursos. Ou seja, eu tive que remover o que os plugins do Gatsby são responsáveis: funções de SEO, gerando favicons, manifesto de PWA. Para comparar exclusivamente pacotes JavaScript criados por estruturas, não incluí imagens e outros conteúdos baixados de fontes externas nos projetos. Ambas as versões do aplicativo foram implantadas na plataforma Firebase Hosting. Para referência, duas versões do aplicativo foram criadas com base no Gatsby 2.20.9 e Next.js 9.3.4.

Corri o Lighthouse no meu computador 6 vezes para cada versão. Os resultados mostraram uma pequena vantagem para Gatsby.


Valores médios obtidos após o lançamento de 6 Lighthouse para cada estrutura ( imagem original )

Em termos de avaliação geral de desempenho, a versão Next.js ficou apenas ligeiramente atrás da versão Gatsby. O mesmo vale para FCP e Speed ​​Index. O próximo atraso em potencial da primeira entrada para a versão Next.js. é um pouco maior que na versão Gatsby.

Para entender melhor o que está acontecendo, virei para a guia Rede das ferramentas de desenvolvedor do Chrome. Como se viu, na versão Next.js. do projeto, o número de fragmentos nos quais o código JavaScript é dividido é 3 a mais do que na versão Gatsby (excluindo arquivos de manifesto), mas o código compactado é 20 KB menor. As solicitações extras necessárias para baixar esses arquivos superam tanto os benefícios de um pacote menor que prejudicam o desempenho?


Na versão Gatsby do projeto, 7 solicitações são executadas para baixar 379 KB de dados. Na versão Next.js. do projeto - 12 solicitações para baixar 359 KB de dados ( imagem original )

Se você analisar o desempenho do JavaScript, as ferramentas do desenvolvedor dizem que a versão Next.js. do projeto precisa de 300 ms adicionais para a primeira renderização e que esta versão gaste mais tempo na tarefa Avaliar Script. Nas ferramentas do desenvolvedor, essa tarefa foi marcada como uma "tarefa longa".


Análise do desempenho de diferentes opções de projeto usando a guia Desempenho das ferramentas de desenvolvedor do Chrome ( imagem original )

. Comparei o código do projeto para descobrir se existem diferenças na implementação que possam afetar o desempenho. Com exceção da remoção de códigos e correções desnecessárias associadas aos tipos TypeScript ausentes, a única diferença era a implementação de rolagem suave da página ao passar para partes individuais. Esse recurso foi introduzido anteriormente por um arquivogatsby-browser.jse foi movido para um componente importado dinamicamente . Como resultado, esse código seria executado apenas em um navegador. (Usamos o pacote npm de rolagem suavee, ao importá-lo, ele precisa de um objetowindow.) Esse problema pode ser o culpado, mas não sei como ele é tratado no Next.js.

Gatsby é mais conveniente do ponto de vista do desenvolvedor


No final, decidi optar pela versão Gatsby do projeto. Além disso, aqui não levei em consideração a vantagem de desempenho muito pequena que o Gatsby mostrou em comparação com o mecanismo SSG Next.js. (não vou me apegar seriamente à vantagem de 0,6 segundos?). O fato é que, na versão Gatsby do projeto, muitos recursos do PWA já estão implementados, e não vi o ponto de tê-los implementados novamente na versão Next.js. do aplicativo.

Quando eu estava criando a primeira versão do Gatsby do projeto, pude adicionar rapidamente alguns recursos úteis do PWA ao projeto. Por exemplo, para adicionar a cada página minhas próprias metatags necessárias para o SEO, eu apenas tive que ler o manual . Para equipar o projeto com um manifesto do PWA, eu só precisava usar o plug-in apropriado. Para equipar o projeto com favicons que suportariam todas as plataformas disponíveis (e, neste caso, ainda há uma bagunça terrível ), eu nem precisei fazer nada, pois o suporte a favicon faz parte do plugin responsável pelo manifesto. É muito confortável!

A implementação dos mesmos recursos na versão Next.js do aplicativo exigiria mais trabalho. Eu precisaria procurar manuais de treinamento, todos os tipos de "melhores práticas". E o fato de que eu teria sucesso, de qualquer maneira, não me daria nenhuma vantagem. Afinal, a versão Next.js. do projeto não difere em desempenho superior à versão Gatsby. Além disso, esse foi o motivo pelo qual decidi simplesmente desativar os recursos correspondentes da versão Gatsby do projeto, comparando-a com a versão Next.js. A documentação do Next.js é mais concisa que a do Gatsby (talvez o fato seja que o Next.js seja menor que o Gatsby) Eu realmente gosto do tutorial gamificado do Next.js. Mas a documentação mais extensa do Gatsby é mais valiosa com o desenvolvimento real do PWA, embora à primeira vista pareça enorme.


Documentação do Gatsby É

verdade, não consigo ficar calado sobre os pontos fortes do Next.js.

  • Graças ao tutorial e à documentação concisa do Next.js, parece que essa estrutura pode ser aprendida mais rapidamente que o Gatsby.
  • O sistema de carregamento de dados usado no Next.js é baseado em funções assíncronas e na API de busca. Como resultado, ao desenvolver o Next.js., o desenvolvedor não tem a sensação de que precisa aprender o GraphQL para aproveitar ao máximo os recursos da estrutura.
  • Next.js TypeScript, Gatsby , ( ). Next.js , , , .

Devido ao fato de o Next.js ter aprimorado o suporte ao SSG, essa estrutura se tornou uma ferramenta poderosa que permite, no nível de cada página individual, escolher o método de trabalhar com ela. Pode ser SSR, SSG ou CSR.

De fato, se eu pudesse gerar esse aplicativo de uma forma completamente estática, o Next.js me serviria melhor, pois eu poderia usar a API-Algolia JS padrão e manter o código para carregar dados no mesmo arquivo que e código do componente. Como o Algolia não possui uma API GraphQL integrada e não há um plug-in do Gatsby para o Algolia, a implementação de um mecanismo desse tipo no Gatsby exigiria a adição desse código a um novo arquivo . E isso vai contra a maneira declarativa intuitiva de descrever as páginas.

Sobre maneiras adicionais de melhorar o desempenho do projeto


Depois de resolvermos o problema de escolher uma estrutura, note-se que existem maneiras adicionais de melhorar o desempenho do projeto que não estão relacionadas à estrutura. Essas melhorias podem elevar a classificação do projeto Lighthouse para 100.

  • Na lista de discussão de março da Algolia , foi recomendado adicionar uma dica preconnectpara aumentar ainda mais a velocidade da execução da consulta. (É verdade, infelizmente, o fragmento de código errado é fornecido no boletim. Aqui está o código correto.)
  • . JS- CSS-, webpack- Gatsby. Gatsby . , , Netlify Amazon S3. , Firebase Hosting, , .
  • Usamos imagens JPEG e PNG carregadas por criadores de inicialização no aplicativo. Nós não os compactamos e otimizamos. Melhorar esse aspecto de nosso aplicativo é um grande desafio e está além do escopo deste projeto. Além disso, seria ótimo se todas essas imagens fossem convertidas para o formato WebP. Como resultado, teríamos que armazenar imagens usando apenas um formato gráfico altamente eficiente. Infelizmente, como em muitos outros recursos do PWA, a equipe de desenvolvimento do Safari WebKit é viciante com o suporte ao WebP. Agora é o único navegador principal que não suporta esse formato.

Sumário


Se resumirmos brevemente o que estávamos falando aqui, podemos dizer o seguinte:

  • A saída da versão "esquelética" da página durante o carregamento de dados pelo cliente cria ao usuário uma sensação de operação mais rápida do site do que quando o usuário visualiza uma página em branco enquanto o servidor está carregando dados.
  • A versão gatsby do site era apenas um pouco mais rápida que a versão Next.js. No entanto, o sistema de plug-in Gatsby e a documentação do projeto de alta qualidade aumentam a usabilidade dessa estrutura para o desenvolvedor.

Queridos leitores! Você usa geradores de sites estáticos ou sistemas de renderização no lado do servidor para acelerar seus projetos?


All Articles