Recursos em linha em frameworks JavaScript

Melhorar a Largest Contentful Paint no ecossistema JavaScript.

Como parte do projeto Aurora, o Google tem trabalhado com frameworks da Web conhecidos para garantir que eles tenham bom desempenho de acordo com as Core Web Vitals. O Angular e o Next.js já têm a formatação de fonte inline, explicada na primeira parte deste artigo. A segunda otimização que vamos abordar é o inline CSS crítico, que agora é ativado por padrão no Angular CLI e tem uma implementação em andamento no Nuxt.js.

Inlining de fontes

Depois de analisar centenas de aplicativos, a equipe do Aurora descobriu que os desenvolvedores geralmente incluem fontes nos aplicativos referenciando-as no elemento <head> de index.html. Confira um exemplo de como isso ficaria ao incluir os ícones do Material Design:

<!doctype html>
<html lang="en">
<head>
  <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
  ...
</html>

Mesmo que esse padrão seja totalmente válido e funcional, ele bloqueia a renderização do aplicativo e introduz uma solicitação extra. Para entender melhor o que está acontecendo, confira o código-fonte da folha de estilo referenciada no HTML acima:

/* fallback */
@font-face {
  font-family: 'Material Icons';
  font-style: normal;
  font-weight: 400;
  src: url(https://fonts.gstatic.com/font.woff2) format('woff2');
}

.material-icons {
  /*...*/
}

Observe como a definição de font-face faz referência a um arquivo externo hospedado em fonts.gstatic.com. Ao carregar o aplicativo, o navegador precisa primeiro fazer o download da folha de estilo original referenciada no cabeçalho.

Uma imagem mostrando como o site precisa fazer uma solicitação ao servidor e fazer o download da folha de estilo externa
Primeiro, o site carrega a folha de estilo de fontes.

Em seguida, o navegador faz o download do arquivo woff2 e, por fim, pode continuar a renderização do aplicativo.

Uma imagem mostrando as duas solicitações feitas, uma para a folha de estilo de fonte e a segunda para o arquivo de fonte.
Em seguida, é feita uma solicitação para carregar a fonte.

Uma oportunidade de otimização é fazer o download da folha de estilo inicial no tempo de build e in-line no index.html. Isso pula uma ida e volta inteira para o CDN no momento da execução, reduzindo o tempo de bloqueio.

Ao criar o aplicativo, uma solicitação é enviada para a CDN, que busca a folha de estilo e a inline no arquivo HTML, adicionando um <link rel=preconnect> ao domínio. Ao aplicar essa técnica, teríamos o seguinte resultado:

<!doctype html>
<html lang="en">
<head>
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin >
  <style type="text/css">
  @font-face{font-family:'Material Icons';font-style:normal;font-weight:400;src:url(https://fonts.gstatic.com/font.woff2) format('woff2');}.material-icons{/*...*/}</style>
  ...
</html>

O recurso inline de fontes já está disponível no Next.js e no Angular

Quando os desenvolvedores de frameworks implementam a otimização nas ferramentas, eles facilitam a ativação de aplicativos novos e existentes, trazendo melhorias para todo o ecossistema.

Essa melhoria é ativada por padrão na Next.js v10.2 e na Angular v11. Ambos oferecem suporte a fontes inline do Google e da Adobe. O Angular pretende introduzir o segundo na v12.2.

Você pode encontrar a implementação da inlining de fontes no Next.js no GitHub e conferir o vídeo que explica essa otimização no contexto do Angular.

Inserir CSS essencial

Outro aprimoramento envolve melhorar as métricas First Contentful Paint (FCP) e Largest Contentful Paint (LCP) com a incorporação de CSS crítico. O CSS essencial de uma página inclui todos os estilos usados na renderização inicial. Para saber mais sobre o assunto, consulte Defer CSS não críticos.

Observamos que muitos aplicativos estão carregando estilos de forma síncrona, o que bloqueia a renderização do aplicativo. Uma solução rápida é carregar os estilos de forma assíncrona. Em vez de carregar os scripts com media="all", defina o valor do atributo media como print e, quando o carregamento for concluído, substitua o valor do atributo por all:

<link rel="stylesheet" href="..." media="print" onload="this.media='all'">

No entanto, essa prática pode causar a oscilação de conteúdo sem estilo.

A página parecerá oscilar conforme os estilos são carregados.

O vídeo acima mostra a renderização de uma página, que carrega os estilos de forma assíncrona. Essa oscilação acontece porque o navegador começa a fazer o download dos estilos e depois renderiza o HTML depois. Depois que o navegador faz o download dos estilos, ele aciona o evento onload do elemento de link, atualiza o atributo media para all e aplica os estilos ao DOM.

Durante o tempo entre a renderização do HTML e a aplicação dos estilos, a página fica parcialmente sem estilo. Quando o navegador usa os estilos, há uma cintilação, que é uma experiência ruim para o usuário e resulta em regressões na mudança de layout cumulativa (CLS, na sigla em inglês).

O uso de CSS crítico em linha, junto com o carregamento de estilo assíncrono, pode melhorar o comportamento de carregamento. A ferramenta critters encontra quais estilos são usados na página, analisando os seletores em uma folha de estilo e os comparando com o HTML. Quando ele encontra uma correspondência, considera os estilos correspondentes como parte do CSS crítico e os coloca inline.

Vejamos um exemplo:

O que não fazer
<head>
   <link rel="stylesheet" href="/styles.css" media="print" onload="this.media='all'">
</head>
<body>
  <section>
    <button class="primary"></button>
  </section>
</body>
/* styles.css */
section button.primary {
  /* ... */
}
.list {
  /* ... */
}

Exemplo antes do inline.

No exemplo acima, o Critter vai ler e analisar o conteúdo de styles.css. Depois disso, ele vai comparar os dois seletores com o HTML e descobrir que usamos section button.primary. Por fim, o Critters vai inserir os estilos correspondentes no <head> da página, resultando em:

O que fazer
<head>
  <link rel="stylesheet" href="/styles.css" media="print" onload="this.media='all'">
  <style>
  section button.primary {
    /* ... */
  }
  </style>
</head>
<body>
  <section>
    <button class="primary"></button>
  </section>
</body>

Exemplo após a inserção inline.

Depois de incluir o CSS crítico no HTML, você vai notar que a cintilação da página desapareceu:

O carregamento da página após o inline do CSS.

O inline de CSS crítico agora está disponível no Angular e ativado por padrão na v12. Se você estiver usando a v11, ative essa opção definindo a propriedade inlineCritical como true em angular.json. Para ativar esse recurso no Next.js, adicione experimental: { optimizeCss: true } ao next.config.js.

Conclusões

Neste post, abordamos algumas das colaborações entre o Chrome e os frameworks da Web. Se você já criou uma estrutura e reconhece alguns dos problemas que abordamos na sua tecnologia, esperamos que nossas descobertas inspirem você a aplicar otimizações de desempenho semelhantes.

Saiba mais sobre as melhorias. Confira uma lista completa do trabalho de otimização que fizemos para as Core Web Vitals na postagem Introdução ao Aurora.