Otimizar o carregamento de recursos

No módulo anterior, vimos alguma teoria por trás do caminho crítico de renderização e como os recursos de bloqueio de renderização e de análise podem atrasar a renderização inicial de uma página. Agora que você entende parte da teoria por trás disso, está pronto para aprender algumas técnicas para otimizar o caminho crítico de renderização.

À medida que uma página é carregada, muitos recursos são referenciados no HTML que fornecem a página com aparência e layout usando CSS, bem como interatividade usando JavaScript. Neste módulo, são abordados vários conceitos importantes relacionados a esses recursos e como eles afetam o tempo de carregamento de uma página.

Bloqueio da renderização

Conforme discutido no módulo anterior, o CSS é um recurso de bloqueio de renderização, porque impede que o navegador renderize conteúdo até que o modelo de objeto CSS (CSSOM, na sigla em inglês) seja criado. O navegador bloqueia a renderização para evitar um Flash de conteúdo sem estilo (FOUC, na sigla em inglês), o que é indesejável do ponto de vista da experiência do usuário.

No vídeo anterior, há um breve FOUC em que você pode ver a página sem nenhum estilo. Em seguida, todos os estilos serão aplicados quando o CSS da página terminar de carregar na rede, e a versão sem estilo da página será imediatamente substituída pela versão com estilo.

De modo geral, um FOUC é algo que você normalmente não vê, mas o conceito é importante para entender por que o navegador bloqueia a renderização da página até que o CSS seja transferido por download e aplicado à página. O bloqueio da renderização não é necessariamente indesejável, mas é importante minimizar o tempo de duração mantendo seu CSS otimizado.

Bloqueio do analisador

Um recurso de bloqueio do analisador interrompe o analisador de HTML, como um elemento <script> sem atributos async ou defer. Quando o analisador encontra um elemento <script>, o navegador precisa avaliar e executar o script antes de prosseguir com a análise do restante do HTML. Isso ocorre por padrão, já que os scripts podem modificar ou acessar o DOM durante um período enquanto ele ainda está sendo criado.

<!-- This is a parser-blocking script: -->
<script src="/script.js"></script>

Ao usar arquivos JavaScript externos (sem async ou defer), o analisador é bloqueado do momento em que o arquivo é descoberto até que ele seja transferido por download, analisado e executado. Ao usar o JavaScript inline, o analisador é bloqueado de forma semelhante até que o script in-line seja analisado e executado.

O scanner de pré-carregamento

O scanner de pré-carregamento é uma otimização de navegador na forma de um analisador de HTML secundário que verifica a resposta HTML bruta para encontrar e buscar recursos de modo especulativo antes que o analisador HTML principal os descubra. Por exemplo, o verificador de pré-carregamento permite que o navegador inicie o download de um recurso especificado em um elemento <img>, mesmo quando o analisador de HTML é bloqueado ao buscar e processar recursos como CSS e JavaScript.

Para aproveitar o verificador de pré-carregamento, os recursos essenciais precisam ser incluídos na marcação HTML enviada pelo servidor. Os seguintes padrões de carregamento de recursos não são detectáveis pelo verificador de pré-carregamento:

  • Imagens carregadas pelo CSS usando a propriedade background-image. Essas referências de imagem estão em CSS e não podem ser descobertas pelo scanner de pré-carregamento.
  • Scripts carregados dinamicamente na forma de marcação de elemento <script> injetada no DOM usando JavaScript ou módulos carregados usando import() dinâmico.
  • HTML renderizado no cliente usando JavaScript. Essa marcação fica contida em strings em recursos JavaScript e não é detectável pelo verificador de pré-carregamento.
  • Declarações @import CSS.

Esses padrões de carregamento de recursos são todos recursos descobertos tardiamente e, portanto, não se beneficiam do verificador de pré-carregamento. Evite-as sempre que possível. No entanto, se não for possível evitar esses padrões, use uma dica preload para evitar atrasos na descoberta de recursos.

CSS

O CSS determina a apresentação e o layout de uma página. Conforme descrito anteriormente, o CSS é um recurso de bloqueio de renderização. Portanto, otimizar seu CSS pode ter um impacto considerável no tempo geral de carregamento da página.

Minificação

Minimizar arquivos CSS reduz o tamanho do arquivo de um recurso CSS, tornando o download mais rápido. Isso é feito principalmente removendo o conteúdo de um arquivo CSS de origem, como espaços e outros caracteres invisíveis, e enviando o resultado para um arquivo recém-otimizado:

/* Unminified CSS: */

/* Heading 1 */
h1 {
  font-size: 2em;
  color: #000000;
}

