Mudando para Next.js e acelerando o carregamento da página inicial do manifold.co 7,5 vezes

Hoje estamos publicando uma tradução de uma história sobre como a transição do React Boilerplate para o Next.js. , uma estrutura para o desenvolvimento de aplicativos Web progressivos baseados no React, acelerou o carregamento da página inicial do projeto manifold.co em 7,5 vezes. Nenhuma outra alteração foi feita no projeto, e essa transição, em geral, acabou sendo completamente invisível para outras partes do sistema. No final, o que acabou por ser ainda melhor do que o esperado.



Visão geral dos resultados


De fato, podemos dizer que a transição para o Next.js nos deu algo como "um aumento de produtividade do projeto que surgiu do nada". Veja como é o tempo de carregamento do projeto ao usar vários recursos de hardware e conexões de rede.
Ligação
CPU
A segundos
Depois de segundos
% De melhoria
Rápido (200 Mbps)
Rápido
1.5
0,2
750
Médio (3G)
Rápido
5.6
1.1
500
Médio (3G)
Médio
7,5
1.3
570
Lento (conexão 3G lenta)
Médio
22
4
550

Ao usar uma conexão rápida e um dispositivo com um processador rápido, o tempo de carregamento do site caiu de 1,5 s. até 0,2 s., ou seja, esse indicador melhorou 7,5 vezes. Em uma conexão de qualidade média e em um dispositivo com desempenho médio, o tempo de carregamento do site caiu de 7,5 s. até 1,3 s

O que acontece depois que um usuário clica em um URL?


Para entender os recursos do trabalho de aplicativos da web progressivos (Progressive Web App, PWA), primeiro você precisa descobrir o que acontece entre o momento em que o usuário acessa a URL (no endereço do site) e o momento em que vê algo em uma janela do navegador (nesse caso, nosso aplicativo React).


Etapas do aplicativo

Considere os 5 estágios do trabalho com o aplicativo, cujo diagrama é dado acima.

  1. O usuário acessa a URL, o sistema descobre o endereço do servidor usando DNS e acessa o servidor. Tudo isso é feito com extrema rapidez, geralmente levando menos de 100 milissegundos, mas essa etapa leva algum tempo, razão pela qual é mencionada aqui.
  2. Agora, o servidor retorna o código HTML da página, mas a página no navegador permanece vazia até que os recursos necessários para sua exibição sejam carregados (a menos que os recursos sejam carregados de forma assíncrona ). Na verdade, mais ações estão ocorrendo nesta fase do que as mostradas no diagrama, mas uma revisão conjunta de todos esses processos também nos convém.
  3. Depois de carregar o código HTML e os recursos mais importantes, o navegador começa a exibir o que pode ser exibido, continuando carregando tudo o resto (imagens, por exemplo) em segundo plano. Você já se perguntou por que as imagens às vezes aparecem de repente em uma página obviamente mais rápida do que o necessário e às vezes carregam muito tempo? É exatamente por isso que isso acontece. Essa abordagem permite criar rapidamente uma página finalizada.
  4. O código JavaScript pode ser analisado e executado somente após o carregamento. Dependendo do tamanho do código JS usado na página (e isso pode ser, para um aplicativo React típico, bastante grande se o código estiver empacotado em um único arquivo), isso poderá levar alguns segundos ou mais (observe que JS o código não precisa, para iniciar a execução, aguardar o carregamento de todos os outros recursos, apesar de no diagrama parecer exatamente assim).
  5. No caso de um aplicativo React, chega o momento em que o código modifica o DOM, o que faz com que o navegador redesenhe a página já exibida. Em seguida, outro ciclo de carregamento de recursos é iniciado. O tempo que esta etapa leva dependerá da complexidade da página.

Quanto mais rápido, melhor.


Como um aplicativo Web progressivo utiliza o código React e produz código HTML e CSS estático, isso significa que o usuário já vê o aplicativo React na etapa 3 do esquema acima, e não na etapa 5. Em nossos testes, isso leva de 0,2 a 4 segundos , que depende da velocidade da conexão do usuário com a Internet e do dispositivo dele. Isso é muito melhor do que os 1,5-22 segundos anteriores. Os aplicativos da web progressivos são uma maneira confiável de fornecer aplicativos do React mais rapidamente para o usuário.

A razão pela qual aplicativos da Web progressivos e estruturas relacionadas como Next.js ainda não são muito populares é porque, tradicionalmente, as estruturas JS não são particularmente bem-sucedidas na geração de código HTML estático. Hoje, tudo mudou muito devido ao fato de que estruturas como React, Vue e Angular, entre outras, têm excelente suporte para renderização no servidor. No entanto, para usar essas ferramentas, você ainda precisa de um entendimento profundo dos recursos do trabalho dos empacotadores e das ferramentas para criar projetos. Trabalhar com tudo isso não é sem problemas.

