Site estático não é imune a anúncio ruim. Um <script> de terceiro carregado sem cuidado empurra conteúdo na tela, ocupa a thread principal e atrasa o maior elemento visível da página, e isso vale tanto para um blog em WordPress quanto para um site 100% pré-renderizado como este. A diferença é que, num site estático, não há desculpa de “servidor lento”: se o Core Web Vitals ficar ruim, o culpado é decisão de frontend, ponto.
Antes de ligar o Google AdSense neste blog, tratei anúncio como um orçamento, não como um bônus. Este post é o registro real desse orçamento: três números, três decisões técnicas, e por que cada uma existe.
Os três números que viram orçamento
Core Web Vitals tem três métricas com limiar de “bom” bem definido:
- LCP (Largest Contentful Paint): tempo até o maior elemento visível renderizar. Bom é 2,5 segundos ou menos (web.dev/articles/lcp).
- INP (Interaction to Next Paint): latência entre uma interação do usuário (clique, toque, tecla) e o próximo frame pintado na tela. Bom é 200 milissegundos ou menos (web.dev/articles/inp).
- CLS (Cumulative Layout Shift): soma dos deslocamentos inesperados de layout durante a vida da página. Bom é 0,1 ou menos (web.dev/articles/cls).
Anúncio de terceiro ameaça os três ao mesmo tempo: o script atrasa a rede (LCP), executa JavaScript pesado na thread principal (INP) e, se o espaço dele não estiver reservado antes de carregar, empurra tudo abaixo dele quando finalmente aparece (CLS). Tratei cada ameaça como uma decisão separada, não como um problema genérico de “performance”.
Altura fixa, não mínima, contra o CLS
A primeira versão do design usava min-height genérico para reservar espaço de anúncio. Isso é um erro sutil: min-height garante um piso, mas não impede o elemento de crescer se o criativo carregado for maior que o esperado, e ainda por cima infla o espaço reservado do formato real da unidade (um leaderboard de 90 a 100 pixels de altura não precisa de 280 pixels de reserva).
A decisão que ficou foi reservar altura fixa por variante real de anúncio: 100 pixels no leaderboard mobile (90 no desktop), 250 pixels no retângulo dentro do artigo e 250 pixels no retângulo da barra lateral, sempre usando height, não min-height, no contêiner. Com isso, o espaço existe exatamente do tamanho do anúncio antes de ele carregar, e não muda depois, preenchido ou vazio. CLS por anúncio vira, na prática, zero.
.ad-slot--leaderboard { height: var(--ad-height-leaderboard); }
.ad-slot--in-article { height: var(--ad-height-in-article); }
.ad-slot--sidebar { height: var(--ad-height-sidebar); }
Carregar depois do LCP, sob demanda
O script do AdSense carrega de forma assíncrona e só depois do elemento que normalmente é o LCP da página (o <h1> do post ou a imagem de capa, quando existir): nunca antes dele, para não competir por rede e renderização com o que realmente decide a métrica.
Para os slots abaixo da dobra, a inicialização só acontece quando o slot está perto de entrar na viewport, via IntersectionObserver com uma margem de cerca de 200 pixels: carrega um pouco antes do usuário chegar lá, não só quando o elemento já está visível. Isso evita gastar orçamento de rede e de thread principal com anúncio que o leitor talvez nunca role até ver.
Limite de 2 a 3 unidades por página
Cada unidade de anúncio adicional é mais JavaScript de terceiro competindo pela mesma thread principal que processa clique e toque do leitor, ou seja, mais risco para o INP. Por isso o teto deste blog é de 2 a 3 unidades por página, não “quantas couberem”. Numa página de post, isso significa exatamente três posições fixas (topo, dentro do artigo, barra lateral) e nenhuma quarta posição, mesmo em telas grandes.
Como eu verifico antes de publicar
Nenhuma dessas decisões vale nada sem uma forma objetiva de checar se o orçamento está sendo respeitado. Antes de existir tráfego real neste blog, uso dado de laboratório: Lighthouse ou PageSpeed Insights rodado com perfil fixo de dispositivo e rede, não com a conexão boa do meu próprio computador. O perfil que uso é o padrão mobile dessas ferramentas: CPU com throttling de 4 vezes e rede simulando uma “Slow 4G”. É um cenário deliberadamente pior que o normal, porque é isso que separa “funciona no meu notebook” de “funciona para o leitor real”.
Cada slot novo de anúncio passa por essa checagem antes de virar padrão. Se o resultado estourar o orçamento de LCP, INP ou CLS, a resposta não é “relevar” o número: é remover o slot ou reposicioná-lo até o orçamento fechar de novo. Depois que o site tiver tráfego de verdade por algumas semanas, o Google Search Console passa a mostrar o Core Web Vitals com dado de campo real (CrUX), que confirma se o que o laboratório previu bate com a experiência real do leitor; mas esse dado de campo só acumula amostra suficiente depois de um mês de tráfego, então ele serve para confirmar depois, não para decidir antes de lançar.
O que eu faria diferente
Duas coisas que aprendi montando este orçamento. A primeira é que “espaço reservado” só resolve CLS se o valor reservado bater com o tamanho real da unidade veiculada; testar com o tamanho real do anúncio, não com um placeholder genérico, é obrigatório antes de considerar o slot pronto. A segunda é que auto ads (o modo do Google que decide sozinho onde inserir anúncio) provavelmente renderia mais no curto prazo, mas eu abri mão desse ganho de propósito: posição imprevisível é controle de CLS pior, e prefiro um orçamento que eu decido a um que o algoritmo decide por mim. Se a receita ficar muito abaixo do esperado, essa é a primeira decisão que eu reavaliaria, não a última.