/* Heading 2 */
h2 {
  font-size: 1.5em;
  color: #000000;
}
/* Minified CSS: */
h1,h2{color:#000}h1{font-size:2em}h2{font-size:1.5em}

Na forma mais básica, a minificação do CSS é uma otimização eficaz que pode melhorar a FCP do seu site e talvez até mesmo a LCP em alguns casos. Ferramentas como bundlers podem executar essa otimização automaticamente em builds de produção.

Remover CSS não utilizado

Antes de renderizar qualquer conteúdo, o navegador precisa fazer o download e analisar todas as folhas de estilo. O tempo necessário para concluir a análise também inclui estilos que não são usados na página atual. Se você usa um bundler que combina todos os recursos CSS em um único arquivo, seus usuários provavelmente estão fazendo o download de mais CSS do que o necessário para renderizar a página atual.

Para descobrir CSS não utilizado na página atual, use a Ferramenta de cobertura no Chrome DevTools.

Uma captura de tela da ferramenta de cobertura no Chrome DevTools. Um arquivo CSS está selecionado no painel inferior, mostrando uma quantidade considerável de CSS não utilizado pelo layout da página atual.
A ferramenta de cobertura no Chrome DevTools é útil para detectar CSS (e JavaScript) não utilizados pela página atual. Ele pode ser usado para dividir arquivos CSS em vários recursos para serem carregados por páginas diferentes, em vez de enviar um pacote CSS muito maior, que pode atrasar a renderização da página.

A remoção do CSS não utilizado tem um efeito duplo: além de reduzir o tempo de download, você está otimizando a construção da árvore de renderização, porque o navegador precisa processar menos regras de CSS.

Evitar declarações @import CSS

Pode parecer conveniente, mas evite declarações @import no CSS:

/* Don't do this: */
@import url('style.css');

Da mesma forma que o elemento <link> funciona no HTML, a declaração @import no CSS permite importar um recurso CSS externo de uma folha de estilos. A principal diferença entre essas duas abordagens é que o elemento HTML <link> faz parte da resposta HTML e, portanto, é descoberto muito antes do download de um arquivo CSS por uma declaração @import.

Isso ocorre porque, para que uma declaração @import seja descoberta, é necessário fazer o download primeiro do arquivo CSS que a contém. Isso resulta no que é conhecido como cadeia de solicitações que, no caso do CSS, atrasa o tempo necessário para uma página ser renderizada inicialmente. Outra desvantagem é que as planilhas de estilo carregadas usando uma declaração @import não podem ser descobertas pelo verificador de pré-carregamento e, portanto, se tornam recursos de bloqueio de renderização descobertos tardiamente.

<!-- Do this instead: -->
<link rel="stylesheet" href="style.css">

Na maioria dos casos, é possível substituir o @import usando um elemento <link rel="stylesheet">. Os elementos <link> permitem o download das folhas de estilo simultaneamente e reduz o tempo total de carregamento, diferente das declarações @import, que fazem o download delas consecutivamente.

CSS essencial inline

O tempo necessário para fazer o download dos arquivos CSS pode aumentar a FCP da página. A inserção de estilos críticos no documento <head> elimina a solicitação de rede para um recurso CSS e, quando feita corretamente, pode melhorar os tempos de carregamento inicial quando o cache do navegador do usuário não está preparado. O CSS restante pode ser carregado de forma assíncrona ou anexado ao final do elemento <body>.

<head>
  <title>Page Title</title>
  <!-- ... -->
  <style>h1,h2{color:#000}h1{font-size:2em}h2{font-size:1.5em}</style>
</head>
<body>
  <!-- Other page markup... -->
  <link rel="stylesheet" href="non-critical.css">
</body>

A desvantagem é que uma grande quantidade de CSS em linha adiciona mais bytes à resposta HTML inicial. Como os recursos HTML geralmente não podem ser armazenados em cache por muito tempo (ou muito), isso significa que o CSS in-line não é armazenado em cache para páginas subsequentes que podem usar o mesmo CSS em folhas de estilo externas. Teste e meça o desempenho da sua página para garantir que as compensações valem o esforço.

Demonstrações de CSS

JavaScript

O JavaScript impulsiona a maior parte da interatividade na Web, mas tem um custo. O envio de muito JavaScript pode deixar a resposta da página da Web mais lenta durante o carregamento e até mesmo causar problemas de capacidade de resposta que atrasam as interações. Ambos os fatores podem ser frustrantes para os usuários.

JavaScript de bloqueio de renderização

Ao carregar elementos <script> sem os atributos defer ou async, o navegador bloqueia a análise e a renderização até que o script seja transferido por download, analisado e executado. Da mesma forma, os scripts in-line bloqueiam o analisador até que o script seja analisado e executado.

async x defer

async e defer permitem que scripts externos sejam carregados sem bloquear o analisador de HTML, enquanto os scripts (incluindo scripts in-line) com type="module" são adiados automaticamente. No entanto, async e defer têm algumas diferenças importantes de entender.

Uma representação de vários mecanismos de carregamento de script, todos detalhando as funções de analisador, busca e execução com base em vários atributos usados, como async, adiar, type=&#39;module&#39; e uma combinação dos três.
Fonte: https://html.spec.whatwg.org/multipage/scripting.html

Os scripts carregados com async são analisados e executados imediatamente após o download, enquanto os scripts carregados com defer são executados quando a análise do documento HTML é concluída. Isso ocorre ao mesmo tempo que o evento DOMContentLoaded do navegador. Além disso, os scripts async podem ser executados fora de ordem, enquanto os scripts defer são executados na ordem em que aparecem na marcação.

Renderização do lado do cliente

De modo geral, evite usar o JavaScript para renderizar qualquer conteúdo crítico ou o elemento LCP de uma página. Isso é conhecido como renderização no lado do cliente, e é uma técnica muito usada em aplicativos de página única (SPAs).

A marcação renderizada por JavaScript evita o verificador de pré-carregamento, já que os recursos contidos na marcação renderizada pelo cliente não são detectáveis por ela. Isso pode atrasar o download de recursos cruciais, como uma imagem LCP. O navegador só começa a fazer o download da imagem LCP após a execução do script e a adição do elemento ao DOM. Por sua vez, o script só pode ser executado depois de ser descoberto, transferido por download e analisado. Isso é conhecido como cadeia de solicitação crítica e precisa ser evitado.

Além disso, a renderização de marcações usando JavaScript tem mais probabilidade de gerar tarefas longas do que a marcação transferida por download do servidor em resposta a uma solicitação de navegação. O uso extensivo da renderização de HTML do lado do cliente pode afetar negativamente a latência de interação. Isso acontece principalmente nos casos em que o DOM de uma página é muito grande, o que aciona um trabalho de renderização significativo quando o JavaScript modifica o DOM.

Minificação

Semelhante ao CSS, a minificação de JavaScript diminui o tamanho do arquivo de um recurso de script. Isso pode acelerar os downloads, permitindo que o navegador passe para o processo de análise e compilação de JavaScript mais rapidamente.

Além disso, a minificação de JavaScript vai além da minificação de outros recursos, como CSS. Quando o JavaScript é reduzido, ele não é apenas removido de elementos como espaços, guias e comentários, mas os símbolos no JavaScript de origem são encurtados. Esse processo às vezes é conhecido como uglificação. Para ver a diferença, use este código-fonte JavaScript:

// Unuglified JavaScript source code:
export function injectScript () {
  const scriptElement = document.createElement('script');
  scriptElement.src = '/js/scripts.js';
  scriptElement.type = 'module';

  document.body.appendChild(scriptElement);
}

Quando o código-fonte JavaScript anterior é uglificado, o resultado pode ser semelhante a este snippet de código:

// Uglified JavaScript production code:
export function injectScript(){const t=document.createElement("script");t.src="/js/scripts.js",t.type="module",document.body.appendChild(t)}

No snippet anterior, é possível observar que a variável legível scriptElement na origem é encurtada para t. Quando aplicada em um grande conjunto de scripts, a economia pode ser bastante significativa, sem afetar os recursos que o JavaScript de produção de um site da Web oferece.

Se você estiver usando um bundler para processar o código-fonte do site, a uglificação costuma ser feita automaticamente em builds de produção. Uglificadores, como Terser, por exemplo, também são altamente configuráveis, o que permite ajustar a agressividade do algoritmo de uglificação para conseguir o máximo de economia. No entanto, os padrões de qualquer ferramenta de uglificação geralmente são suficientes para encontrar o equilíbrio certo entre o tamanho da saída e a preservação dos recursos.

Demonstrações de JavaScript

testar seus conhecimentos

Qual é a melhor maneira de carregar vários arquivos CSS no navegador?

A declaração CSS @import.
Tente novamente.
Vários elementos <link>.
Correto.

O que o scanner de pré-carregamento do navegador faz?

Ele é um analisador de HTML secundário que examina a marcação bruta para descobrir recursos antes que o analisador DOM possa encontrá-los mais cedo.
Correto.
Detecta elementos <link rel="preload"> em um recurso HTML.
Tente novamente.

Por que o navegador bloqueia temporariamente a análise de HTML por padrão ao fazer o download de recursos JavaScript?

Para evitar um relâmpago de conteúdo sem estilo (FOUC).
Tente novamente.
Porque avaliar o JavaScript é uma tarefa que consome muita CPU, e a pausa na análise de HTML fornece mais largura de banda à CPU para concluir o carregamento de scripts.
Tente novamente.
Porque os scripts podem modificar ou acessar o DOM.
Correto.

A seguir: ajudar o navegador com dicas de recursos

Agora que você sabe como os recursos carregados no elemento <head> podem afetar o carregamento inicial da página e várias métricas, é hora de continuar. No próximo módulo, vamos conhecer as dicas de recursos e mostrar como elas podem dar dicas valiosas para o navegador começar a carregar recursos e abrir conexões com servidores de origem cruzada mais cedo do que o navegador faria sem elas.