O recente surgimento de estruturas do PWA, como Next.js e Gatsby (ambos surgiram no final de 2016 - início de 2017), foi um passo sério para a adoção generalizada do PWA devido a barreiras de entrada mais baixas e pelo fato de ter feito do uso dessas estruturas uma tarefa simples e agradável.

Embora nem todos os aplicativos possam ser transferidos para o Next.js., para muitos aplicativos React, essa transição significa o mesmo "desempenho do nada" que estamos falando aqui, complementado por um uso ainda mais eficiente dos recursos de rede.

Quão difícil é migrar para o Next.js?


Em geral, note-se que traduzir nossa página inicial para Next.js não foi muito difícil. No entanto, encontramos algumas dificuldades causadas pelos recursos de arquitetura do nosso aplicativo.

▍ Rejeitar um roteador React


Tivemos que abandonar o roteador React porque o Next.js. possui seu próprio roteador interno, que é melhor combinado com otimizações relacionadas à separação de código executadas na arquitetura PWA. Isso permite que esse roteador forneça um carregamento de página muito mais rápido do que o esperado de qualquer roteador do lado do cliente.

O roteador Next.js é um pouco um roteador React de alta velocidade, mas ainda não é um roteador React.

Na prática, como não aproveitamos os recursos particularmente avançados que o roteador React oferece, a transição para o roteador Next.js. era simplesmente substituir o componente roteador React padrão pelo componente Next.j correspondente:

/*   ( React) */ <Link to="/my/page">  A link </Link> /*   ( Next.js) */ <Link href="/my/page" passHref>  <a>    A link  </a> </Link> 

Em geral, tudo acabou não sendo tão ruim. Tivemos que renomear a propriedade e adicionar uma tag para fins de renderização do servidor. Como também usamos a biblioteca de styled-components , na maioria dos casos é necessário adicionar a propriedade passHref para garantir que o sistema se comporte de maneira que o href sempre aponte para a tag gerada.


Pedidos de rede para manifold.co

Para ver com seus próprios olhos a otimização do roteador Next.js. em ação, abra a guia Rede das ferramentas de desenvolvedor do navegador, visualizando a página manifold.co e clique em algum link. A figura anterior mostra o resultado de clicar no link /services . Como você pode ver, isso leva à execução de uma solicitação para carregar o services.js vez de executar uma solicitação regular.

Não estou falando apenas sobre roteamento do lado do cliente; o roteador React também é adequado para resolver esse problema. Estou falando de uma parte real do código JavaScript que foi extraída do restante do código e carregada mediante solicitação. Isso é feito usando o Next.js. padrão. E isso é muito melhor do que o que tínhamos antes. Nomeadamente, estamos falando de um grande pacote de código JS com um tamanho de 1,7 MB, que o cliente, antes que ele pudesse ver algo, precisou baixar e processar.

Embora a solução apresentada aqui não seja perfeita, ela está muito mais próxima do que a anterior da ideia de que os usuários apenas baixam o código para as páginas que visualizam.

▍ Recursos do Redux


Continuando o tópico das dificuldades associadas à transição para o Next.js., note-se que todas as otimizações interessantes pelas quais o Next.js. passa pelo aplicativo têm um certo impacto nesse aplicativo. Ou seja, como o Next.js realiza a separação de código no nível da página, ele impede que o desenvolvedor acesse o componente raiz React ou o método render() da biblioteca react-dom . Se você já esteve envolvido na configuração do Redux, pode observar que tudo isso nos diz que, para operação normal com o Redux, precisamos resolver o problema, ou seja, não está claro exatamente onde procurar pelo Redux.

O Next.js fornece um componente especial de ordem superior, withRedux , que withRedux como um wrapper para todos os componentes de nível superior em cada página:

 export default withRedux(HomePage); 

Embora tudo isso não seja tão ruim, mas se você precisar createStore() métodos createStore() , como ao usar injetores redux-reducer , espere que você precise de mais tempo para depurar o wrapper (e, a propósito, nunca tente use algo como redux-reducer-injectors ).

Além disso, devido ao fato de o Redux agora ser uma "caixa preta", o uso da biblioteca Imutável com ele se torna problemático. Embora o fato de o Immutable funcione com o Redux pareça bastante óbvio, eu tive um problema. Portanto, o estado de nível superior não era imutável (o get is not a function erro de get is not a function ) ou o componente wrapper tentou usar a notação de ponto para trabalhar com objetos JS em vez do método .get() ( Can't get catalog of undefined erros Can't get catalog of undefined ). Para depurar esse problema, tive que me referir ao código fonte. Afinal, o Next.js força o desenvolvedor a usar seus próprios mecanismos por um motivo.

