Web Performance Made Easy - Google I/O 2018 edition

No Google I/O 2018, apresentamos um resumo de ferramentas, bibliotecas e técnicas de otimização que facilitam a melhoria da performance na Web. Aqui, explicamos esses conceitos usando o app The Oodles Theater. Também falamos sobre nossos experimentos com carregamento preditivo e a nova iniciativa Guess.js.

Ewa Gasperowicz

No ano passado, ficamos bastante ocupados tentando descobrir como tornar a Web mais rápida e com melhor desempenho. Isso levou a novas ferramentas, abordagens e bibliotecas que queremos compartilhar com você neste artigo. Na primeira parte, vamos mostrar algumas técnicas de otimização que usamos na prática ao desenvolver o app Oodles Theater. Na segunda parte, vamos falar sobre nossos experimentos com carregamento preditivo e a nova iniciativa Guess.js.

A necessidade de performance

A Internet fica mais pesada a cada ano. Se verificarmos o estado da Web, veremos que uma página mediana em dispositivos móveis pesa cerca de 1,5 MB, sendo a maior parte JavaScript e imagens.

O tamanho crescente dos sites, junto com outros fatores, como latência de rede, limitações de CPU, padrões de bloqueio de renderização ou código desnecessário de terceiros, contribui para o complicado quebra-cabeça de desempenho.

A maioria dos usuários classifica a velocidade como o principal item na hierarquia de necessidades da experiência do usuário. Isso não é muito surpreendente, porque não é possível fazer muita coisa até que uma página termine de carregar. Não é possível extrair valor da página nem admirar a estética dela.

Pirâmide da hierarquia de UX
Figura 1. Qual é a importância da velocidade para os usuários? (Speed Matters, Vol. 3)

Sabemos que a performance é importante para os usuários, mas também pode parecer um segredo descobrir onde começar a otimizar. Felizmente, há ferramentas que podem ajudar você nessa jornada.

Lighthouse: uma base para o fluxo de trabalho de performance

O Lighthouse faz parte do Chrome DevTools e permite auditar seu site, além de dar dicas sobre como melhorá-lo.

Recentemente, lançamos várias novas auditorias de performance que são muito úteis no fluxo de trabalho de desenvolvimento diário.

Novas auditorias do Lighthouse
Figura 2. Novas auditorias do Lighthouse

Vamos ver como aproveitar esses recursos em um exemplo prático: o app Oodles Theater. É um pequeno web app de demonstração em que você pode testar alguns dos nossos Google Doodles interativos favoritos e até jogar um ou dois jogos.

Ao criar o app, queríamos garantir que ele tivesse o melhor desempenho possível. O ponto de partida para a otimização foi um relatório do Lighthouse.

Relatório do Lighthouse para o app Oodles
Figura 3. Relatório do Lighthouse para o app Oodles

O desempenho inicial do nosso app, conforme mostrado no relatório do Lighthouse, era péssimo. Em uma rede 3G, o usuário precisava esperar 15 segundos para a primeira renderização significativa ou para o app ficar interativo. O Lighthouse destacou vários problemas no nosso site, e a pontuação geral de desempenho de 23 refletiu exatamente isso.

A página pesava cerca de 3,4 MB, e precisávamos reduzir esse peso.

Isso iniciou nosso primeiro desafio de performance: encontrar coisas que podemos remover facilmente sem afetar a experiência geral.

Oportunidades de otimização de performance

Remover recursos desnecessários

Há algumas coisas óbvias que podem ser removidas com segurança: espaços em branco e comentários.

Ganhos da minificação
Figura 4. Minificar e compactar JavaScript e CSS

O Lighthouse destaca essa oportunidade na auditoria de CSS e JavaScript não minimizados. Usamos o webpack no nosso processo de build. Para fazer a minificação, usamos o plug-in Uglify JS.

A minificação é uma tarefa comum. Por isso, você pode encontrar uma solução pronta para qualquer processo de build que use.

