Desempenho de front-end 2021: otimizações de entrega
Publicados: 2022-03-10Este guia foi gentilmente apoiado por nossos amigos da LogRocket, um serviço que combina monitoramento de desempenho de front-end , repetição de sessão e análise de produto para ajudá-lo a criar melhores experiências para o cliente. LogRocket rastreia as principais métricas, incl. DOM completo, tempo para o primeiro byte, atraso da primeira entrada, CPU do cliente e uso de memória. Obtenha uma avaliação gratuita do LogRocket hoje.
Índice
- Preparação: planejamento e métricas
- Definir metas realistas
- Definindo o Ambiente
- Otimizações de recursos
- Otimizações de compilação
- Otimizações de entrega
- Rede, HTTP/2, HTTP/3
- Teste e monitoramento
- Vitórias rápidas
- Tudo em uma página
- Baixe a lista de verificação (PDF, Apple Pages, MS Word)
- Assine nossa newsletter por e-mail para não perder os próximos guias.
Otimizações de entrega
-
defer
para carregar JavaScript crítico de forma assíncrona?
Quando o usuário solicita uma página, o navegador busca o HTML e constrói o DOM, então busca o CSS e constrói o CSSOM, e então gera uma árvore de renderização combinando o DOM e o CSSOM. Se algum JavaScript precisar ser resolvido, o navegador não começará a renderizar a página até que ela seja resolvida, atrasando assim a renderização. Como desenvolvedores, temos que dizer explicitamente ao navegador para não esperar e começar a renderizar a página. A maneira de fazer isso para scripts é com os atributosdefer
easync
em HTML.Na prática, é melhor usar
defer
em vez deasync
. Ah, qual é a diferença de novo? De acordo com Steve Souders, assim que os scriptsasync
chegam, eles são executados imediatamente – assim que o script estiver pronto. Se isso acontecer muito rápido, por exemplo, quando o script já estiver no cache, ele pode bloquear o analisador HTML. Comdefer
, o navegador não executa scripts até que o HTML seja analisado. Portanto, a menos que você precise que o JavaScript seja executado antes de iniciar a renderização, é melhor usardefer
. Além disso, vários arquivos assíncronos serão executados em uma ordem não determinística.Vale a pena notar que existem alguns equívocos sobre
async
edefer
. Mais importante,async
não significa que o código será executado sempre que o script estiver pronto; significa que ele será executado sempre que os scripts estiverem prontos e todo o trabalho de sincronização anterior estiver concluído. Nas palavras de Harry Roberts, "Se você colocar um scriptasync
após os scripts de sincronização, seu scriptasync
será tão rápido quanto o script de sincronização mais lento".Além disso, não é recomendado usar
async
edefer
. Os navegadores modernos suportam ambos, mas sempre que ambos os atributos são usados, oasync
sempre vence.Se você quiser se aprofundar em mais detalhes, Milica Mihajlija escreveu um guia muito detalhado sobre como construir o DOM mais rapidamente, entrando nos detalhes da análise especulativa, assíncrona e adiada.
- Carregue lentamente componentes caros com IntersectionObserver e dicas de prioridade.
Em geral, é recomendável carregar lentamente todos os componentes caros, como JavaScript pesado, vídeos, iframes, widgets e potencialmente imagens. O carregamento lento nativo já está disponível para imagens e iframes com o atributo deloading
(somente Chromium). Sob o capô, esse atributo adia o carregamento do recurso até atingir uma distância calculada da janela de visualização.<!-- Lazy loading for images, iframes, scripts. Probably for images outside of the viewport. --> <img loading="lazy" ... /> <iframe loading="lazy" ... /> <!-- Prompt an early download of an asset. For critical images, eg hero images. --> <img loading="eager" ... /> <iframe loading="eager" ... />
Esse limite depende de algumas coisas, desde o tipo de recurso de imagem que está sendo buscado até o tipo de conexão efetiva. Mas experimentos realizados usando o Chrome no Android sugerem que em 4G, 97,5% das imagens abaixo da dobra que são carregadas com preguiça foram totalmente carregadas em 10 ms após se tornarem visíveis, por isso deve ser seguro.
Também podemos usar o atributo de
importance
(high
oulow
) em um elemento<script>
,<img>
ou<link>
(somente Blink). Na verdade, é uma ótima maneira de priorizar imagens em carrosséis, bem como redefinir a prioridade de scripts. No entanto, às vezes podemos precisar de um controle um pouco mais granular.<!-- When the browser assigns "High" priority to an image, but we don't actually want that. --> <img src="less-important-image.svg" importance="low" ... /> <!-- We want to initiate an early fetch for a resource, but also deprioritize it. --> <link rel="preload" importance="low" href="/script.js" as="script" />
A maneira mais eficiente de fazer um carregamento lento um pouco mais sofisticado é usando a API Intersection Observer, que fornece uma maneira de observar as alterações de forma assíncrona na interseção de um elemento de destino com um elemento ancestral ou com a janela de visualização de um documento de nível superior. Basicamente, você precisa criar um novo objeto
IntersectionObserver
, que recebe uma função de retorno de chamada e um conjunto de opções. Em seguida, adicionamos um alvo para observar.A função de retorno de chamada é executada quando o destino se torna visível ou invisível, portanto, quando ela intercepta a viewport, você pode começar a executar algumas ações antes que o elemento se torne visível. Na verdade, temos um controle granular sobre quando o retorno de chamada do observador deve ser invocado, com
rootMargin
(margem ao redor da raiz) ethreshold
(um único número ou uma matriz de números que indicam em qual porcentagem da visibilidade do alvo estamos mirando).Alejandro Garcia Anglada publicou um tutorial útil sobre como realmente implementá-lo, Rahul Nanwani escreveu um post detalhado sobre carregamento lento de imagens de primeiro plano e plano de fundo, e o Google Fundamentals fornece um tutorial detalhado sobre carregamento lento de imagens e vídeos com o Intersection Observer também.
Lembra-se de longas leituras de histórias com direção de arte com objetos em movimento e pegajosos? Você também pode implementar o scrollytelling de alto desempenho com o Intersection Observer.
Verifique novamente o que mais você poderia carregar com preguiça. Até mesmo strings de tradução e emojis com carregamento lento podem ajudar. Ao fazer isso, o Mobile Twitter conseguiu alcançar uma execução JavaScript 80% mais rápida a partir do novo pipeline de internacionalização.
Uma palavra rápida de cautela, porém: vale a pena notar que o carregamento lento deve ser uma exceção e não a regra. Provavelmente não é razoável carregar com preguiça qualquer coisa que você realmente queira que as pessoas vejam rapidamente, por exemplo, imagens de páginas de produtos, imagens de heróis ou um script necessário para que a navegação principal se torne interativa.
- Carregue imagens progressivamente.
Você pode até levar o carregamento lento para o próximo nível adicionando carregamento progressivo de imagens às suas páginas. Da mesma forma que no Facebook, Pinterest, Medium e Wolt, você pode carregar imagens de baixa qualidade ou até desfocadas primeiro e, à medida que a página continuar a carregar, substitua-as pelas versões de qualidade total usando a técnica BlurHash ou LQIP (Low Quality Image Placeholders) técnica.As opiniões diferem se essas técnicas melhoram ou não a experiência do usuário, mas definitivamente melhora o tempo para a primeira pintura de conteúdo. Podemos até automatizá-lo usando SQIP que cria uma versão de baixa qualidade de uma imagem como um espaço reservado SVG, ou Espaços reservados de imagem de gradiente com gradientes lineares CSS.
Esses espaços reservados podem ser incorporados ao HTML, pois são naturalmente compactados com métodos de compactação de texto. Em seu artigo, Dean Hume descreveu como essa técnica pode ser implementada usando o Intersection Observer.
Cair pra trás? Se o navegador não suportar observador de interseção, ainda podemos carregar um polyfill com preguiça ou carregar as imagens imediatamente. E tem até uma biblioteca para isso.
Quer ficar mais chique? Você pode rastrear suas imagens e usar formas e arestas primitivas para criar um espaço reservado SVG leve, carregá-lo primeiro e, em seguida, fazer a transição da imagem vetorial do espaço reservado para a imagem bitmap (carregada).
- Você adia a renderização com
content-visibility
?
Para layouts complexos com muitos blocos de conteúdo, imagens e vídeos, decodificar dados e renderizar pixels pode ser uma operação bastante cara — especialmente em dispositivos de baixo custo. Comcontent-visibility: auto
, podemos solicitar que o navegador ignore o layout dos filhos enquanto o contêiner estiver fora da janela de visualização.Por exemplo, você pode pular a renderização do rodapé e das seções finais no carregamento inicial:
footer { content-visibility: auto; contain-intrinsic-size: 1000px; /* 1000px is an estimated height for sections that are not rendered yet. */ }
Observe que a visibilidade do conteúdo: auto; se comporta como estouro: oculto; , mas você pode corrigi-lo aplicando
padding-left
epadding-right
em vez domargin-left: auto;
,margin-right: auto;
e uma largura declarada. O preenchimento basicamente permite que os elementos transbordem a caixa de conteúdo e entrem na caixa de preenchimento sem deixar o modelo de caixa como um todo e ser cortado.Além disso, lembre-se de que você pode introduzir algum CLS quando o novo conteúdo for renderizado, portanto, é uma boa ideia usar
contain-intrinsic-size
com um espaço reservado de tamanho adequado ( obrigado, Una! ).Thijs Terluin tem muito mais detalhes sobre ambas as propriedades e como
contain-intrinsic-size
é calculado pelo navegador, Malte Ubl mostra como você pode calculá-lo e um breve vídeo explicativo de Jake e Surma explica como tudo funciona.E se você precisar ficar um pouco mais granular, com CSS Containment, você pode pular manualmente layout, estilo e trabalho de pintura para descendentes de um nó DOM se você precisar apenas de tamanho, alinhamento ou estilos computados em outros elementos - ou o elemento está atualmente fora da tela.
- Você adia a decodificação com
decoding="async"
?
Às vezes, o conteúdo aparece fora da tela, mas queremos garantir que ele esteja disponível quando os clientes precisarem — idealmente, não bloqueando nada no caminho crítico, mas decodificando e renderizando de forma assíncrona. Podemos usardecoding="async"
para dar ao navegador permissão para decodificar a imagem do thread principal, evitando o impacto do usuário do tempo de CPU usado para decodificar a imagem (via Malte Ubl):<img decoding="async" … />
Alternativamente, para imagens fora da tela, podemos exibir primeiro um espaço reservado e, quando a imagem estiver dentro da janela de visualização, usando IntersectionObserver, acionar uma chamada de rede para que a imagem seja baixada em segundo plano. Além disso, podemos adiar a renderização até a decodificação com img.decode() ou baixar a imagem se a API de decodificação de imagem não estiver disponível.
Ao renderizar a imagem, podemos usar animações de fade-in, por exemplo. Katie Hempenius e Addy Osmani compartilham mais insights em sua palestra Speed at Scale: Web Performance Tips and Tricks from the Trenches.
- Você gera e serve CSS crítico?
Para garantir que os navegadores comecem a renderizar sua página o mais rápido possível, tornou-se uma prática comum coletar todo o CSS necessário para começar a renderizar a primeira parte visível da página (conhecida como "CSS crítico" ou "CSS acima da dobra ") e incluí-lo inline no<head>
da página, reduzindo assim as viagens de ida e volta. Devido ao tamanho limitado dos pacotes trocados durante a fase de início lento, seu orçamento para CSS crítico é de cerca de 14 KB.Se você for além disso, o navegador precisará de viagens de ida e volta adicionais para buscar mais estilos. CriticalCSS e Critical permitem que você produza CSS crítico para cada modelo que estiver usando. Em nossa experiência, porém, nenhum sistema automático foi melhor do que a coleta manual de CSS crítico para cada modelo e, de fato, essa é a abordagem para a qual voltamos recentemente.
Você pode então inline CSS crítico e carregar lentamente o resto com o plugin critters Webpack. Se possível, considere usar a abordagem de inlining condicional usada pelo Filament Group ou converta o código inline em ativos estáticos em tempo real.
Se você atualmente carrega seu CSS completo de forma assíncrona com bibliotecas como loadCSS, isso não é realmente necessário. Com
media="print"
, você pode enganar o navegador para buscar o CSS de forma assíncrona, mas aplicar ao ambiente da tela assim que ele for carregado. ( obrigado, Scott! )<!-- Via Scott Jehl. https://www.filamentgroup.com/lab/load-css-simpler/ --> <!-- Load CSS asynchronously, with low priority --> <link rel="stylesheet" href="full.css" media="print" onload="this.media='all'" />
Ao coletar todo o CSS crítico para cada modelo, é comum explorar apenas a área "acima da dobra". No entanto, para layouts complexos, pode ser uma boa ideia incluir a base do layout também para evitar custos massivos de recálculo e repintura, prejudicando sua pontuação no Core Web Vitals como resultado.
E se um usuário obtiver um URL que está direcionando diretamente para o meio da página, mas o CSS ainda não foi baixado? Nesse caso, tornou-se comum ocultar conteúdo não crítico, por exemplo, com
opacity: 0;
em CSS embutido eopacity: 1
em arquivo CSS completo e exibi-lo quando CSS estiver disponível. No entanto, ele tem uma grande desvantagem , pois os usuários em conexões lentas podem nunca conseguir ler o conteúdo da página. É por isso que é melhor sempre manter o conteúdo visível, mesmo que não esteja estilizado corretamente.Colocar CSS crítico (e outros ativos importantes) em um arquivo separado no domínio raiz traz benefícios, às vezes até mais do que inlining, devido ao cache. O Chrome abre especulativamente uma segunda conexão HTTP com o domínio raiz ao solicitar a página, o que elimina a necessidade de uma conexão TCP para buscar esse CSS. Isso significa que você pode criar um conjunto de arquivos -CSS críticos (por exemplo, critical-homepage.css , critical-product-page.css etc.) e servi-los de sua raiz, sem ter que inline-los. ( obrigado, Felipe! )
Uma palavra de cautela: com HTTP/2, CSS crítico pode ser armazenado em um arquivo CSS separado e entregue por meio de um servidor push sem sobrecarregar o HTML. O problema é que o push do servidor foi problemático com muitas pegadinhas e condições de corrida nos navegadores. Ele nunca foi suportado de forma consistente e teve alguns problemas de cache (veja o slide 114 em diante da apresentação de Hooman Beheshti).
O efeito pode, de fato, ser negativo e sobrecarregar os buffers da rede, impedindo que quadros genuínos no documento sejam entregues. Portanto, não foi muito surpreendente que, por enquanto, o Chrome esteja planejando remover o suporte ao Server Push.
- Experimente reagrupar suas regras CSS.
Estamos acostumados com CSS crítico, mas existem algumas otimizações que podem ir além disso. Harry Roberts conduziu uma pesquisa notável com resultados bastante surpreendentes. Por exemplo, pode ser uma boa ideia dividir o arquivo CSS principal em suas consultas de mídia individuais. Dessa forma, o navegador recuperará CSS crítico com alta prioridade e todo o resto com baixa prioridade - completamente fora do caminho crítico.Além disso, evite colocar
<link rel="stylesheet" />
antes de trechosasync
. Se os scripts não dependerem de folhas de estilo, considere colocar scripts de bloqueio acima dos estilos de bloqueio. Se o fizerem, divida esse JavaScript em dois e carregue-o em ambos os lados do seu CSS.Scott Jehl resolveu outro problema interessante armazenando em cache um arquivo CSS embutido com um service worker, um problema comum familiar se você estiver usando CSS crítico. Basicamente, adicionamos um atributo de ID ao elemento de
style
para que seja fácil encontrá-lo usando JavaScript, então um pequeno pedaço de JavaScript encontra esse CSS e usa a API de cache para armazená-lo em um cache de navegador local (com um tipo de conteúdo detext/css
) para uso nas páginas subsequentes. Para evitar o inline nas páginas subsequentes e, em vez disso, referenciar os ativos armazenados em cache externamente, definimos um cookie na primeira visita a um site. Voilá!Vale a pena notar que o estilo dinâmico também pode ser caro, mas geralmente apenas nos casos em que você confia em centenas de componentes compostos renderizados simultaneamente. Portanto, se você estiver usando CSS-in-JS, certifique-se de que sua biblioteca CSS-in-JS otimize a execução quando seu CSS não tiver dependências de tema ou adereços e não sobreponha componentes estilizados . Aggelos Arvanitakis compartilha mais insights sobre os custos de desempenho do CSS-in-JS.
- Você transmite respostas?
Frequentemente esquecidos e negligenciados, os fluxos fornecem uma interface para leitura ou gravação de blocos de dados assíncronos, dos quais apenas um subconjunto pode estar disponível na memória a qualquer momento. Basicamente, eles permitem que a página que fez a solicitação original comece a trabalhar com a resposta assim que o primeiro bloco de dados estiver disponível e use analisadores otimizados para streaming para exibir progressivamente o conteúdo.Poderíamos criar um fluxo de várias fontes. Por exemplo, em vez de servir um shell de interface do usuário vazio e permitir que o JavaScript o preencha, você pode permitir que o service worker construa um fluxo em que o shell vem de um cache, mas o corpo vem da rede. Como Jeff Posnick observou, se o seu aplicativo da web é alimentado por um CMS que renderiza HTML pelo servidor juntando modelos parciais, esse modelo se traduz diretamente no uso de respostas de streaming, com a lógica de modelagem replicada no service worker em vez de no servidor. O artigo The Year of Web Streams de Jake Archibald destaca como exatamente você pode construí-lo. O aumento de desempenho é bastante perceptível.
Uma vantagem importante de transmitir toda a resposta HTML é que o HTML renderizado durante a solicitação de navegação inicial pode aproveitar ao máximo o analisador HTML de streaming do navegador. Pedaços de HTML que são inseridos em um documento após o carregamento da página (como é comum com conteúdo preenchido via JavaScript) não podem aproveitar essa otimização.
Suporte ao navegador? Ainda chegando lá com suporte parcial no Chrome, Firefox, Safari e Edge suportando a API e Service Workers sendo suportados em todos os navegadores modernos. E se você se sentir aventureiro novamente, pode verificar uma implementação experimental de solicitações de streaming, que permite começar a enviar a solicitação enquanto ainda gera o corpo. Disponível no Chrome 85.
- Considere tornar seus componentes com reconhecimento de conexão.
Os dados podem ser caros e com carga útil crescente, precisamos respeitar os usuários que optam por optar pela economia de dados ao acessar nossos sites ou aplicativos. O cabeçalho de solicitação de dica do cliente Save-Data nos permite personalizar o aplicativo e a carga útil para usuários com restrições de custo e desempenho.Na verdade, você pode reescrever solicitações de imagens de alto DPI para imagens de baixo DPI, remover fontes da Web, efeitos de paralaxe sofisticados, miniaturas de visualização e rolagem infinita, desativar a reprodução automática de vídeo, envios de servidor, reduzir o número de itens exibidos e diminuir a qualidade da imagem ou até mesmo alterar a forma como você entrega marcação. Tim Vereecke publicou um artigo muito detalhado sobre estratégias de data-s(h)aver apresentando muitas opções para economia de dados.
Quem está usando
save-data
, você pode estar se perguntando? 18% dos usuários globais do Android Chrome têm o Modo Lite ativado (comSave-Data
ativado) e o número provavelmente será maior. De acordo com a pesquisa de Simon Hearne, a taxa de adesão é mais alta em dispositivos mais baratos, mas há muitos valores discrepantes. Por exemplo: os usuários no Canadá têm uma taxa de adesão de mais de 34% (em comparação com ~7% nos EUA) e os usuários do mais recente carro-chefe da Samsung têm uma taxa de adesão de quase 18% globalmente.Com o modo
Save-Data
ativado, o Chrome Mobile fornecerá uma experiência otimizada, ou seja, uma experiência da Web com proxy com scripts adiados ,font-display: swap
e carregamento lento forçado. É mais sensato construir a experiência por conta própria, em vez de depender do navegador para fazer essas otimizações.Atualmente, o cabeçalho é suportado apenas no Chromium, na versão Android do Chrome ou por meio da extensão Data Saver em um dispositivo desktop. Por fim, você também pode usar a API de informações de rede para fornecer módulos JavaScript caros, imagens e vídeos de alta resolução com base no tipo de rede. A API de informações de rede e especificamente
navigator.connection.effectiveType
usam valoresRTT
,downlink
,effectiveType
(e alguns outros) para fornecer uma representação da conexão e dos dados que os usuários podem manipular.Nesse contexto, Max Bock fala de componentes com reconhecimento de conexão e Addy Osmani fala de serviço de módulo adaptável. Por exemplo, com o React, poderíamos escrever um componente que renderiza de forma diferente para diferentes tipos de conexão. Como Max sugeriu, um componente
<Media />
em um artigo de notícias pode gerar:-
Offline
: um espaço reservado com textoalt
, -
2G
/ modosave-data
: uma imagem de baixa resolução, -
3G
em tela não Retina: uma imagem de resolução média, -
3G
em telas Retina: imagem Retina de alta resolução, -
4G
: um vídeo HD.
Dean Hume fornece uma implementação prática de uma lógica semelhante usando um service worker. Para um vídeo, podemos exibir um pôster de vídeo por padrão e, em seguida, exibir o ícone "Reproduzir", bem como o shell do player de vídeo, metadados do vídeo etc. em conexões melhores. Como alternativa para navegadores não compatíveis, poderíamos ouvir o evento
canplaythrough
e usarPromise.race()
para expirar o carregamento da fonte se o eventocanplaythrough
não disparar em 2 segundos.Se você quiser se aprofundar um pouco mais, aqui estão alguns recursos para começar:
- Addy Osmani mostra como implementar a veiculação adaptativa no React.
- React Adaptive Loading Hooks & Utilities fornece trechos de código para React,
- Netanel Basel explora componentes com reconhecimento de conexão em Angular,
- Theodore Vorilas compartilha como funciona Servir Componentes Adaptativos Usando a API de Informações de Rede no Vue.
- Umar Hansa mostra como baixar/executar seletivamente JavaScript caro.
-
- Considere tornar seu dispositivo de componentes com reconhecimento de memória.
A conexão de rede nos dá apenas uma perspectiva no contexto do usuário. Indo além, você também pode ajustar dinamicamente os recursos com base na memória disponível do dispositivo, com a API de memória do dispositivo.navigator.deviceMemory
retorna a quantidade de RAM do dispositivo em gigabytes, arredondado para a potência de dois mais próxima. A API também apresenta um cabeçalho de dicas do cliente,Device-Memory
, que informa o mesmo valor.Bônus : Umar Hansa mostra como adiar scripts caros com importações dinâmicas para alterar a experiência com base na memória do dispositivo, conectividade de rede e simultaneidade de hardware.
- Aqueça a conexão para acelerar a entrega.
Use dicas de recursos para economizar tempo nadns-prefetch
(que executa uma pesquisa de DNS em segundo plano), pré-preconnect
(que solicita ao navegador para iniciar o handshake de conexão (DNS, TCP, TLS) em segundo plano),prefetch
-busca (que solicita ao navegador para solicitar um recurso) epreload
-carregar (que pré-busca recursos sem executá-los, entre outras coisas). Bem suportado em navegadores modernos, com suporte para Firefox em breve.Lembra
prerender
? A dica de recurso usada para solicitar ao navegador que crie a página inteira em segundo plano para a próxima navegação. Os problemas de implementação foram bastante problemáticos, variando de um enorme consumo de memória e uso de largura de banda a vários acessos de análise registrados e impressões de anúncios.Sem surpresa, ele foi preterido, mas a equipe do Chrome o trouxe de volta como mecanismo NoState Prefetch. Na verdade, o Chrome trata a dica de pré-renderização como uma
prerender
Prefetch, então ainda podemos usá-la hoje. Como Katie Hempenius explica nesse artigo, "assim como a pré-renderização, a NoState Prefetch busca recursos antecipadamente ; mas, diferentemente da pré-renderização, ela não executa JavaScript ou renderiza qualquer parte da página antecipadamente".O NoState Prefetch usa apenas ~45MiB de memória e os sub-recursos que são buscados serão buscados com uma prioridade de rede
IDLE
. Desde o Chrome 69, o NoState Prefetch adiciona o cabeçalho Objetivo: Pré -busca a todas as solicitações para torná-las distinguíveis da navegação normal.Além disso, fique atento às alternativas e portais de pré-renderização, um novo esforço em direção à pré-renderização consciente da privacidade, que fornecerá a
preview
inserida do conteúdo para navegações perfeitas.Usar dicas de recursos é provavelmente a maneira mais fácil de aumentar o desempenho e funciona bem. Quando usar o quê? Como Addy Osmani explicou, é razoável pré-carregar recursos que sabemos que provavelmente serão usados na página atual e para futuras navegações em vários limites de navegação, por exemplo, pacotes Webpack necessários para páginas que o usuário ainda não visitou.
O artigo de Addy sobre "Carregar prioridades no Chrome" mostra exatamente como o Chrome interpreta as dicas de recursos, portanto, depois de decidir quais ativos são críticos para renderização, você pode atribuir alta prioridade a eles. Para ver como suas solicitações são priorizadas, você pode ativar uma coluna "prioridade" na tabela de solicitações de rede do Chrome DevTools (assim como o Safari).
Na maioria das vezes, usaremos pelo menos
preconnect
edns-prefetch
, e seremos cautelosos ao usarprefetch
,preload
eprerender
. Observe que, mesmo compreconnect
edns-prefetch
, o navegador tem um limite no número de hosts que ele procurará/conectará em paralelo, então é uma aposta segura ordená-los com base na prioridade ( obrigado Philip Tellis! ).Como as fontes geralmente são recursos importantes em uma página, às vezes é uma boa ideia solicitar ao navegador que baixe fontes críticas com
preload
-carregamento . No entanto, verifique se isso realmente ajuda no desempenho, pois há um quebra-cabeça de prioridades ao pré-carregar fontes: como opreload
-carregamento é visto como de alta importância, ele pode ultrapassar recursos ainda mais críticos, como CSS crítico. ( obrigado, Barry! )<!-- Loading two rendering-critical fonts, but not all their weights. --> <!-- crossorigin="anonymous" is required due to CORS. Without it, preloaded fonts will be ignored. https://github.com/w3c/preload/issues/32 via https://twitter.com/iamakulov/status/1275790151642423303 --> <link rel="preload" as="font" href="Elena-Regular.woff2" type="font/woff2" crossorigin="anonymous" media="only screen and (min-width: 48rem)" /> <link rel="preload" as="font" href="Mija-Bold.woff2" type="font/woff2" crossorigin="anonymous" media="only screen and (min-width: 48rem)" />
<!-- Loading two rendering-critical fonts, but not all their weights. --> <!-- crossorigin="anonymous" is required due to CORS. Without it, preloaded fonts will be ignored. https://github.com/w3c/preload/issues/32 via https://twitter.com/iamakulov/status/1275790151642423303 --> <link rel="preload" as="font" href="Elena-Regular.woff2" type="font/woff2" crossorigin="anonymous" media="only screen and (min-width: 48rem)" /> <link rel="preload" as="font" href="Mija-Bold.woff2" type="font/woff2" crossorigin="anonymous" media="only screen and (min-width: 48rem)" />
Como
<link rel="preload">
aceita um atributo demedia
, você pode optar por baixar recursos seletivamente com base nas regras de consulta@media
, conforme mostrado acima.Além disso, podemos usar os atributos
imagesrcset
eimagesizes
para pré-carregar imagens de herói descobertas tardiamente mais rapidamente, ou qualquer imagem que seja carregada via JavaScript, por exemplo, pôsteres de filmes:<!-- Addy Osmani. https://addyosmani.com/blog/preload-hero-images/ --> <link rel="preload" as="image" href="poster.jpg" image image>
<!-- Addy Osmani. https://addyosmani.com/blog/preload-hero-images/ --> <link rel="preload" as="image" href="poster.jpg" image image>
Também podemos pré-carregar o JSON como fetch , para que seja descoberto antes que o JavaScript o solicite:
<!-- Addy Osmani. https://addyosmani.com/blog/preload-hero-images/ --> <link rel="preload" as="fetch" href="foo.com/api/movies.json" crossorigin>
Também poderíamos carregar JavaScript dinamicamente, efetivamente para execução lenta do script.
/* Adding a preload hint to the head */ var preload = document.createElement("link"); link.href = "myscript.js"; link.rel = "preload"; link.as = "script"; document.head.appendChild(link); /* Injecting a script when we want it to execute */ var script = document.createElement("script"); script.src = "myscript.js"; document.body.appendChild(script);
Algumas pegadinhas a serem lembradas: o
preload
-carregamento é bom para aproximar o tempo de download de um ativo da solicitação inicial, mas os ativos pré-carregados chegam ao cache de memória que está vinculado à página que faz a solicitação.preload
funciona bem com o cache HTTP: uma solicitação de rede nunca é enviada se o item já estiver no cache HTTP.Portanto, é útil para recursos descobertos tardiamente, imagens de herói carregadas por meio
background-image
, CSS crítico embutido (ou JavaScript) e pré-carregamento do restante do CSS (ou JavaScript).Uma tag de
preload
-carregamento pode iniciar um pré-carregamento somente após o navegador ter recebido o HTML do servidor e o analisador lookahead encontrar a tag depreload
-carregamento. O pré-carregamento através do cabeçalho HTTP pode ser um pouco mais rápido, pois não precisamos esperar que o navegador analise o HTML para iniciar a solicitação (embora seja debatido).As dicas iniciais ajudarão ainda mais, permitindo que o pré-carregamento seja ativado antes mesmo que os cabeçalhos de resposta para o HTML sejam enviados (no roteiro no Chromium, Firefox). Além disso, as dicas de prioridade nos ajudarão a indicar as prioridades de carregamento dos scripts.
Cuidado : se você estiver usando
preload
,as
deve ser definido ou nada carrega, mais fontes pré-carregadas sem o atributocrossorigin
serão buscadas duas vezes. Se você estiver usandoprefetch
, cuidado com os problemas do cabeçalhoAge
no Firefox.
- Use service workers para cache e fallbacks de rede.
Nenhuma otimização de desempenho em uma rede pode ser mais rápida do que um cache armazenado localmente na máquina de um usuário (há exceções). Se seu site estiver sendo executado em HTTPS, podemos armazenar em cache ativos estáticos em um cache de service worker e armazenar fallbacks offline (ou mesmo páginas offline) e recuperá-los da máquina do usuário, em vez de ir para a rede.Conforme sugerido por Phil Walton, com os service workers, podemos enviar cargas úteis HTML menores gerando nossas respostas programaticamente. Um service worker pode solicitar apenas o mínimo de dados de que precisa do servidor (por exemplo, uma parcial de conteúdo HTML, um arquivo Markdown, dados JSON etc.) e, em seguida, pode transformar programaticamente esses dados em um documento HTML completo. Assim, quando um usuário visita um site e o service worker é instalado, o usuário nunca mais solicitará uma página HTML completa. O impacto no desempenho pode ser bastante impressionante.
Suporte ao navegador? Os service workers são amplamente suportados e o fallback é a rede de qualquer maneira. Isso ajuda a aumentar o desempenho ? Ah, sim, ele faz. E está ficando melhor, por exemplo, com Background Fetch permitindo uploads/downloads em segundo plano através de um service worker também.
Há vários casos de uso para um service worker. Por exemplo, você pode implementar o recurso "Salvar para offline", lidar com imagens quebradas, introduzir mensagens entre guias ou fornecer diferentes estratégias de armazenamento em cache com base nos tipos de solicitação. Em geral, uma estratégia confiável comum é armazenar o shell do aplicativo no cache do service worker junto com algumas páginas críticas, como página offline, página inicial e qualquer outra coisa que possa ser importante no seu caso.
Existem algumas pegadinhas para manter em mente embora. Com um service worker no local, precisamos tomar cuidado com as solicitações de intervalo no Safari (se você estiver usando o Workbox para um service worker, ele terá um módulo de solicitação de intervalo). Se você já se deparou com
DOMException: Quota exceeded.
erro no console do navegador, então veja o artigo de Gerardo Quando 7KB é igual a 7MB.Como Gerardo escreve: “Se você estiver criando um aplicativo da Web progressivo e estiver experimentando armazenamento em cache inchado quando seu service worker armazena em cache ativos estáticos servidos de CDNs, certifique-se de que o cabeçalho de resposta CORS adequado exista para recursos de origem cruzada, você não armazena em cache respostas opacas com seu service worker inadvertidamente, você aceita ativos de imagem de origem cruzada no modo
crossorigin
adicionando o atributo de origem cruzada à tag<img>
.”Há muitos recursos excelentes para começar a trabalhar com service workers:
- Service Worker Mindset, que ajuda você a entender como os service workers trabalham nos bastidores e o que entender ao construir um.
- Chris Ferdinandi fornece uma grande série de artigos sobre service workers, explicando como criar aplicativos offline e cobrindo uma variedade de cenários, desde salvar páginas visualizadas recentemente offline até definir uma data de expiração para itens em um cache de service worker.
- Armadilhas e práticas recomendadas do Service Worker, com algumas dicas sobre o escopo, atraso no registro de um service worker e no cache do service worker.
- Ótima série de Ire Aderinokun em "Offline First" com Service Worker, com uma estratégia de pré-cache no shell do aplicativo.
- Service Worker: uma introdução com dicas práticas sobre como usar o service worker para experiências offline avançadas, sincronizações periódicas em segundo plano e notificações push.
- Sempre vale a pena consultar o livro de receitas offline do bom e velho Jake Archibald, com várias receitas sobre como preparar seu próprio service worker.
- Workbox é um conjunto de bibliotecas de service worker criadas especificamente para criar aplicativos da Web progressivos.
- Você está executando servidores de trabalho no CDN/Edge, por exemplo, para testes A/B?
Neste ponto, estamos bastante acostumados a executar service workers no cliente, mas com CDNs implementando-os no servidor, podemos usá-los para ajustar o desempenho na borda também.Por exemplo, em testes A/B, quando o HTML precisa variar seu conteúdo para diferentes usuários, podemos usar Service Workers nos servidores CDN para lidar com a lógica. Também podemos transmitir a reescrita de HTML para acelerar sites que usam o Google Fonts.
- Otimize o desempenho de renderização.
Sempre que o aplicativo está lento, é perceptível imediatamente. Portanto, precisamos ter certeza de que não há atraso ao rolar a página ou quando um elemento é animado e que você está atingindo consistentemente 60 quadros por segundo. Se isso não for possível, pelo menos tornar os quadros por segundo consistentes é preferível a um intervalo misto de 60 a 15. Use o CSS'will-change
para informar ao navegador quais elementos e propriedades serão alterados.Sempre que estiver enfrentando, depure repinturas desnecessárias no DevTools:
- Meça o desempenho de renderização em tempo de execução. Confira algumas dicas úteis sobre como entender isso.
- Para começar, verifique o curso gratuito Udacity de Paul Lewis sobre otimização de renderização de navegador e o artigo de Georgy Marchuk sobre pintura de navegador e considerações para desempenho na web.
- Ative o Paint Flashing em "Mais ferramentas → Renderização → Paint Flashing" no Firefox DevTools.
- No React DevTools, marque "Destacar atualizações" e ative "Registrar por que cada componente foi renderizado",
- Você também pode usar Why Did You Render, então quando um componente for renderizado novamente, um flash irá notificá-lo sobre a mudança.
Você está usando um layout de alvenaria? Tenha em mente que pode ser possível construir um layout Masonry apenas com grade CSS, muito em breve.
Se você quiser se aprofundar no tópico, Nolan Lawson compartilhou truques para medir com precisão o desempenho do layout em seu artigo, e Jason Miller também sugeriu técnicas alternativas. Também temos um pequeno artigo de Sergey Chikuyonok sobre como obter a animação GPU certa.
Nota : as alterações nas camadas compostas por GPU são as menos caras, portanto, se você puder se safar acionando apenas a composição via
opacity
etransform
, estará no caminho certo. Anna Migas também forneceu muitos conselhos práticos em sua palestra sobre Depuração do desempenho de renderização da interface do usuário. E para entender como depurar o desempenho da pintura no DevTools, confira o vídeo de auditoria de desempenho da pintura de Umar. - Você otimizou para o desempenho percebido?
Embora a sequência de como os componentes aparecem na página e a estratégia de como fornecemos recursos para o navegador sejam importantes, também não devemos subestimar o papel do desempenho percebido. O conceito lida com aspectos psicológicos da espera, basicamente mantendo os clientes ocupados ou engajados enquanto outra coisa está acontecendo. É aí que entram em jogo a gestão da percepção, o início preventivo, a conclusão antecipada e a gestão da tolerância.O que tudo isso significa? Ao carregar ativos, podemos tentar estar sempre um passo à frente do cliente, para que a experiência pareça rápida enquanto há muita coisa acontecendo em segundo plano. Para manter o cliente engajado, podemos testar telas de esqueleto (demonstração de implementação) ao invés de indicadores de carregamento, adicionar transições/animações e basicamente enganar o UX quando não há mais nada para otimizar.
Em seu estudo de caso sobre The Art of UI Skeletons, Kumar McMillan compartilha algumas ideias e técnicas sobre como simular listas dinâmicas, texto e a tela final, bem como considerar o pensamento de esqueleto com o React.
Cuidado, porém: as telas de esqueleto devem ser testadas antes da implantação, pois alguns testes mostraram que as telas de esqueleto podem ter o pior desempenho em todas as métricas.
- Você evita mudanças de layout e repinturas?
No domínio do desempenho percebido, provavelmente uma das experiências mais disruptivas é a mudança de layout , ou refluxo , causada por imagens e vídeos redimensionados, fontes da Web, anúncios injetados ou scripts descobertos tardiamente que preenchem componentes com conteúdo real. Como resultado, um cliente pode começar a ler um artigo apenas para ser interrompido por um salto de layout acima da área de leitura. A experiência é muitas vezes abrupta e bastante desorientadora: e esse é provavelmente um caso de prioridades de carregamento que precisam ser reconsideradas.A comunidade desenvolveu algumas técnicas e soluções alternativas para evitar refluxos. Em geral, é uma boa ideia evitar a inserção de novo conteúdo acima do conteúdo existente , a menos que isso aconteça em resposta a uma interação do usuário. Sempre defina os atributos de largura e altura nas imagens, para que os navegadores modernos atribuam a caixa e reservem o espaço por padrão (Firefox, Chrome).
Tanto para imagens quanto para vídeos, podemos usar um espaço reservado SVG para reservar a caixa de exibição na qual a mídia aparecerá. Isso significa que a área será reservada adequadamente quando você precisar manter sua proporção também. Também podemos usar marcadores de posição ou imagens substitutas para anúncios e conteúdo dinâmico, bem como pré-alocar espaços de layout.
Em vez de imagens de carregamento lento com scripts externos, considere usar carregamento lento nativo ou carregamento lento híbrido quando carregarmos um script externo somente se o carregamento lento nativo não for suportado.
Como mencionado acima, sempre agrupe repinturas de fontes da Web e faça a transição de todas as fontes de fallback para todas as fontes da Web de uma só vez - apenas certifique-se de que essa mudança não seja muito abrupta, ajustando a altura da linha e o espaçamento entre as fontes com o font-style-matcher .
Para substituir as métricas de fonte de uma fonte substituta para emular uma fonte da Web, podemos usar os descritores @font-face para substituir as métricas de fonte (demo, ativado no Chrome 87). (Observe que os ajustes são complicados com pilhas de fontes complicadas.)
Para CSS tardio, podemos garantir que o CSS de layout crítico seja embutido no cabeçalho de cada modelo. Além disso: para páginas longas, quando a barra de rolagem vertical é adicionada, ela desloca o conteúdo principal 16px para a esquerda. Para exibir uma barra de rolagem antecipadamente, podemos adicionar
overflow-y: scroll
onhtml
para impor uma barra de rolagem na primeira pintura. O último ajuda porque as barras de rolagem podem causar mudanças de layout não triviais devido ao refluxo do conteúdo acima da dobra quando a largura muda. Deve acontecer principalmente em plataformas com barras de rolagem sem sobreposição, como o Windows. Mas: breakposition: sticky
porque esses elementos nunca rolarão para fora do contêiner.Se você lida com cabeçalhos que se tornam fixos ou fixos posicionados no topo da página na rolagem, reserve espaço para o cabeçalho quando ele se tornar pinado, por exemplo, com um elemento de espaço reservado ou
margin-top
no conteúdo. Uma exceção deve ser banners de consentimento de cookies que não devem ter impacto no CLS, mas às vezes eles têm: depende da implementação. Existem algumas estratégias e conclusões interessantes neste tópico do Twitter.Para um componente de guia que pode incluir várias quantidades de textos, você pode evitar mudanças de layout com pilhas de grade CSS. Ao colocar o conteúdo de cada guia na mesma área da grade e ocultar uma delas por vez, podemos garantir que o contêiner sempre tenha a altura do elemento maior, para que não ocorram mudanças de layout.
Ah, e claro, rolagem infinita e "Carregar mais" também podem causar mudanças de layout se houver conteúdo abaixo da lista (por exemplo, rodapé). Para melhorar o CLS, reserve espaço suficiente para o conteúdo que seria carregado antes que o usuário role para essa parte da página, remova o rodapé ou qualquer elemento DOM na parte inferior da página que possa ser empurrado para baixo pelo carregamento do conteúdo. Além disso, pré-buscar dados e imagens para conteúdo abaixo da dobra para que, quando um usuário rolar até esse ponto, ele já esteja lá. Você pode usar bibliotecas de virtualização de listas como react-window para otimizar listas longas também ( obrigado, Addy Osmani! ).
Para garantir que o impacto dos refluxos seja contido, meça a estabilidade do layout com a Layout Instability API. Com ele, você pode calcular a pontuação Cumulative Layout Shift ( CLS ) e incluí-la como requisito em seus testes, assim sempre que uma regressão aparecer, você poderá rastreá-la e corrigi-la.
Para calcular a pontuação de mudança de layout, o navegador analisa o tamanho da janela de visualização e o movimento de elementos instáveis na janela de visualização entre dois quadros renderizados. Idealmente, a pontuação seria próxima de
0
. Há um ótimo guia de Milica Mihajlija e Philip Walton sobre o que é o CLS e como medi-lo. É um bom ponto de partida para medir e manter o desempenho percebido e evitar interrupções, especialmente para tarefas críticas para os negócios.Dica rápida : para descobrir o que causou uma mudança de layout no DevTools, você pode explorar as mudanças de layout em "Experiência" no Painel de desempenho.
Bônus : se você deseja reduzir refluxos e repinturas, consulte o guia de Charis Theodoulou para Minimizing DOM Reflow/Layout Thrashing e a lista de Paul Irish de O que força layout/reflow, bem como CSSTriggers.com, uma tabela de referência sobre propriedades CSS que acionam layout, pintura e composição.
Índice
- Preparação: planejamento e métricas
- Definir metas realistas
- Definindo o Ambiente
- Otimizações de recursos
- Otimizações de compilação
- Otimizações de entrega
- Rede, HTTP/2, HTTP/3
- Teste e monitoramento
- Vitórias rápidas
- Tudo em uma página
- Baixe a lista de verificação (PDF, Apple Pages, MS Word)
- Assine nossa newsletter por e-mail para não perder os próximos guias.