Fonte própria sem travar o carregamento
Fonte é o recurso que o navegador descobre mais tarde e precisa mais cedo. Essa contradição explica o texto que demora a aparecer, o texto que aparece e depois muda de forma, e boa parte do LCP ruim em páginas que só têm texto no topo.
O problema é a ordem, não o tamanho
Um arquivo de fonte moderno em WOFF2 é pequeno. O problema nunca foi o peso. É quando o navegador descobre que precisa dele.
A cadeia normal é esta: o navegador baixa o HTML, encontra o CSS, baixa o CSS, processa o CSS, descobre a regra @font-face, verifica quais caracteres a página usa, e só então começa a baixar a fonte. São quatro passos em série antes do primeiro byte da fonte sair do servidor.
Enquanto isso, o texto já poderia estar na tela. Se a página tem um título grande no topo, esse título costuma ser o elemento que define o LCP. Uma fonte que chega atrasada pode atrasar a métrica inteira, mesmo com todo o resto pronto. O contexto completo dessa métrica está em o guia de Core Web Vitals.
A fonte é o recurso que o navegador descobre por último e precisa primeiro. Todo o resto deste artigo é sobre desfazer essa inversão.
O custo real de puxar a fonte de um CDN de terceiro
A forma mais comum de usar uma fonte é colar uma linha que aponta para o servidor de quem distribui a fonte. Funciona, é fácil, e adiciona uma conta que não aparece:
- Um domínio novo custa uma negociação inteira. Consulta de DNS, conexão e handshake de segurança acontecem antes de qualquer byte útil. Isso vale por origem, não por arquivo.
- Normalmente são duas origens, não uma. No arranjo mais popular, o CSS vem de um domínio e o arquivo da fonte vem de outro. São duas negociações em cadeia, e a segunda só começa depois da primeira terminar.
- Você entrou numa fila que não controla. Se o serviço estiver lento, a sua página fica lenta junto, e não existe nada que você possa fazer naquele momento.
- Aumenta a cadeia de dependência crítica. Cada elo em série é um ponto onde a renderização do texto pode parar.
O argumento do cache compartilhado morreu
A justificativa clássica para usar CDN de fonte era esta: se muitos sites usam a mesma fonte do mesmo endereço, o visitante já teria o arquivo em cache de outro site e não baixaria nada.
Isso deixou de ser verdade. Os navegadores passaram a particionar o cache HTTP por site, justamente para impedir que um site descubra o histórico de navegação do usuário medindo o que já está em cache. O Safari faz isso há muitos anos, e Chrome e Firefox passaram a fazer no começo desta década.
Na prática: o arquivo baixado no site A não é reaproveitado no site B, mesmo sendo exatamente a mesma URL. Todo visitante seu baixa a fonte do zero, agora com o custo extra de conversar com um domínio estranho. O benefício sumiu e o custo ficou.
Hospedar a fonte no próprio domínio
A alternativa é baixar os arquivos e servi-los do mesmo endereço da sua página. É o que este site faz: Inter e JetBrains Mono vivem em /fonts aqui mesmo, não vêm de lugar nenhum.
O que isso resolve, em ordem de importância:
- Zero conexão nova. A conexão com o seu domínio já está aberta desde o HTML. A fonte entra por ela.
- Cadeia mais curta. Some o passo de baixar um CSS de terceiro só para descobrir onde a fonte está.
- Controle de cache. Você define o cabeçalho de cache. Arquivo de fonte com nome versionado pode ser cacheado por um ano com segurança.
- Você pode fazer preload. Este é o ponto decisivo, e ele só existe quando você sabe a URL final do arquivo. Com CDN de terceiro, a URL do arquivo está escondida dentro de um CSS que você ainda não baixou.
Use sempre WOFF2. Ele é o formato comprimido moderno, tem suporte universal nos navegadores atuais e dispensa a lista de formatos alternativos que ainda circula em tutoriais antigos. Servir TTF ou OTF direto na web é desperdício puro de banda.
preload: antecipar a descoberta
O preload resolve o problema de ordem descrito lá no começo. Ele diz ao navegador, ainda na leitura do HTML, que aquele arquivo vai ser necessário. O download começa em paralelo com o CSS, em vez de esperar por ele.
É uma linha no head, e ela tem uma armadilha que anula tudo se for esquecida:
Não pré-carregue tudo
O preload não cria banda, ele reordena a fila. Cada arquivo que você antecipa disputa com a imagem do topo, que costuma ser o elemento de LCP. Pré-carregar seis fontes é uma forma eficiente de atrasar tudo.
O critério é estreito: pré-carregue apenas as fontes que aparecem na primeira tela, no peso e no estilo em que aparecem. Aqui neste site são duas, uma de cada família, e apenas o subset latino. O resto o CSS busca no tempo normal dele.
font-display: escolher entre texto invisível e texto que pula
Enquanto a fonte não chega, o navegador precisa decidir o que fazer com o texto. Existem duas escolhas ruins e você tem que ficar com uma: mostrar nada, ou mostrar com outra fonte e trocar depois. A propriedade font-display é onde você declara qual prefere.
| Valor | O que acontece | Efeito colateral |
|---|---|---|
block | Texto invisível por cerca de 3 segundos, depois troca | É o texto invisível clássico. Atrasa o LCP quando o topo é texto. |
swap | Texto imediato com a fonte de sistema, troca assim que a sua chegar | Nada fica invisível. Risco de o texto mudar de forma e empurrar o layout. |
fallback | Janela curta de invisibilidade, depois fallback, e a troca só vale por poucos segundos | Meio-termo. Em rede ruim, a sua fonte simplesmente não é usada. |
optional | Janela curta e sem troca: ou a fonte chegou a tempo, ou não é usada nesta visita | Zero risco de deslocamento. O visitante pode ver a página inteira sem a sua fonte. |
O padrão do navegador quando você não declara nada equivale ao comportamento de bloqueio. Ou seja: não declarar é escolher o pior caso para LCP.
Para uma landing page, swap costuma ser a escolha certa e é a que este site usa. O raciocínio: texto legível imediatamente vale mais que consistência tipográfica nos primeiros instantes, e quem entrou na página quer ler, não admirar a fonte. Quem prioriza estabilidade absoluta de layout acima de identidade visual tem em optional um argumento legítimo.
O texto que pula na troca, e como reduzir isso
O preço do swap é que a fonte de fallback e a sua fonte quase nunca ocupam o mesmo espaço. Larguras de caractere e altura de linha diferem, então o texto reflui no momento da troca. Se isso empurra o conteúdo abaixo, você acabou de gerar deslocamento de layout.
O controle existe e é pouco usado. No @font-face da fonte de fallback dá para declarar size-adjust, ascent-override e descent-override, ajustando a fonte de sistema para ocupar aproximadamente o mesmo espaço da sua. A troca continua acontecendo, mas quase não empurra nada.
Duas medidas mais simples ajudam antes disso: escolher uma fonte de fallback com métricas parecidas com a sua, e evitar declarar altura de linha em unidades que dependam do tamanho do glifo. Boa parte do pulo visível vem de espaçamento calculado a partir da fonte, não da fonte em si.
Subsetting: baixar só os caracteres que a página usa
Uma fonte completa carrega alfabeto cirílico, grego, vietnamita e um punhado de símbolos que a sua página em português nunca vai mostrar. Subsetting é cortar isso.
O mecanismo elegante é o unicode-range: você declara vários @font-face para a mesma família, cada um apontando para um arquivo com uma faixa de caracteres. O navegador olha o que a página realmente usa e baixa só os arquivos necessários. Se nenhum caractere daquela faixa aparece, o arquivo nunca é requisitado.
É assim que este site está montado: cada família tem um arquivo para o subset latino e outro para o latino estendido, separados por unicode-range.
Existe uma modalidade mais agressiva, que é gerar um arquivo contendo exatamente os caracteres de um texto fixo. Serve para logotipo e para título único. Não serve para conteúdo que muda, porque qualquer caractere fora do subset simplesmente não é desenhado com a sua fonte.
A decisão que rende mais que todas as anteriores
Toda a técnica deste artigo perde para uma escolha de projeto: quantas fontes a página carrega.
Cada família é um conjunto novo de arquivos. Cada peso é potencialmente mais um arquivo. Cada estilo itálico, mais um. Uma página com duas famílias, quatro pesos e itálico pode acabar com uma dúzia de arquivos, e nenhuma otimização compensa isso.
- Duas famílias no máximo. Uma para texto e, se houver motivo real, uma segunda para um papel específico.
- Prefira fonte variável. Um único arquivo cobrindo uma faixa de pesos costuma pesar menos que três arquivos estáticos.
- Não carregue itálico se você não usa itálico. É a fonte fantasma mais comum, herdada de configuração padrão de tema.
- Considere a pilha do sistema. Para muito conteúdo, usar a fonte nativa do aparelho é uma decisão defensável: zero requisição, zero espera, zero deslocamento.
Uma nota de honestidade sobre resultado: eu não vou te dar um número de quantos milissegundos essas medidas economizam, porque esse número depende da sua rede, do seu aparelho, do seu servidor e do seu layout. Qualquer valor específico apresentado como universal aqui seria invenção. Meça a sua página antes e depois, com a página real e com limitação de rede ligada, e o seu número vai valer mais que qualquer média publicada.
Perguntas frequentes
Devo hospedar a fonte no meu servidor ou usar o CDN?
Hospedar no próprio domínio é melhor na maior parte dos casos. Você elimina uma consulta de DNS e um handshake com origem externa, encurta a cadeia de dependências, controla o cache e passa a poder fazer preload, o que é impossível quando a URL do arquivo está escondida dentro de um CSS de terceiro. O antigo argumento do cache compartilhado deixou de valer quando os navegadores passaram a particionar o cache HTTP por site.
Qual valor de font-display eu devo usar?
Para landing page, swap costuma ser a melhor escolha: o texto aparece imediatamente com a fonte do sistema e troca quando a sua chega. Você troca invisibilidade por um possível reflow, e o reflow pode ser reduzido ajustando as métricas da fonte de fallback. Se estabilidade absoluta de layout for mais importante que a identidade tipográfica, optional elimina o deslocamento ao custo de a sua fonte não aparecer em conexões ruins. Não declarar nada é o pior caso, porque o padrão bloqueia o texto.
Preciso fazer preload de todas as minhas fontes?
Não, e fazer isso costuma piorar. O preload reordena a fila de downloads, não aumenta a banda disponível, então cada fonte antecipada disputa com a imagem do topo. Pré-carregue apenas as fontes visíveis na primeira tela, no peso em que aparecem. E lembre do atributo crossorigin: sem ele, a fonte é baixada duas vezes.
Por que meu texto fica invisível por alguns segundos?
É o comportamento padrão do navegador quando font-display não foi declarado. Ele esconde o texto por cerca de três segundos esperando a fonte chegar, e só depois desenha com a fonte de sistema. Declarar font-display swap resolve imediatamente: o texto passa a aparecer desde o início.
Fonte variável vale a pena?
Vale quando você usa mais de dois pesos da mesma família. Um único arquivo variável cobrindo a faixa de pesos costuma pesar menos que os arquivos estáticos equivalentes, e evita requisições separadas. Se você usa apenas um peso, um arquivo estático daquele peso específico pode ser menor. A decisão depende de quantos pesos o seu design realmente exige.