Outra auditoria útil nesse espaço é Ativar a compactação de texto. Não há motivo para enviar arquivos não compactados, e a maioria das CDNs oferece suporte a isso atualmente.

Usamos o Firebase Hosting para hospedar nosso código. O Firebase ativa o gzipping por padrão. Assim, ao hospedar nosso código em uma CDN razoável, recebemos esse recurso sem custos financeiros.

Embora o gzip seja uma forma muito conhecida de compactação, outros mecanismos, como Zopfli e Brotli, também estão ganhando espaço. O Brotli tem suporte na maioria dos navegadores, e você pode usar um binário para pré-compactar seus recursos antes de enviá-los ao servidor.

Use políticas de cache eficientes

A próxima etapa foi garantir que não enviamos recursos duas vezes se não for necessário.

A auditoria Política de cache ineficiente no Lighthouse nos ajudou a perceber que podíamos otimizar nossas estratégias de cache para alcançar exatamente isso. Ao definir um cabeçalho de expiração max-age no nosso servidor, garantimos que, em uma visita repetida, o usuário possa reutilizar os recursos que baixou antes.

O ideal é armazenar em cache o máximo de recursos possível com segurança pelo maior período de tempo possível e fornecer tokens de validação para revalidar com eficiência os recursos atualizados.

Remover código não utilizado

Até agora, removemos as partes óbvias do download desnecessário, mas e as partes menos óbvias? Por exemplo, código não usado.

Cobertura de código no DevTools
Figura 5. Verificar a cobertura do código

Às vezes, incluímos nos nossos apps códigos que não são realmente necessários. Isso acontece principalmente se você trabalhar no app por um período mais longo, se a equipe ou as dependências mudarem e, às vezes, uma biblioteca órfã for deixada para trás. Foi exatamente isso que aconteceu com a gente.

No início, usamos a biblioteca de componentes do Material para criar um protótipo rápido do app. Com o tempo, passamos a usar uma aparência mais personalizada e esquecemos completamente dessa biblioteca. Felizmente, a verificação de cobertura de código ajudou a redescobrir isso no nosso pacote.

Você pode verificar as estatísticas de cobertura de código nas DevTools, tanto para o tempo de execução quanto para o tempo de carregamento do aplicativo. É possível ver as duas grandes faixas vermelhas na captura de tela abaixo. Tínhamos mais de 95% do nosso CSS não utilizado, além de uma grande quantidade de JavaScript.

O Lighthouse também detectou esse problema na auditoria de regras de CSS não usadas. Ele mostrou uma possível economia de mais de 400 KB. Então, voltamos ao nosso código e removemos a parte JavaScript e CSS dessa biblioteca.

Se removermos o adaptador MVC, nossos estilos vão cair para 10 KB.
Figura 6. Se descartarmos o adaptador MVC, nossos estilos vão cair para 10 KB.

Isso reduziu nosso pacote CSS em 20 vezes, o que é muito bom para um commit pequeno de duas linhas.

É claro que isso aumentou nossa pontuação de desempenho e também melhorou muito o tempo até a interação.

No entanto, com mudanças como essa, não basta verificar apenas suas métricas e pontuações. Remover código real nunca é livre de riscos. Por isso, sempre fique atento a possíveis regressões.

Nosso código não foi usado em 95% dos casos. Ainda há 5% em algum lugar. Aparentemente, um dos nossos componentes ainda estava usando os estilos dessa biblioteca: as pequenas setas no controle deslizante do doodle. Como ele era muito pequeno, pudemos incorporar manualmente esses estilos de volta aos botões.

Botões quebrados por falta de biblioteca
Figura 7. Um componente ainda estava usando a biblioteca removida

Portanto, se você remover o código, verifique se tem um fluxo de trabalho de teste adequado para ajudar a evitar possíveis regressões visuais.

Evite payloads de rede muito grandes

Sabemos que recursos grandes podem diminuir a velocidade de carregamento das páginas da Web. Elas podem custar dinheiro aos nossos usuários e ter um grande impacto nos planos de dados deles. Por isso, é muito importante ter isso em mente.

