Otimizar o carregamento de recursos

No módulo anterior, algumas teorias sobre o caminho de renderização crítica foram abordadas, assim como a forma 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 um pouco da teoria por trás disso, está pronto para aprender algumas técnicas de otimização do caminho de renderização crítica.

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

Bloqueio de renderização

Conforme discutido no módulo anterior, o CSS é um recurso que bloqueia a renderização, já que impede que o navegador renderize qualquer conteúdo até que o Modelo de objeto do CSS (CSSOM) seja construído. O navegador bloqueia a renderização para evitar um Flash de conteúdo sem estilo (FOUC), o que é indesejável do ponto de vista da experiência do usuário.

No vídeo anterior, há um breve FOUC em que é possível ver a página sem estilização. Depois disso, todos os estilos são aplicados quando o CSS da página termina de carregar da rede, e a versão sem estilo da página é imediatamente substituída pela versão estilizada.

Em 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 baixado e aplicado a ela. O bloqueio de renderização não é necessariamente indesejável, mas é importante minimizar a duração dele mantendo o CSS otimizado.

Bloqueio do analisador

Um recurso de bloqueio do analisador interrompe o analisador 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 continuar analisando o restante do HTML. Isso é proposital, já que os scripts podem modificar ou acessar o DOM durante um período em que ele ainda está sendo construído.

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

Ao usar arquivos JavaScript externos (sem async ou defer), o analisador é bloqueado desde a descoberta do arquivo até o download, a análise e a execução. Ao usar JavaScript in-line, o analisador também é bloqueado até que o script in-line seja analisado e executado.

O scanner de pré-carregamento

O scanner de pré-carregamento é uma otimização do navegador na forma de um analisador HTML secundário que verifica a resposta HTML bruta para encontrar e buscar recursos especulativamente antes que o analisador HTML principal os descubra. Por exemplo, o scanner de pré-carregamento permite que o navegador comece a baixar um recurso especificado em um elemento <img>, mesmo quando o analisador HTML está bloqueado durante a busca e o processamento de recursos como CSS e JavaScript.

Para aproveitar o scanner de pré-carregamento, os recursos críticos precisam ser incluídos na marcação HTML enviada pelo servidor. Os seguintes padrões de carregamento de recursos não podem ser descobertos pelo scanner 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 está contida em strings em recursos JavaScript e não pode ser descoberta pelo scanner de pré-carregamento.
  • Declarações @import do CSS.

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

CSS

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

Minificação

A minificação de arquivos CSS reduz o tamanho de um recurso CSS, tornando o download mais rápido. Isso é feito principalmente removendo conteúdo de um arquivo CSS de origem, como espaços e outros caracteres invisíveis, e gerando o resultado em 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 redução do CSS é uma otimização eficaz que pode melhorar o FCP do seu site e, talvez, até o LCP em alguns casos. Ferramentas como empacotadores podem realizar essa otimização automaticamente para você 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ê estiver usando um bundler que combina todos os recursos CSS em um único arquivo, é provável que seus usuários estejam baixando mais CSS do que o necessário para renderizar a página atual.

Para descobrir o 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 é selecionado no painel inferior, mostrando uma quantidade considerável de CSS não usado pelo layout da página atual.
A ferramenta de cobertura no Chrome DevTools é útil para detectar CSS (e JavaScript) não usado pela página atual. Ele pode ser usado para dividir arquivos CSS em vários recursos a 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 de CSS não usado tem um efeito duplo: além de reduzir o tempo de download, você otimiza a construção da árvore de renderização, já que o navegador precisa processar menos regras de CSS.

Evite declarações CSS @import

Embora pareça conveniente, evite declarações @import em CSS:

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

Assim como o elemento <link> funciona em HTML, a declaração @import em CSS permite importar um recurso CSS externo de uma folha de estilo. A principal diferença entre essas duas abordagens é que o elemento HTML <link> faz parte da resposta HTML e, portanto, é descoberto muito antes de um arquivo CSS baixado por uma declaração @import.

