Quando o navegador carrega a tua página, lê o código HTML linha a linha. Quando encontra <script src="ficheiro-grande.js"> ou <link rel="stylesheet">, para tudo o resto, descarrega o ficheiro e executa-o.
Só depois renderiza o resto da página. Isto é “Render-Blocking” (Bloqueio de Renderização).
Em 2026, quando os Core Web Vitals importam (especialmente LCP - Largest Contentful Paint), tens de corrigir isto.
1. Javascript: Async e defer
A velha guarda dizIA: “mete scripts no rodapé (wp_footer)”. A nova guarda diz: “usa atributos”.
<script async>: Descarrega em segundo plano, executa imediatamente após download (arriscado para dependências, ex: jQuery).<script defer>: Descarrega em segundo plano, executa só após HTML carregado (seguro, preserva ordem).
Como adicionar defer no WordPress? Plugins de cache (WP Rocket, Autoptimize, LiteSpeed Cache) têm opção “Defer JS”. Ativa-a.
Isto normalmente resolve 90% dos problemas de JS.
2. CSS: Critical CSS
CSS é mais difícil. Não podes “atrasar” porque a página vai parecer “texto cru” por um momento (sem estilos - efeito FOUC).
A solução é Critical CSS.
- Pega apenas no CSS necessário para mostrar conteúdo “acima da dobra” (visível sem scroll).
- Cola-o inline em
<style>no header. - Carrega o resto do CSS (rodapé, secções inferiores) de forma assíncrona em segundo plano.
A maioria dos plugins de otimização modernos gera Critical CSS automaticamente.
Resumo da estratégia
- JS: Tudo com
defer(exceto scripts absolutamente críticos de analytics/cookies). - CSS: Critical CSS inline + resto assíncrono.
- Fontes: Usa
font-display: swap.
Assim, o utilizador vê conteúdo imediatamente enquanto scripts pesados de galerias ou mapas carregam em segundo plano.
Diagnóstico: descobrir o que bloqueia mesmo
O relatório do Lighthouse lista os ficheiros bloqueadores, mas não diz que parte deles é realmente usada. Abre as DevTools, corre “Show Coverage” a partir da paleta de comandos e recarrega. A coluna “Unused Bytes” separa dois problemas muito diferentes: CSS a mais, ou CSS carregado no momento errado. Num tema com construtor de páginas, a fatia não utilizada na página inicial costuma andar entre 70 e 90 por cento, e nesse caso minificar não resolve nada de relevante.
Depois vai ao separador Network e procura ficheiros pequenos que começam a descarregar tarde. São quase sempre recursos que o navegador só descobriu depois de processar outro ficheiro, e são esses, e só esses, que justificam um preload.
Que atributo usar em cada caso
| Atributo | Descarga | Execução | Mantém a ordem | Indicado para |
|---|---|---|---|---|
| nenhum | bloqueia o parsing | imediata | sim | evitar sempre no <head> |
async | paralela | assim que o ficheiro chega | não | scripts isolados, sem dependências |
defer | paralela | depois do HTML processado | sim | tudo o que depende de jQuery ou de outro script |
type="module" | paralela | depois do parsing | sim | módulos ES, já trazem defer |
Na prática, defer é a escolha por omissão e async é a excepção. O motivo é simples: com async os ficheiros executam pela ordem em que calham ficar prontos. Uma galeria que chame jQuery(...) antes de o jQuery estar carregado devolve um TypeError na consola e uma galeria vazia na página. No computador de quem desenvolve isto raramente aparece, porque está tudo em cache e chega na ordem certa por acaso.
Defer no WordPress sem plugin
O WordPress 6.3 acrescentou o argumento strategy ao wp_enqueue_script(), e é hoje a forma correcta de o fazer, porque é o próprio WordPress a validar a cadeia de dependências:
wp_enqueue_script(
'tema-galeria',
get_template_directory_uri() . '/js/galeria.js',
array( 'jquery' ),
'1.2.0',
array(
'in_footer' => true,
'strategy' => 'defer',
)
);Declarar a dependência é o que dá valor ao bloco: o WordPress recusa adiar o script enquanto o próprio jQuery não estiver adiado. O erro clássico deixa de ser possível.
Para instalações mais antigas, ou para um plugin que não podes alterar, resta o filtro:
add_filter( 'script_loader_tag', 'pt_defer_scripts_escolhidos', 10, 2 );
function pt_defer_scripts_escolhidos( string $tag, string $handle ): string {
$adiados = array( 'slider', 'lightbox', 'mapa' );
return in_array( $handle, $adiados, true )
? str_replace( ' src', ' defer src', $tag )
: $tag;
}Repara no sentido da lista. É uma lista de permissões, não de exclusões. Uma lista de exclusões (“tudo menos estes”) passa também a adiar qualquer script que o próximo plugin instalado venha a registar, e aí tens um defeito que não escreveste.
O pedido mais rápido é o que não acontece
Antes de adiar, pergunta se aquilo precisa sequer de ser carregado naquela página. Um plugin de formulários carrega CSS e JS em todo o site por causa de um formulário que existe apenas na página de contacto:
add_action( 'wp_enqueue_scripts', 'pt_assets_do_formulario_so_no_contacto', 99 );
function pt_assets_do_formulario_so_no_contacto(): void {
if ( ! is_page( 'contacto' ) ) {
wp_dequeue_style( 'contact-form-7' );
wp_dequeue_script( 'contact-form-7' );
}
}O teste que falta quase sempre a seguir a este bloco: submeter o formulário depois da alteração. Se o site tiver formulários embebidos noutro sítio, por exemplo um pedido de marcação no rodapé, a condição is_page remove os assets exactamente onde eram precisos e o formulário deixa de enviar sem dar erro visível.
Critical CSS sem a página a piscar
O padrão é sempre o mesmo: o CSS crítico embutido no <head> e o resto carregado sem bloquear.
<style>/* apenas as regras do que se vê sem scroll */</style>
<link rel="preload" href="/style.css" as="style"
onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/style.css"></noscript>A linha noscript não é decoração. Sem ela, quem tem JavaScript desactivado fica sem folha de estilos nenhuma, e isso inclui redes empresariais com filtragem de conteúdos.
Há três coisas que decidem o resultado. O tamanho: mantém o bloco embutido abaixo de cerca de 15 KB, porque acima disso passas a pagar uma resposta HTML maior em cada visita e apenas mudaste o problema de sítio. O viewport: CSS crítico gerado a 1300 pixéis de largura não conhece as regras exclusivas de telemóvel, por isso gera para as duas larguras e junta o resultado. E o teste em ligação lenta: põe as DevTools em “Slow 3G” e recarrega. Se vires texto sem estilos durante meio segundo antes de o layout encaixar, falta conteúdo no bloco crítico.
Tipos de letra, o bloqueio esquecido
Uma fonte própria sem font-display provoca FOIT: o navegador esconde o texto até o ficheiro chegar. Numa ligação móvel fraca são vários segundos de área em branco onde devia estar o conteúdo.
@font-face {
font-family: 'Inter';
src: url('/fonts/inter.woff2') format('woff2');
font-display: swap;
}Com swap, o texto aparece de imediato na fonte do sistema e troca quando o ficheiro chega. Junta um preload, com crossorigin, que é exigido para fontes mesmo quando o ficheiro está no teu próprio domínio:
<link rel="preload" href="/fonts/inter.woff2" as="font" type="font/woff2" crossorigin>Alojar a fonte no próprio servidor poupa ainda uma resolução de DNS e um handshake TLS contra um domínio externo antes de a descarga começar sequer.
Scripts de terceiros costumam ser o tecto
Podes optimizar o teu tema ao detalhe e continuar preso no mesmo LCP porque um widget de conversação e um mapa embebido trazem algumas centenas de kilobytes cada um. Carrega o widget só quando houver sinal de presença do utilizador:
addEventListener('scroll', function carregar() {
const s = document.createElement('script');
s.src = 'https://widget.example.com/chat.js';
s.defer = true;
document.body.appendChild(s);
}, { once: true });Ninguém abre uma conversa nos primeiros 300 milissegundos, por isso não há nada a perder em esperar.
Para mapas e vídeos usa uma fachada: uma imagem estática com o aspecto do player ou do mapa, e o iframe verdadeiro apenas ao clique. É o caso típico de um restaurante ou de uma clínica que embebe o mapa da morada no rodapé de todas as páginas, quando esse mapa só interessa a uma minoria das visitas. O pacote lite-youtube-embed faz exactamente isto para o YouTube e reduz a incorporação de centenas de kilobytes para poucos.
Medir antes e depois
O Lighthouse são dados de laboratório e servem para ter resposta rápida a cada alteração. O que aparece na Search Console são dados de campo do Chrome User Experience Report. Não se contradizem, medem coisas diferentes: uma medição num aparelho contra uma janela deslizante de 28 dias sobre aparelhos e ligações reais.
A consequência prática interessa: se a Search Console estiver na mesma no dia seguinte ao deploy, isso não é um resultado, é a janela ainda não ter rodado. Regista a data da publicação, senão daqui a um mês já ninguém consegue dizer que parte da curva pertence a que alteração.
Cinco erros que custam mais caro
asyncem algo que depende do jQuery. Galerias vazias e formulários que não enviam, e só em ligações lentas.- CSS embutido a mais. Acima de cerca de 15 KB, cada visita paga a conta.
- Esquecer o viewport móvel no Critical CSS. É no telemóvel que se faz a medição que conta.
- Lista de exclusões em vez de lista de permissões ao adiar scripts. O próximo plugin passa a ser problema teu.
- Não voltar a verificar depois de actualizar plugins. Os plugins mudam o que registam, e uma configuração que funcionava no ano passado pode estar partida em silêncio hoje.
Veja os nossos serviços de otimização de velocidade WordPress.