O Lighthouse detectou um problema com alguns dos nossos payloads de rede usando a auditoria Payload de rede enorme.

Detectar payloads de rede muito grandes
Figura 8. Detectar payloads de rede muito grandes

Aqui, vimos que tínhamos mais de 3 MB de código sendo enviado, o que é muito, especialmente em dispositivos móveis.

No topo dessa lista, o Lighthouse destacou que tínhamos um pacote de fornecedor JavaScript com 2 MB de código não compactado. Esse também é um problema destacado pelo webpack.

Como diz o ditado: a solicitação mais rápida é aquela que não é feita.

O ideal é medir o valor de cada recurso que você está oferecendo aos usuários, a performance deles e decidir se vale a pena enviar com a experiência inicial. Às vezes, esses recursos podem ser adiados, carregados de forma lenta ou processados durante o tempo ocioso.

No nosso caso, como estamos lidando com muitos pacotes JavaScript, tivemos sorte porque a comunidade JavaScript tem um conjunto avançado de ferramentas de auditoria de pacotes JavaScript.

Auditoria de pacote JavaScript
Figura 9. Auditoria de pacote JavaScript

Começamos com o analisador de pacote do webpack, que informou que estávamos incluindo uma dependência chamada unicode, que tinha 1,6 MB de JavaScript analisado, ou seja, bastante.

Em seguida, acessamos nosso editor e, usando o plug-in de custo de importação para código visual, conseguimos visualizar o custo de cada módulo que estávamos importando. Isso nos permitiu descobrir qual componente estava incluindo código que fazia referência a esse módulo.

Depois, mudamos para outra ferramenta, o BundlePhobia. Essa ferramenta permite inserir o nome de qualquer pacote NPM e ver qual é o tamanho estimado dele após a minificação e a compactação com gzip. Encontramos uma alternativa interessante para o módulo de slug que estávamos usando, que pesava apenas 2,2 kb.Por isso, fizemos a troca.

Isso teve um grande impacto na nossa performance. Entre essa mudança e a descoberta de outras oportunidades para reduzir o tamanho do pacote JavaScript, economizamos 2,1 MB de código.

Observamos melhorias gerais de 65% depois de considerar o tamanho compactado e minimizado desses pacotes. Descobrimos que isso valeu a pena como um processo.

Portanto, em geral, tente eliminar downloads desnecessários nos seus sites e apps. Fazer um inventário dos seus recursos e medir o impacto na performance pode fazer uma grande diferença. Por isso, faça auditorias com frequência.

Reduza o tempo de inicialização do JavaScript com a divisão de código

Embora grandes payloads de rede possam ter um grande impacto no nosso app, há outra coisa que pode ter um impacto muito grande: o JavaScript.

O JavaScript é seu recurso mais caro. Em dispositivos móveis, se você estiver enviando grandes pacotes de JavaScript, isso poderá atrasar a interação dos usuários com os componentes da interface. Isso significa que eles podem tocar na interface sem que nada significativo aconteça. Por isso, é importante entender por que o JavaScript custa tanto.

É assim que um navegador processa JavaScript.

Processamento de JavaScript
Figura 10. Processamento de JavaScript

Primeiro, precisamos fazer o download desse script. Temos um mecanismo JavaScript que precisa analisar esse código, compilá-lo e executá-lo.

Essas fases não levam muito tempo em um dispositivo de última geração, como um computador desktop, um laptop ou até mesmo um smartphone de última geração. Mas em um smartphone comum, esse processo pode levar de cinco a dez vezes mais tempo. Isso atrasa a interatividade, então é importante tentar reduzir esse tempo.

Para ajudar você a descobrir esses problemas no seu app, adicionamos uma nova auditoria de tempo de inicialização do JavaScript ao Lighthouse.

Tempo de inicialização do JavaScript
Figura 11. Auditoria do tempo de inicialização do JavaScript

No caso do app Oodle, ele informou que tínhamos 1,8 segundo de tempo gasto na inicialização do JavaScript. O que estava acontecendo era que estávamos importando de forma estática todas as nossas rotas e componentes em um pacote JavaScript monolítico.