Isso acontece porque, para que uma declaração @import seja descoberta, o arquivo CSS que a contém precisa primeiro ser baixado. Isso resulta no que é conhecido como uma cadeia de solicitações que, no caso do CSS, atrasa o tempo necessário para a renderização inicial de uma página. Outra desvantagem é que as folhas de estilo carregadas usando uma declaração @import não podem ser descobertas pelo scanner 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 que as folhas de estilo sejam baixadas simultaneamente e reduzem o tempo geral de carregamento, ao contrário das declarações @import, que baixam as folhas de estilo consecutivamente.

CSS essencial inline

O tempo necessário para baixar arquivos CSS pode aumentar o FCP de uma página. A inclusã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 de um usuário não está preparado. O restante do CSS 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>

Por outro lado, a inclusão de uma grande quantidade de CSS adiciona mais bytes à resposta HTML inicial. Como os recursos HTML geralmente não podem ser armazenados em cache por muito tempo ou não podem ser armazenados em cache, 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 a performance da página para garantir que as compensações valem a pena.

Demonstrações de CSS

JavaScript

O JavaScript impulsiona a maior parte da interatividade na Web, mas isso tem um custo. O excesso de JavaScript pode tornar a página da Web lenta para responder durante o carregamento da página e até mesmo causar problemas de capacidade de resposta que diminuem as interações. Ambos 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 baixado, analisado e executado. Da mesma forma, os scripts inline 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 HTML, enquanto scripts (incluindo scripts inline) com type="module" são adiados automaticamente. No entanto, async e defer têm algumas diferenças importantes.

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, defer, 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 acontece 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

Em geral, evite usar JavaScript para renderizar qualquer conteúdo essencial ou um elemento LCP de uma página. Isso é conhecido como renderização do lado do cliente e é uma técnica usada extensivamente em aplicativos de página única (SPAs).

A marcação renderizada pelo JavaScript evita o scanner de pré-carregamento, já que os recursos contidos na marcação renderizada pelo cliente não podem ser descobertos por ele. Isso pode atrasar o download de recursos cruciais, como uma imagem de LCP. O navegador só começa a baixar a imagem da LCP depois que o script é executado e adiciona o elemento ao DOM. Por sua vez, o script só pode ser executado depois de ser descoberto, baixado e analisado. Isso é conhecido como uma cadeia de solicitações críticas e deve ser evitado.

Além disso, a renderização de marcação usando JavaScript tem mais chances de gerar tarefas longas do que a marcação baixada do servidor em resposta a uma solicitação de navegação. O uso extensivo da renderização do lado do cliente de HTML pode afetar negativamente a latência de interação. Isso é especialmente verdadeiro em 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

Assim como o CSS, a minificação do JavaScript reduz o tamanho do arquivo de um recurso de script. Isso pode levar a downloads mais rápidos, permitindo que o navegador passe para o processo de análise e compilação de JavaScript com mais rapidez.

Além disso, a minificação de JavaScript vai um passo além da minificação de outros recursos, como CSS. Quando o JavaScript é minimizado, ele não é apenas removido de coisas como espaços, tabulações e comentários, mas os símbolos no JavaScript de origem são encurtados. Esse processo às vezes é conhecido como uglification. Para ver a diferença, use o seguinte 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 é minimizado, o resultado pode ser parecido com o snippet de código a seguir:

// 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 notar que a variável legível scriptElement na origem é abreviada para t. Quando aplicado a uma grande coleção de scripts, a economia pode ser bastante significativa, sem afetar os recursos que o JavaScript de produção de um site oferece.

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

Demonstrações em JavaScript

Teste seus conhecimentos

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

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

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

É um analisador HTML secundário que examina a marcação bruta para descobrir recursos antes do analisador DOM e, assim, encontrá-los mais cedo.
Detecta elementos <link rel="preload"> em um recurso HTML.

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

Para evitar um Flash de conteúdo sem estilo (FOUC, na sigla em inglês).
Porque os scripts podem modificar ou acessar o DOM.
Porque a avaliação de JavaScript é uma tarefa que exige muito da CPU, e pausar a análise HTML dá mais largura de banda para a CPU terminar de carregar scripts.

A seguir: como 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 seguir em frente. No próximo módulo, vamos falar sobre as dicas de recursos e como elas podem dar dicas valiosas ao navegador para começar a carregar recursos e abrir conexões com servidores de origem cruzada mais cedo do que o navegador faria sem elas.