Em geral, pode-se notar que o principal problema associado ao Next.js. é que muito pouco nesta estrutura está bem documentado. Existem muitos exemplos na documentação com base nos quais você pode criar algo próprio, mas se entre eles não houver um que reflita os recursos do seu projeto, você poderá desejar boa sorte.

Etch Buscar rejeição


Usamos a biblioteca react-inlinesvg , que oferece opções de estilo para imagens SVG incorporadas e cache de consultas. Mas aqui tivemos um problema: ao executar a renderização do servidor, não existem solicitações XHR (pelo menos não no sentido de URLs geradas pelo Webpack, como seria de esperar). As tentativas de executar essas solicitações interferem na renderização do servidor.

Embora existam outras bibliotecas para trabalhar com dados SVG incorporados que suportam SSR, decidi abandonar esse recurso, pois os arquivos SVG ainda eram raramente usados. Substituí-los por imagens comuns, tags <img> , se não precisar de estilo ao exibir as imagens correspondentes, ou incorporá-las ao código na forma de React JSX. Provavelmente, tudo ficou melhor, já que as ilustrações JSX agora atingem o navegador quando a página foi carregada pela primeira vez e o pacote JS enviado ao cliente tinha 1 biblioteca a menos.

Se você precisar usar mecanismos de carregamento de dados (eu precisava desse recurso para outra biblioteca), poderá configurá-lo com next.config.js , usando whatwg-fetch e node-fetch :

 module.exports = { webpack: (config, options) =>   Object.assign(config, {     plugins: config.plugins.concat([       new webpack.ProvidePlugin(         config.isServer           ? {}           : { fetch: 'imports-loader?this=>global!exports-loader?global.fetch!whatwg-fetch' }       ),     ]),   resolve: Object.assign(config.resolve, {     alias: Object.assign(       config.resolve.alias,       config.isServer ? {} : { fetch: 'node-fetch' }     ),   }), }), }; 

JS JS do cliente e servidor


O último recurso do Next.js, que eu gostaria de mencionar aqui, é que essa estrutura é iniciada duas vezes - uma para o servidor e outra para o cliente. Isso obscurece levemente a linha entre o JavaScript do lado do cliente e o código Node.js. na mesma base de código, causando erros incomuns como o fs is undefined ao tentar tirar proveito dos recursos do Node.js. no cliente.

Como resultado, precisamos construir essas construções em next.js.config :

 module.exports = { webpack: (config, options) =>   Object.assign(config, {     node: config.isServer ? undefined : { fs: 'empty' },   }), }; 

O sinalizador config.isServer no Webpack será seu melhor amigo se você precisar executar o mesmo código em ambientes diferentes.

Além disso, o Next.js. suporta, além dos métodos padrão para o ciclo de vida dos componentes React, o método getInitialProps() , chamado somente quando o código getInitialProps() no modo de servidor:

 class HomePage extends React.Component { static getInitialProps() {   //         } componentDidMount() {   //     ,    } … } 

Sim, e não vamos esquecer que nosso bom amigo, o objeto de window , necessário para organizar a escuta de eventos, para determinar o tamanho da janela do navegador e dar acesso a muitas funções úteis, não está disponível no Node.js:

 if (typeof window !== 'undefined') { // ,     `window`      } 

Deve-se observar que mesmo o Next.js não pode salvar o desenvolvedor da necessidade de resolver problemas associados à execução do mesmo código no servidor e no cliente. Mas, ao resolver esses problemas, config.isServer e getInitialProps() são muito úteis.

Resultados: o que acontecerá após o Next.js?


A curto prazo, a estrutura Next.js. corresponde perfeitamente, em termos de desempenho, aos nossos requisitos de renderização do servidor e à capacidade de exibir nosso site em dispositivos com o JavaScript desativado. Além disso, agora ele permite o uso de metatags avançadas (avançadas).

Talvez, no futuro, consideraremos outras opções, caso nosso aplicativo precise da renderização do servidor e de uma lógica mais complexa do servidor (por exemplo, examinamos a possibilidade de implementar a tecnologia de logon único em manifold.co e dashboard.manifold.co ) Mas até então, usaremos o Next.js, pois essa estrutura, com pequenos custos de tempo, nos trouxe enormes benefícios.

Caros leitores! Você usa o Next.js em seus projetos?

Source: https://habr.com/ru/post/pt442654/


All Articles