Uma técnica para contornar isso é usar a divisão de código.

Divisão de código é como pizza

A divisão de código é essa noção de, em vez de dar aos usuários uma pizza inteira de JavaScript, e se você desse apenas uma fatia por vez conforme eles precisassem?

A divisão de código pode ser aplicada no nível da rota ou do componente. Ele funciona muito bem com React e React Loadable, Vue.js, Angular, Polymer, Preact e várias outras bibliotecas.

Incorporamos a divisão de código ao nosso aplicativo e mudamos de importações estáticas para importações dinâmicas, o que nos permitiu carregar o código de forma assíncrona e lenta conforme necessário.

Divisão de código com importações dinâmicas
Figura 13. Divisão de código com importações dinâmicas

O impacto disso foi a redução do tamanho dos nossos pacotes e a diminuição do tempo de inicialização do JavaScript. Isso reduziu o tempo para 0,78 segundo, tornando o app 56% mais rápido.

Em geral, se você estiver criando uma experiência com muito JavaScript, envie apenas o código necessário para o usuário.

Aproveite conceitos como divisão de código, explore ideias como tree shaking e confira o repositório webpack-libs-optimizations para algumas ideias de como reduzir o tamanho da biblioteca se você estiver usando o webpack.

Otimizar imagens

Piada sobre o desempenho do carregamento de imagens

No app Oodle, usamos muitas imagens. Infelizmente, o Lighthouse não ficou tão entusiasmado quanto nós. Na verdade, falhamos em todas as três auditorias relacionadas a imagens.

Esquecemos de otimizar as imagens, não as dimensionamos corretamente e também poderíamos ter algum ganho usando outros formatos de imagem.

Auditorias de imagens
Figura 14. Auditorias de imagens do Lighthouse

Começamos otimizando nossas imagens.

Para uma rodada única de otimização, use ferramentas visuais como ImageOptim ou XNConvert.

Uma abordagem mais automatizada é adicionar uma etapa de otimização de imagem ao processo de build com bibliotecas como imagemin.

Assim, você garante que as imagens adicionadas no futuro sejam otimizadas automaticamente. Algumas CDNs, como Akamai ou soluções de terceiros, como Cloudinary, Fastly ou Uploadcare, oferecem soluções abrangentes de otimização de imagens. Assim, você também pode hospedar suas imagens nesses serviços.

Se você não quiser fazer isso por causa do custo ou de problemas de latência, projetos como Thumbor ou Imageflow oferecem alternativas auto-hospedadas.

Antes e depois da otimização
Figura 15. Antes e depois da otimização

Nosso PNG de plano de fundo foi sinalizado no webpack como grande, e com razão. Depois de dimensioná-lo corretamente para a janela de visualização e executá-lo no ImageOptim, chegamos a 100 kb, o que é aceitável.

Repetir esse processo para várias imagens no site permitiu reduzir significativamente o peso geral da página.

Use o formato certo para conteúdo animado

Os GIFs podem ficar muito caros. Surpreendentemente, o formato GIF nunca foi pensado como uma plataforma de animação. Portanto, mudar para um formato de vídeo mais adequado oferece uma grande economia em termos de tamanho do arquivo.

No app Oodle, usamos um GIF como uma sequência de introdução na página inicial. De acordo com o Lighthouse, podemos economizar mais de 7 MB ao mudar para um formato de vídeo mais eficiente. Nosso clipe pesava cerca de 7,3 MB, muito para qualquer site razoável.Por isso, transformamos em um elemento de vídeo com dois arquivos de origem: um MP4 e um WebM para oferecer suporte a mais navegadores.

Substituir GIFs animados por vídeos
Figura 16. Substituir GIFs animados por vídeo

Usamos a ferramenta FFmpeg para converter nosso GIF animado em um arquivo MP4. O formato WebM oferece uma economia ainda maior. A API ImageOptim pode fazer essa conversão para você.

