Cada fonte personalizada significa, no mínimo, uma requisição de rede crítica e um repaint quando o navegador troca o fallback pela fonte de verdade. Em 4G médio, um arquivo woff2 de 35 KB pode jogar entre 200 e 400 ms no LCP se você não pré-carregar. E tem um detalhe pior ainda: se as métricas do fallback (x-height, advance width, essas coisas) estiverem muito longe da fonte final, a troca causa layout shift visível — e lá se vai seu CLS para fora do limite de 0,1.
Os três sintomas que você precisa eliminar são clássicos:
- FOIT (Flash of Invisible Text): texto fica invisível até a fonte chegar. Péssimo pro LCP quando o elemento principal da página é texto.
- FOUT (Flash of Unstyled Text): o texto aparece com fallback e depois "salta" pra fonte final. Péssimo pro CLS.
- Render-blocking de @font-face: declarações em CSS externo só são descobertas depois que o CSS termina de baixar e ser parseado, atrasando o início do download da fonte.
woff2 com Brotli: o ponto de partida não negociável
Em 2026, woff2 tem suporte universal e já vem com compressão Brotli embutida. Servir ttf, otf ou o woff antigo (versão 1) é jogar 30 a 50% mais bytes no fio sem ganhar nada em troca. Vale a pena dar uma olhada no seu CDN e eliminar formatos legados de uma vez:
@font-face {
font-family: "Inter";
src: url("/fonts/inter-var.woff2") format("woff2-variations"),
url("/fonts/inter-var.woff2") format("woff2");
font-weight: 100 900;
font-style: normal;
font-display: swap;
}
O fallback duplo é um truque pequeno mas útil — ele permite que browsers antigos sem suporte a woff2-variations ainda usem o mesmo arquivo como woff2 estático.
font-display: a decisão que define seu CLS e LCP
O descritor font-display controla dois períodos: o block period (em que o texto fica invisível) e o swap period (em que o fallback é mostrado). As cinco opções, na prática:
auto — comportamento padrão, geralmente equivalente a block. Evite. Sério.
block — block period de até 3s, depois swap. Causa FOIT pesado.
swap — block period de 100ms, swap infinito. Bom pro LCP (texto aparece rápido), mas pode piorar CLS se você não tomar cuidado.
fallback — block period de 100ms, swap period de 3s. Compromisso razoável.
optional — block period de 100ms, sem swap se a fonte demorar. Melhor pro CLS; ideal quando a fonte é estilística e não funcional.
Minha regra prática para 2026: use swap combinado com size-adjust e métricas de fallback ajustadas para neutralizar o layout shift. Isso te dá o melhor dos dois mundos. Reserve optional para fontes secundárias — cabeçalhos decorativos, citações, esse tipo de coisa.
Eliminando CLS com size-adjust, ascent-override e descent-override
Honestamente, essa é a técnica que mudou o jogo nos últimos anos: definir uma fonte fallback com métricas idênticas às da fonte web, de forma que a troca não cause reflow nenhum. Os descritores foram padronizados pelo CSS Fonts Module Level 4 e têm suporte total em Chrome, Firefox e Safari desde 2023.
/* Fonte fallback ajustada para casar metricas com Inter */
@font-face {
font-family: "Inter Fallback";
src: local("Arial");
size-adjust: 107%;
ascent-override: 90%;
descent-override: 22%;
line-gap-override: 0%;
}
body {
font-family: "Inter", "Inter Fallback", sans-serif;
}
Para gerar os valores corretos, use o utilitário Fontaine (npm fontaine) ou o capsize. Os dois leem o arquivo da fonte real e calculam os overrides necessários — você não precisa adivinhar nada. Resultado típico que vejo em produção: CLS cai de 0,15–0,25 para perto de zero, sem precisar mudar para font-display: optional.
Preload com fetchpriority: priorizando a fonte do LCP
Se o seu LCP é um texto (heading principal, parágrafo de hero, etc.), pré-carregue a fonte usada nele. Sem preload, o navegador só descobre a fonte depois de parsear o CSS — e isso adiciona um round-trip inteiro à cadeia crítica. Não é trivial.
<link rel="preload"
href="/fonts/inter-var.woff2"
as="font"
type="font/woff2"
crossorigin
fetchpriority="high">
Três detalhes que muita gente erra (eu já errei também, confesso):
crossorigin é obrigatório, mesmo para fontes do mesmo domínio. Sem ele, o navegador baixa a fonte duas vezes — sim, duas.
type="font/woff2" permite que browsers sem suporte simplesmente ignorem o preload.
fetchpriority="high" (suportado em Chromium e Safari 17+) sobe a prioridade acima de scripts e CSS não-crítico.
Mas atenção: limite-se a uma ou duas fontes preloadadas. Preload em excesso compete com a imagem do LCP e acaba degradando a performance que você queria melhorar. É uma ironia que vale lembrar.
Variable Fonts: um arquivo, todos os pesos
Em vez de servir inter-400.woff2, inter-500.woff2, inter-700.woff2 (cada um com seus 25 a 40 KB), uma única variable font cobre toda a faixa de pesos com tamanho um pouco maior — algo entre 60 e 90 KB. Para sites que usam três pesos ou mais, é economia líquida na largura de banda.
@font-face {
font-family: "Inter";
src: url("/fonts/inter-var.woff2") format("woff2-variations");
font-weight: 100 900;
font-style: normal;
font-display: swap;
}
h1 { font-weight: 800; }
p { font-weight: 400; }
strong { font-weight: 600; }
Confira no DevTools (aba Network) se você não está acidentalmente baixando arquivos estáticos junto com a variable font. Já vi isso acontecer em projetos onde alguém esqueceu de remover os @font-face antigos.
Subsetting com unicode-range: pague só pelos glyphs que usa
Uma fonte completa cobre Latin, Latin Extended, Cyrillic, Greek e Vietnamese — facilmente 200 KB. Mas se seu site é só em português, o que você precisa de fato é Latin + Latin Extended-A, talvez uns 40 KB no total. Use unicode-range para que o navegador só baixe o subset quando algum caractere daquele intervalo realmente aparecer na página:
@font-face {
font-family: "Inter";
src: url("/fonts/inter-latin.woff2") format("woff2");
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC,
U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074,
U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215;
font-display: swap;
}
@font-face {
font-family: "Inter";
src: url("/fonts/inter-latin-ext.woff2") format("woff2");
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020,
U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F;
font-display: swap;
}
Use glyphhanger ou fonttools subset (Python) para gerar os subsets a partir do TTF original. É menos doloroso do que parece — escreve um script uma vez e nunca mais pensa no assunto.
Self-hosting vs Google Fonts em 2026
Aqui vai uma opinião que pode soar polêmica para alguns: desde que o Chrome 86 particionou o HTTP cache por origem, o argumento clássico de "Google Fonts é cacheado entre sites" simplesmente deixou de existir. Em 2026, self-hosting é mais rápido em praticamente todos os cenários. Não é nem perto de ser disputa.
- Elimina a conexão extra com
fonts.gstatic.com (handshake TLS de 100–300 ms).
- Permite preload no HTML (impossível com Google Fonts CDN sem hardcodar a URL atual, que muda).
- Permite controle total sobre cache headers (
immutable, 1 ano, etc.).
- Resolve o risco de GDPR — Google Fonts já foi alvo de processos por logging de IPs na UE.
Use serviços como Fontsource (npm) ou simplesmente baixe direto do Google Fonts e sirva do seu próprio CDN. É trabalho de dez minutos.
Cache headers: sirva fontes uma única vez
Fontes têm hash no nome do arquivo (gerado pelo bundler) ou são versionadas de alguma forma. Sirva com cache imutável de longa duração e pronto:
# nginx
location ~* \.(woff2|woff)$ {
add_header Cache-Control "public, max-age=31536000, immutable";
add_header Access-Control-Allow-Origin "*";
}
Com immutable, o navegador nem revalida (zero 304s) — a fonte é servida do disco em menos de 5 ms.
Checklist completo para auditar fontes hoje
- Abra DevTools → Network → filtro Font. Conte quantos arquivos foram baixados.
- Para cada um, verifique: o formato é woff2? Tem
Cache-Control: immutable? O tamanho está <100 KB?
- Rode o Lighthouse e procure pelas auditorias "Ensure text remains visible during webfont load" e "Avoid enormous network payloads".
- Use o painel Performance → seção Web Vitals: o LCP element é texto? Se sim, a fonte dele está preloadada?
- Rode
capsize ou fontaine e gere os overrides para cada fonte. Aplique no CSS.
- Meça CLS antes e depois com PageSpeed Insights ou CrUX. A meta é <0,05.
FAQ
font-display: swap ou optional para melhor Core Web Vitals?
Use swap com size-adjust e overrides de métricas. Isso te dá o melhor LCP (texto aparece em 100 ms) sem degradar o CLS, porque as métricas do fallback são praticamente idênticas às da fonte final. optional só vence quando você aceita que usuários em conexão lenta podem nunca ver a fonte real — o que, sinceramente, raramente é desejável.
Devo preloadar todas as minhas fontes?
Não, e por favor não faça isso. Preload deve ser reservado para a fonte usada no elemento de LCP — tipicamente a fonte do heading principal ou do parágrafo de hero. Preloadar 4 ou 5 fontes força o navegador a competir com CSS e imagens críticas, e prejudica o próprio LCP. Para fontes secundárias, deixe a descoberta natural via CSS.
Variable font vence se eu uso só dois pesos?
Geralmente, não. Para 1 ou 2 pesos, dois arquivos estáticos (~50 KB no total) batem uma variable font (~80 KB). A variable font ganha a partir de 3 pesos, ou quando você usa font-weight arbitrário (550, 650) que estáticas não cobrem. Confira no Chrome DevTools Coverage antes de decidir.
Como elimino o CLS quando a fonte muda?
Defina uma fonte fallback com @font-face usando local() e os descritores size-adjust, ascent-override, descent-override e line-gap-override. Gere os valores com fontaine ou capsize — eles calculam overrides exatos comparando as métricas da fonte real com Arial ou Times. CLS típico cai de 0,2 para <0,01.
Self-hosted Google Fonts ainda vale a pena em 2026?
Sim, e mais do que nunca. Com o cache HTTP particionado por origem desde Chrome 86, o cache compartilhado do Google Fonts deixou de existir. Self-hosting elimina um DNS lookup, um TLS handshake e ainda permite preload + cache imutável de 1 ano. Use npm install @fontsource/inter ou baixe e sirva do seu próprio CDN. Simples.