Descubra o que é o verificador de pré-carregamento do navegador, como ele ajuda no desempenho e como você pode evitar isso.
Um aspecto esquecido da otimização da velocidade da página envolve saber um pouco sobre os componentes internos do navegador. Os navegadores fazem determinadas otimizações para melhorar o desempenho de maneiras que nós, desenvolvedores, não podem, mas somente desde que essas otimizações não sejam frustradas involuntariamente.
Uma otimização interna do navegador que você deve entender é o scanner de pré-carregamento do navegador. Esta postagem aborda como o scanner de pré-carregamento funciona e, o mais importante, como você pode evitar obstáculos.
O que é um scanner de pré-carregamento?
Todo navegador tem um analisador HTML primário que tokeniza a marcação bruta e a processa em um modelo de objeto. Tudo isso continua até que o analisador faz uma pausa quando encontra um recurso de bloqueio, como uma folha de estilo carregada com um elemento <link>
ou um script carregado com um elemento <script>
sem um atributo async
ou defer
.
No caso de arquivos CSS, a renderização é bloqueada para evitar um flash de conteúdo sem estilo (FOUC, na sigla em inglês), ou seja, quando a versão sem estilo de uma página pode ser vista rapidamente antes da aplicação de estilos.
O navegador também bloqueia a análise e a renderização da página ao encontrar elementos <script>
sem um atributo defer
ou async
.
A razão para isso é que o navegador não pode saber com certeza se algum script modificará o DOM enquanto o analisador HTML primário ainda está fazendo seu trabalho. Por isso, é uma prática comum carregar o JavaScript no final do documento para que os efeitos do bloqueio de análise e renderização se tornem marginal.
Esses são bons motivos para o navegador precisar bloquear a análise e a renderização. No entanto, o bloqueio de qualquer uma dessas etapas importantes é indesejável, pois elas podem atrasar a descoberta de outros recursos importantes. Felizmente, os navegadores fazem o possível para mitigar esses problemas com um analisador HTML secundário chamado verificação de pré-carregamento.
A função de um verificador de pré-carregamento é especulativa, ou seja, ele examina a marcação bruta para encontrar recursos a serem buscados oportunamente antes que o analisador HTML principal os descubra.
Como saber quando o scanner de pré-carregamento está funcionando
O scanner de pré-carregamento existe devido ao bloqueio da renderização e da análise. Se esses dois problemas de desempenho nunca existissem, o scanner de pré-carregamento não seria muito útil. A chave para descobrir se uma página da Web se beneficia do verificador de pré-carregamento depende desses fenômenos de bloqueio. Para isso, é possível introduzir um atraso artificial nas solicitações para descobrir onde o scanner de pré-carregamento está funcionando.
Considere esta página de texto e imagens básicos com uma folha de estilo como exemplo. Como os arquivos CSS bloqueiam a renderização e a análise, você introduz um atraso artificial de dois segundos na folha de estilo por meio de um serviço de proxy. Esse atraso facilita a visualização na hierarquia da rede em que o verificador de pré-carregamento está funcionando.
Como você pode ver na hierarquia, o scanner de pré-carregamento descobre o elemento <img>
mesmo com a renderização e a análise de documentos bloqueadas. Sem essa otimização, o navegador não pode buscar coisas de forma oportunista durante o período de bloqueio, e mais solicitações de recursos seriam consecutivas em vez de simultâneas.
Agora que você já sabe como o brinquedo funciona, vamos analisar alguns exemplos reais em que o scanner de pré-carregamento pode ser derrotado e o que pode ser feito para corrigir isso.
Scripts async
injetados
Digamos que você tenha um HTML no <head>
que inclua um JavaScript in-line como este:
<script>
const scriptEl = document.createElement('script');
scriptEl.src = '/yall.min.js';
document.head.appendChild(scriptEl);
</script>
Os scripts injetados são async
por padrão. Portanto, quando injetados, eles se comportam como se o atributo async
tivessem sido aplicados a ele. Isso significa que ele será executado o mais rápido possível e não bloqueará a renderização. Parece ideal, certo? No entanto, se você presumir que esse <script>
in-line vem depois de um elemento <link>
que carrega um arquivo CSS externo, terá um resultado abaixo do ideal:
Vamos detalhar o que aconteceu aqui:
- Em 0 segundos, o documento principal é solicitado.
- Em 1,4 segundo, chega o primeiro byte da solicitação de navegação.
- Após 2 segundos, o CSS e a imagem são solicitados.
- Como o analisador está bloqueado ao carregar a folha de estilo, e o JavaScript in-line que injeta o script
async
vem depois da folha de estilo (2,6 segundos), a funcionalidade fornecida pelo script não fica disponível tão cedo.
Isso não é o ideal porque a solicitação do script só ocorre depois que o download da folha de estilo é concluído. Isso atrasa a execução do script o mais rápido possível. Em contrapartida, como o elemento <img>
é detectável na marcação fornecida pelo servidor, ele é descoberto pelo scanner de pré-carregamento.
Então, o que acontece se você usar uma tag <script>
normal com o atributo async
em vez de injetar o script no DOM?
<script src="/yall.min.js" async></script>
Este é o resultado:
Pode haver a tentação de sugerir que esses problemas podem ser corrigidos usando o rel=preload
. Isso certamente funciona, mas pode ter alguns efeitos colaterais. Afinal, por que usar rel=preload
para corrigir um problema que pode ser evitado não injetando um elemento <script>
no DOM?
Pré-carregamento de "correções" o problema aqui, mas ele introduz um novo problema: o script async
nas duas primeiras demonstrações, mesmo sendo carregado no <head>
, são carregados em "Baixa". prioridade, enquanto a folha de estilo é carregada na prioridade prioridade. Na última demonstração, em que o script async
é carregado previamente, a folha de estilos ainda é carregada com a prioridade "Mais alta", mas a prioridade do script foi promovida para "Alta".
Quando a prioridade de um recurso aumenta, o navegador aloca mais largura de banda a ele. Isso significa que, mesmo que a folha de estilo tenha a maior prioridade, a prioridade elevada do script pode causar contenção de largura de banda. Isso pode ser um fator em conexões lentas ou em casos em que os recursos são muito grandes.
A resposta é direta: se um script for necessário durante a inicialização, não invalide o scanner de pré-carregamento injetando-o no DOM. Faça experimentos conforme necessário com o posicionamento de elementos <script>
e com atributos como defer
e async
.
Carregamento lento com JavaScript
O carregamento lento é um ótimo método de conservação de dados e é geralmente aplicado a imagens. No entanto, às vezes, o carregamento lento é aplicado incorretamente a imagens que estão "acima da dobra", por assim dizer.
Isso apresenta possíveis problemas com a detecção de recursos no que diz respeito ao scanner de pré-carregamento e pode atrasar desnecessariamente o tempo necessário para descobrir uma referência a uma imagem, fazer o download dela, decodificá-la e apresentá-la. Vamos conferir este exemplo de marcação de imagem:
<img data-src="/sand-wasp.jpg" alt="Sand Wasp" width="384" height="255">
O uso de um prefixo data-
é um padrão comum em carregadores lentos com tecnologia JavaScript. Quando a imagem é rolada para a janela de visualização, o carregador lento remove o prefixo data-
, o que significa que, no exemplo anterior, data-src
se torna src
. Essa atualização solicita que o navegador busque o recurso.
Esse padrão não é problemático até ser aplicado a imagens que estão na janela de visualização durante a inicialização. Como o verificador de pré-carregamento não lê o atributo data-src
da mesma maneira que faria com um atributo src
(ou srcset
), a referência da imagem não é descoberta antes. Pior ainda, o carregamento da imagem é atrasado depois do download, compilação e execução do JavaScript do carregador lento.
Dependendo do tamanho da imagem, que depende do tamanho da janela de visualização, ela pode ser um elemento candidato à Largest Contentful Paint (LCP). Quando o scanner de pré-carregamento não consegue buscar especulativamente o recurso de imagem antecipadamente, possivelmente durante o ponto em que a folha de estilo da página é renderizada, a LCP é prejudicada.
A solução é mudar a marcação da imagem:
<img src="/sand-wasp.jpg" alt="Sand Wasp" width="384" height="255">
Esse é o padrão ideal para imagens que estão na janela de visualização durante a inicialização, porque o scanner de pré-carregamento descobrirá e buscará o recurso de imagem mais rapidamente.
O resultado neste exemplo simplificado é uma melhoria de 100 milissegundos na LCP em uma conexão lenta. Isso pode não parecer uma grande melhoria, mas é quando você considera que a solução é uma correção rápida de marcação e que a maioria das páginas da Web é mais complexa do que este conjunto de exemplos. Isso significa que os candidatos ao LCP podem ter que competir pela largura de banda com muitos outros recursos. Por isso, otimizações como essa se tornam cada vez mais importantes.
Imagens de plano de fundo CSS
Lembre-se de que o verificador de pré-carregamento do navegador analisa a marcação. Ele não verifica outros tipos de recursos, como CSS, que podem envolver buscas de imagens referenciadas pela propriedade background-image
.
Assim como o HTML, os navegadores processam CSS em um modelo de objeto próprio, conhecido como CSSOM. Se recursos externos forem descobertos durante a criação do CSSOM, esses recursos serão solicitados no momento da descoberta, e não pelo verificador de pré-carregamento.
Digamos que o candidato à LCP da sua página seja um elemento com uma propriedade CSS background-image
. Veja o que acontece conforme os recursos são carregados:
Nesse caso, o scanner de pré-carregamento não é derrotado, mas não está envolvido. Mesmo assim, se um candidato ao LCP na página for de uma propriedade CSS background-image
, você vai querer pré-carregar essa imagem:
<!-- Make sure this is in the <head> below any
stylesheets, so as not to block them from loading -->
<link rel="preload" as="image" href="lcp-image.jpg">
A dica rel=preload
é pequena, mas ajuda o navegador a descobrir a imagem mais cedo do que faria de outra forma:
Com a sugestão rel=preload
, o candidato do LCP é descoberto mais cedo, reduzindo o tempo do LCP. Essa dica ajuda a corrigir o problema, mas a melhor opção pode ser avaliar se o candidato de LCP da imagem precisa ser carregado do CSS. Com uma tag <img>
, você terá mais controle sobre o carregamento de uma imagem adequada para a janela de visualização e, ao mesmo tempo, permitirá que o leitor de pré-carregamento a descubra.
Muitos recursos em linha
A inserção in-line é uma prática que coloca um recurso dentro do HTML. É possível inserir folhas de estilo em elementos <style>
, scripts em elementos <script>
e praticamente qualquer outro recurso usando a codificação base64.
Colocar recursos inline pode ser mais rápido do que fazer o download deles porque uma solicitação separada não é emitida para o recurso. Ele é incluído no documento e é carregado instantaneamente. No entanto, existem desvantagens significativas:
- Se você não estiver armazenando em cache seu HTML – e simplesmente não conseguir se a resposta HTML for dinâmica – os recursos embutidos nunca serão armazenados em cache. Isso afeta o desempenho porque os recursos em linha não são reutilizáveis.
- Mesmo que seja possível armazenar HTML em cache, os recursos inline não são compartilhados entre os documentos. Isso reduz a eficiência do armazenamento em cache em comparação com arquivos externos que podem ser armazenados em cache e reutilizados em toda a origem.
- Se você ficar muito alinhado, vai atrasar a descoberta de recursos pelo scanner de pré-carregamento no documento, porque o download desse conteúdo extra inline demora mais.
Veja esta página como exemplo. Em determinadas condições, o candidato à LCP é a imagem na parte de cima da página, e o CSS está em um arquivo separado carregado por um elemento <link>
. A página também usa quatro fontes da Web que são solicitadas como arquivos separados do recurso CSS.
O que acontece se o CSS e todas as fontes estiverem inline como recursos base64?
O impacto do inline gera consequências negativas para o LCP nesse exemplo e para a performance em geral. A versão da página sem nada inline mostra a imagem da LCP em cerca de 3,5 segundos. A página que está inline não mostra a imagem da LCP até pouco mais de sete segundos.
Há muito mais a fazer aqui do que apenas o scanner de pré-carregamento. Alinhar fontes não é uma ótima estratégia, porque base64 é um formato ineficiente para recursos binários. Outro fator em questão é que o download de recursos de fontes externas não é feito, a menos que seja determinado como necessário pelo CSSOM. Quando essas fontes são inline como base64, o download delas é feito, sejam elas necessárias para a página atual ou não.
Um pré-carregamento poderia melhorar as coisas aqui? Claro. É possível pré-carregar a imagem da LCP e reduzir o tempo dela, mas a sobrecarga do HTML potencialmente não armazenável em cache com recursos inline tem outras consequências negativas no desempenho. A First Contentful Paint (FCP, na sigla em inglês) também é afetada por esse padrão. Na versão da página em que nada está embutido, a FCP é de aproximadamente 2,7 segundos. Na versão em linha, a FCP é de aproximadamente 5,8 segundos.
Tenha cuidado ao realizar a inserção in-line de coisas no HTML, especialmente em recursos codificados em base64. Em geral, isso não é recomendado, exceto para recursos muito pequenos. O mínimo possível inline, porque isso é brincar com o fogo.
Renderização da marcação com JavaScript do lado do cliente
Não há dúvidas: o JavaScript definitivamente afeta a velocidade da página. Não só os desenvolvedores dependem dele para oferecer interatividade, mas também há uma tendência de fazer isso para entregar o conteúdo por conta própria. De certa forma, isso melhora a experiência do desenvolvedor. mas os benefícios para os desenvolvedores nem sempre se traduzem em benefícios para os usuários.
Um padrão que pode derrotar o scanner de pré-carregamento é renderizar marcação com JavaScript do lado do cliente:
Quando os payloads de marcação são contidos e renderizados inteiramente pelo JavaScript no navegador, todos os recursos dessa marcação ficam efetivamente invisíveis para o scanner de pré-carregamento. Isso atrasa a descoberta de recursos importantes, o que certamente afeta a LCP. No caso desses exemplos, a solicitação da imagem do LCP é significativamente atrasada em comparação com a experiência equivalente renderizada pelo servidor que não exige JavaScript para aparecer.
Isso muda um pouco do foco deste artigo, mas os efeitos da marcação de renderização no cliente vão muito além de derrotar o scanner de pré-carregamento. Por um lado, a introdução de JavaScript para potencializar uma experiência que não exige, apresenta um tempo de processamento desnecessário que pode afetar a Interaction to Next Paint (INP). Renderizar quantidades muito grandes de marcação no cliente tem maior probabilidade de gerar tarefas longas em comparação com a mesma quantidade de marcação enviada pelo servidor. A razão para isso, além do processamento extra que o JavaScript envolve, é que os navegadores transmitem marcações do servidor e dividem a renderização de forma que tende a limitar tarefas longas. A marcação renderizada pelo cliente, por outro lado, é tratada como uma tarefa única e monolítica, que pode afetar o INP de uma página.
A solução para esse cenário depende da resposta a esta pergunta: Há um motivo para a marcação da sua página não poder ser fornecida pelo servidor em vez de ser renderizada no cliente? Se a resposta for "não", a renderização pelo servidor (SSR, na sigla em inglês) ou a marcação gerada estaticamente deve ser considerada sempre que possível, porque isso ajudará o scanner de pré-carregamento a descobrir e buscar recursos importantes com antecedência.
Se a página precisa do JavaScript para anexar a funcionalidade a algumas partes da marcação de página, é possível fazer isso com a SSR, usando JavaScript básico ou hidratação para aproveitar o melhor dos dois mundos.
Ajudar o leitor de pré-carregamento a ajudar você
A verificação de pré-carregamento é uma otimização de navegador altamente eficaz que ajuda a carregar as páginas mais rapidamente durante a inicialização. Ao evitar padrões que invalidam a capacidade de descobrir recursos importantes com antecedência, você não só simplifica o desenvolvimento, como cria experiências de usuário melhores que geram resultados melhores em várias métricas, incluindo algumas Web Vitals.
Para recapitular, veja alguns pontos importantes desta postagem:
- O scanner de pré-carregamento do navegador é um analisador de HTML secundário que verifica antes do principal se ele estiver bloqueado para descobrir de forma oportunista os recursos que podem ser buscados mais cedo.
- Os recursos que não estiverem presentes na marcação fornecida pelo servidor na solicitação de navegação inicial não podem ser descobertos pelo scanner de pré-carregamento. As formas de impedir o verificador de pré-carregamento podem incluir, entre outras coisas:
- Injetando recursos no DOM com JavaScript, sejam scripts, imagens, folhas de estilo ou qualquer outra coisa que seria melhor para o payload de marcação inicial do servidor.
- Carregamento lento de imagens ou iframes acima da dobra usando uma solução JavaScript.
- Renderização da marcação no cliente que pode conter referências a sub-recursos de documentos usando JavaScript.
- O scanner de pré-carregamento só verifica HTML. Ele não examina o conteúdo de outros recursos, principalmente do CSS, que podem incluir referências a recursos importantes, incluindo candidatos à LCP.
Se, por algum motivo, você não conseguir evitar um padrão que afete negativamente a capacidade do scanner de pré-carregamento de acelerar o desempenho de carregamento, considere a dica de recurso rel=preload
. Se você usar rel=preload
, teste em ferramentas de laboratório para garantir que ele está produzindo o efeito desejado. Por fim, não pré-carregue muitos recursos, porque quando você priorizar tudo, nada será.
Recursos
- Scripts assíncronos injetados por script considerado nocivo
- Como o pré-carregador do navegador torna o carregamento das páginas mais rápido
- Pré-carregue recursos essenciais para melhorar a velocidade de carregamento
- Estabelecer conexões de rede antecipadamente para melhorar a velocidade percebida da página
- Como otimizar a Largest Contentful Paint
Imagem principal do Unsplash, por Mohammad Rahmani .