ffmpeg -i animation.gif -b:v 0 -crf 40 -vf scale=600:-1 video.mp4

Conseguimos economizar mais de 80% do nosso peso total graças a essa conversão. Isso reduziu o tamanho para cerca de 1 MB.

Ainda assim, 1 MB é um recurso grande para enviar pela rede, especialmente para um usuário com largura de banda restrita. Felizmente, pudemos usar a API Effective Type para perceber que eles estavam em uma largura de banda lenta e oferecer um JPEG muito menor.

Essa interface usa o tempo de ida e volta efetivo e os valores de redução para estimar o tipo de rede que o usuário está usando. Ele simplesmente retorna uma string: "slow 2G", "2G", "3G" ou "4G". Então, dependendo desse valor, se o usuário estiver em uma rede abaixo de 4G, podemos substituir o elemento de vídeo pela imagem.

if (navigator.connection.effectiveType) { ... }

Isso prejudica um pouco a experiência, mas pelo menos o site pode ser usado em uma conexão lenta.

Carregar imagens fora da tela de forma lenta

Carrosséis, controles deslizantes ou páginas muito longas geralmente carregam imagens, mesmo que o usuário não possa vê-las imediatamente.

O Lighthouse vai sinalizar esse comportamento na auditoria de imagens fora da tela, e você também pode conferir isso no painel de rede do DevTools. Se você vir muitas imagens chegando enquanto apenas algumas estão visíveis na página, talvez seja melhor considerar o carregamento lento delas.

O carregamento lento ainda não é compatível de forma nativa no navegador. Por isso, precisamos usar JavaScript para adicionar essa capacidade. Usamos a biblioteca Lazysizes para adicionar o comportamento de carregamento lento às nossas capas do Oodle.

<!-- Import library -->
import lazysizes from 'lazysizes'  <!-- or -->
<script src="lazysizes.min.js"></script>

<!-- Use it -->

<img data-src="image.jpg" class="lazyload"/>
<img class="lazyload"
    data-sizes="auto"
    data-src="image2.jpg"
    data-srcset="image1.jpg 300w,
    image2.jpg 600w,
    image3.jpg 900w"/>

O Lazysizes é inteligente porque não apenas rastreia as mudanças de visibilidade do elemento, mas também faz a pré-busca proativa de elementos que estão perto da visualização para uma experiência ideal do usuário. Ele também oferece uma integração opcional do IntersectionObserver, que oferece pesquisas de visibilidade muito eficientes.

Depois dessa mudança, as imagens são buscadas sob demanda. Se quiser se aprofundar nesse assunto, confira images.guide, um recurso muito útil e abrangente.

Ajudar o navegador a entregar recursos essenciais com antecedência

Nem todos os bytes enviados pelo fio ao navegador têm o mesmo grau de importância, e o navegador sabe disso. Muitos navegadores têm heurísticas para decidir o que deve ser buscado primeiro. Às vezes, eles buscam CSS antes de imagens ou scripts.

Algo que pode ser útil é nós, como autores da página, informarmos ao navegador o que é realmente importante para nós. Felizmente, nos últimos anos, os fornecedores de navegadores adicionaram vários recursos para ajudar nisso, por exemplo, dicas de recursos como link rel=preconnect, preload ou prefetch.

Esses recursos que foram trazidos para a plataforma da Web ajudam o navegador a buscar a coisa certa na hora certa e podem ser um pouco mais eficientes do que alguns dos carregamentos personalizados, abordagens baseadas em lógica que são feitas usando script.

Vamos ver como o Lighthouse nos orienta a usar alguns desses recursos de maneira eficaz.

A primeira coisa que o Lighthouse nos diz para fazer é evitar várias viagens de ida e volta caras para qualquer origem.

Evite várias viagens de ida e volta caras para qualquer origem
Figura 17. Evite várias viagens de ida e volta caras para qualquer origem

No caso do app Oodle, usamos muito o Google Fonts. Sempre que você solta uma folha de estilo de fonte do Google na sua página, ela se conecta a até dois subdomínios. O Lighthouse está nos dizendo que, se pudéssemos aquecer essa conexão, economizaríamos até 300 milissegundos no tempo de conexão inicial.

Aproveitando a pré-conexão do link rel, podemos mascarar essa latência de conexão de maneira eficaz.

Principalmente com algo como o Google Fonts, em que nosso CSS de família tipográfica é hospedado em googleapis.com e nossos recursos de fontes são hospedados em Gstatic, isso pode ter um impacto muito grande. Então, aplicamos essa otimização e reduzimos algumas centenas de milissegundos.

Em seguida, o Lighthouse sugere que façamos o pré-carregamento de solicitações importantes.

Solicitações de pré-carregamento de chaves
Figura 18. Pré-carregar solicitações importantes

O <link rel=preload> é muito eficiente. Ele informa ao navegador que um recurso é necessário como parte da navegação atual e tenta fazer com que o navegador o busque assim que possível.

Agora, o Lighthouse está informando que devemos pré-carregar nossos principais recursos de fontes da Web, porque estamos carregando duas fontes da Web.

O pré-carregamento em uma fonte da Web é assim: especificando rel=preload, você transmite as com o tipo de fonte e especifica o tipo de fonte que está tentando carregar, como woff2.

O impacto disso na sua página é bastante evidente.

Impacto do pré-carregamento de recursos
Figura 19. Impacto do pré-carregamento de recursos

Normalmente, sem usar o link rel preload, se as fontes da Web forem essenciais para sua página, o navegador precisará buscar o HTML, analisar o CSS e, muito mais tarde, buscar as fontes da Web.

Usando o pré-carregamento de link rel, assim que o navegador analisa seu HTML, ele pode começar a buscar essas fontes da Web muito antes. No caso do nosso app, isso reduziu em um segundo o tempo necessário para renderizar texto usando nossas fontes da Web.

Mas não é tão simples assim se você tentar pré-carregar fontes usando o Google Fonts.

Os URLs das fontes do Google que especificamos nas nossas famílias de fontes nas folhas de estilo são algo que a equipe de fontes atualiza com bastante frequência. Esses URLs podem expirar ou ser atualizados com frequência. Por isso, sugerimos que você faça a hospedagem própria das fontes da Web se quiser ter controle total sobre a experiência de carregamento de fontes. Isso é ótimo porque dá acesso a recursos como o pré-carregamento de link rel.

No nosso caso, descobrimos que a ferramenta Google Web Fonts Helper é muito útil para desativar algumas dessas fontes da Web e configurá-las localmente. Confira essa ferramenta.

Se você estiver usando fontes da Web como parte dos seus recursos essenciais ou se for JavaScript, tente ajudar o navegador a entregar seus recursos essenciais o mais rápido possível.

Experimental: Priority Hints

Temos algo especial para compartilhar com você hoje. Além de recursos como dicas de recursos e pré-carregamento, estamos trabalhando em um novo recurso experimental do navegador chamado dicas de prioridade.

Definir a prioridade do conteúdo inicialmente visível
Figura 20. Dicas de prioridade

Esse é um novo recurso que permite indicar ao navegador a importância de um recurso. Ele expõe um novo atributo, "importance", com os valores "low", "high" ou "auto".

Isso permite transmitir a redução da prioridade de recursos menos importantes, como estilos, imagens ou chamadas da API Fetch não críticas, para reduzir a disputa. Também podemos aumentar a prioridade de coisas mais importantes, como nossas imagens principais.

No caso do nosso app Oodle, isso levou a um lugar prático em que pudemos otimizar.

Definir a prioridade do conteúdo inicialmente visível
Fig. 21. Definir a prioridade do conteúdo inicialmente visível

Antes de adicionarmos o carregamento lento às nossas imagens, o navegador estava fazendo o seguinte: tínhamos este carrossel de imagens com todos os nossos doodles, e o navegador buscava todas as imagens no início do carrossel com alta prioridade. Infelizmente, as imagens no meio do carrossel eram as mais importantes para o usuário. Então, definimos a importância dessas imagens de plano de fundo como muito baixa e as de primeiro plano como muito alta. Isso teve um impacto de dois segundos no 3G lento e na rapidez com que conseguimos buscar e renderizar essas imagens. Então, uma experiência positiva.

Esperamos lançar esse recurso no Canary em algumas semanas. Fique de olho!

Tenha uma estratégia de carregamento de fontes da Web

A tipografia é fundamental para um bom design. Se você estiver usando fontes da Web, o ideal é não bloquear a renderização do texto e, principalmente, não mostrar texto invisível.

Agora, destacamos isso no Lighthouse com a auditoria Evitar texto invisível enquanto as fontes da Web estão sendo carregadas.

Evitar texto invisível enquanto as fontes da Web são carregadas
Figura 22. Evite texto invisível enquanto as webfonts são carregadas

Se você carregar as fontes da Web usando um bloco de família tipográfica, o navegador vai decidir o que fazer se demorar muito para buscar essa fonte. Alguns navegadores aguardam até três segundos antes de usar uma fonte do sistema e, depois, a substituem pela fonte baixada.

Estamos tentando evitar esse texto invisível. Por isso, neste caso, não teríamos conseguido ver os doodles clássicos desta semana se a fonte da Web tivesse demorado muito. Felizmente, com um novo recurso chamado font-display, você tem muito mais controle sobre esse processo.

    @font-face {
      font-family: 'Montserrat';
      font-style: normal;
      font-display: swap;
      font-weight: 400;
      src: local('Montserrat Regular'), local('Montserrat-Regular'),
          /* Chrome 26+, Opera 23+, Firefox 39+ */
          url('montserrat-v12-latin-regular.woff2') format('woff2'),
            /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
          url('montserrat-v12-latin-regular.woff') format('woff');
    }

A exibição de fontes ajuda você a decidir como as fontes da Web serão renderizadas ou substituídas com base no tempo necessário para a troca.

Neste caso, estamos usando a troca de exibição de fontes. A troca dá à família tipográfica um período de bloqueio de zero segundo e um período de troca infinito. Isso significa que o navegador vai renderizar o texto imediatamente com uma fonte substituta se a fonte demorar para carregar. E ele vai trocar assim que a família de fontes estiver disponível.

No caso do nosso app, isso foi ótimo porque permitiu mostrar um texto significativo bem no início e fazer a transição para a fonte da Web assim que ela ficou pronta.

Resultado da exibição de fontes
Figura 23. Resultado da exibição de fontes

Em geral, se você estiver usando fontes da Web, como uma grande porcentagem da Web faz, tenha uma boa estratégia de carregamento de fontes da Web.

Há muitos recursos da plataforma da Web que você pode usar para otimizar a experiência de carregamento de fontes, mas também confira o repositório de receitas de fontes da Web (link em inglês) de Zach Leatherman, porque ele é muito bom.

Reduzir scripts que impedem a renderização

Há outras partes do aplicativo que podem ser enviadas antes na cadeia de download para oferecer pelo menos alguma experiência básica do usuário um pouco antes.

Na faixa da linha do tempo do Lighthouse, é possível ver que, durante esses primeiros segundos em que todos os recursos estão carregando, o usuário não consegue ver nenhum conteúdo.

Reduzir a oportunidade de folhas de estilo que impedem a renderização
Figura 24. Reduzir a oportunidade de folhas de estilo que impedem a renderização

O download e o processamento de folhas de estilo externas estão impedindo que nosso processo de renderização avance.

Podemos tentar otimizar nosso caminho de renderização crítica entregando alguns dos estilos um pouco antes.

Se extrairmos os estilos responsáveis por essa renderização inicial e os incluirmos no HTML, o navegador poderá renderizá-los imediatamente sem esperar que as folhas de estilo externas cheguem.

No nosso caso, usamos um módulo NPM chamado Critical para inserir nosso conteúdo crítico em index.html durante uma etapa de build.

Embora esse módulo tenha feito a maior parte do trabalho pesado para nós, ainda foi um pouco complicado fazer isso funcionar sem problemas em diferentes rotas.

Se você não tiver cuidado ou se a estrutura do site for muito complexa, pode ser difícil introduzir esse tipo de padrão se não tiver planejado a arquitetura de shell de app desde o início.

Por isso, é tão importante considerar a performance desde o início. Se você não projetar para performance desde o início, há uma grande chance de ter problemas ao fazer isso mais tarde.

No fim, o risco valeu a pena. Conseguimos fazer o app funcionar e ele começou a entregar conteúdo muito antes, melhorando significativamente o tempo da primeira exibição significativa.

O resultado

Essa foi uma longa lista de otimizações de desempenho que aplicamos ao nosso site. Vamos analisar o resultado. É assim que nosso app foi carregado em um dispositivo móvel médio em uma rede 3G, antes e depois da otimização.

A pontuação de performance do Lighthouse aumentou de 23 para 91. É um progresso muito bom em termos de velocidade. Todas as mudanças foram impulsionadas pela verificação e acompanhamento contínuos do relatório do Lighthouse. Se quiser conferir como implementamos tecnicamente todas as melhorias, acesse nosso repositório, principalmente os PRs que foram incluídos.

Performance preditiva: experiências do usuário orientadas por dados

Acreditamos que o aprendizado de máquina representa uma oportunidade interessante para o futuro em muitas áreas. Uma ideia que esperamos que inspire mais experimentos no futuro é que dados reais podem orientar as experiências do usuário que estamos criando.

Hoje, tomamos muitas decisões arbitrárias sobre o que o usuário pode querer ou precisar e, portanto, o que vale a pena ser pré-buscado, pré-carregado ou pré-armazenado em cache. Se acertarmos, poderemos priorizar uma pequena quantidade de recursos, mas é muito difícil dimensionar isso para todo o site.

Na verdade, temos dados disponíveis para informar melhor nossas otimizações hoje. Usando a API Google Analytics Reporting, podemos analisar a próxima página principal e as porcentagens de saída de qualquer URL no nosso site e, assim, chegar a conclusões sobre quais recursos devemos priorizar.

Se combinarmos isso com um bom modelo de probabilidade, evitaremos o desperdício de dados do usuário com pré-busca excessiva de conteúdo. Podemos aproveitar esses dados do Google Analytics e usar aprendizado de máquina e modelos como cadeias de Markov ou redes neurais para implementar esses modelos.

Agrupamento baseado em dados para apps da Web
Fig. 25. Agrupamento com base em dados para apps da Web

Para facilitar esses experimentos, temos o prazer de anunciar uma nova iniciativa chamada Guess.js.

Guess.js
Figura 26. Guess.js

O Guess.js é um projeto focado em experiências do usuário orientadas por dados para a Web. Esperamos que isso inspire você a usar dados para melhorar o desempenho da Web e ir além. Tudo é de código aberto e está disponível no GitHub hoje. Ele foi criado em colaboração com a comunidade de código aberto por Minko Gechev, Kyle Matthews do Gatsby, Katie Hempenius e várias outras pessoas.

Confira o Guess.js e diga o que você achou.

Resumo

As pontuações e métricas ajudam a melhorar a velocidade da Web, mas são apenas o meio, não as metas em si.

Todos nós já passamos por carregamentos de página lentos em dispositivos móveis, mas agora temos a oportunidade de oferecer aos nossos usuários experiências mais agradáveis e rápidas.

Melhorar a performance é uma jornada. Muitas pequenas mudanças podem levar a grandes ganhos. Ao usar as ferramentas de otimização certas e ficar de olho nos relatórios do Lighthouse, você pode oferecer uma experiência melhor e mais inclusiva aos seus usuários.

Agradecimentos especiais a: Ward Peeters, Minko Gechev, Kyle Mathews, Katie Hempenius, Dom Farolino, Yoav Weiss, Susie Lu, Yusuke Utsunomiya, Tom Ankers, Lighthouse e Google Doodles.