Quando o navegador carrega a sua 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), tem de corrigir isto.
Como o navegador renderiza uma página
Para perceber porque certos ficheiros travam a renderização, convém olhar para a ordem em que o navegador constrói uma página.
Como o navegador constrói o DOM e o CSSOM
- Descarga do HTML. O navegador descarrega o documento HTML do servidor.
- Parsing do HTML. O parser percorre o HTML linha a linha e constrói o DOM (Document Object Model), a estrutura em árvore da página.
- Encontro com CSS. Quando o parser encontra
<link rel="stylesheet">, tem de descarregar e processar esse ficheiro para construir o CSSOM (CSS Object Model). Até lá a renderização fica parada, porque sem estilos o navegador não sabe como pintar os elementos. Como resume o web.dev, o navegador tem de descarregar e analisar os ficheiros CSS antes de mostrar a página, e isso faz do CSS um recurso que bloqueia a renderização. - Encontro com JavaScript. Perante
<script src="...">sem atributos, o parser para por completo. Descarrega o ficheiro, analisa-o e executa-o, e só depois continua com o resto do HTML. Isto acontece porque o JavaScript pode alterar o DOM que está a ser construído. - Render Tree. Só quando o DOM e o CSSOM estão prontos é que o navegador os combina na Render Tree e pinta a página.
Cada ficheiro CSS ou JavaScript no <head> sem atributos especiais trava este processo. Com 5 ficheiros CSS e 8 scripts no head, o navegador tem de descarregar e processar os 13 antes de mostrar um único píxel ao utilizador.
Como o bloqueio da renderização afeta os Core Web Vitals
O bloqueio da renderização afeta diretamente duas métricas:
FCP (First Contentful Paint): o momento em que o navegador pinta o primeiro conteúdo (texto, imagem, SVG). Quanto mais recursos bloquearem, mais tarde isso acontece. O objetivo é um FCP inferior a 1,8 segundos.
LCP (Largest Contentful Paint): o momento em que o maior elemento visível no ecrã fica renderizado. Se um ficheiro CSS pesado impede o navegador de pintar a imagem hero ou o bloco de texto principal, o LCP dispara. O objetivo é um LCP inferior a 2,5 segundos.
No WordPress, cada plugin pode colocar em fila a sua própria folha de estilos e o seu próprio script em todas as páginas, incluindo as que não os usam, e a lista do PageSpeed Insights cresce com cada plugin instalado. Enquanto esses ficheiros não chegam, o visitante vê um ecrã em branco. No telemóvel cada pedido demora mais por causa da latência da rede, e o mesmo número de ficheiros custa mais tempo do que no computador.
Async e defer em JavaScript no WordPress
A velha guarda dizia: “meta scripts no rodapé (wp_footer)”. A nova guarda diz: “use 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”. Ative-a.
Depois de a ativar, verifique a consola do navegador nas páginas com sliders, formulários e galerias.
O que é Critical CSS e como usar
CSS é mais difícil. Não pode “atrasar” porque a página vai parecer “texto cru” por um momento (sem estilos - efeito FOUC).
A solução é Critical CSS.
- Pegue apenas no CSS necessário para mostrar conteúdo “acima da dobra” (visível sem scroll).
- Cole-o inline em
<style>no header. - Carregue 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). Nuncaasyncem scripts que dependem do jQuery. - CSS: Critical CSS inline por tipo de página (início, artigo, produto, categoria) + resto assíncrono.
- Fontes: Alojadas localmente, em WOFF2, com
font-display: swape preload da fonte principal. - Imagens: A imagem LCP com
fetchpriority="high", as restantes com lazy loading nativo. - Terceiros: Analytics, chat e widgets sociais com
deferou só depois de uma interação do utilizador. - Minificação: Todos os ficheiros CSS e JS minificados, combinados apenas quando isso comprovadamente ajuda.
Assim, o utilizador vê conteúdo imediatamente enquanto scripts pesados de galerias ou mapas carregam em segundo plano.
Como identificar recursos que bloqueiam a renderização
O relatório do Lighthouse lista os ficheiros bloqueadores, mas não diz que parte deles é realmente usada. Abra as DevTools, corra “Show Coverage” a partir da paleta de comandos e recarregue. A coluna “Unused Bytes” separa dois problemas muito diferentes: CSS a mais, ou CSS carregado no momento errado. Se a maior parte de um ficheiro não é usada naquela página, minificar não resolve nada de relevante. A documentação do Lighthouse recomenda o separador Coverage precisamente para encontrar CSS e JS não críticos.
Depois vá ao separador Network e procure 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.
O Lighthouse marca como bloqueante um <script> no <head> sem defer nem async, e um <link rel="stylesheet"> sem atributo disabled cujo media corresponde ao dispositivo. media="all" conta como bloqueante.
Detetar bloqueios com o PageSpeed Insights
O PageSpeed Insights lista os ficheiros em causa em “Eliminar recursos que bloqueiam a renderização”. Para cada um mostra o URL, o tamanho e a poupança potencial em milissegundos, o que dá uma lista de trabalho ordenada por impacto.
Detetar bloqueios com o WebPageTest
O WebPageTest mostra um waterfall detalhado em que cada pedido aparece dividido em DNS, ligação, TLS e descarga. Procure os ficheiros CSS e JS que terminam antes da linha vertical de Start Render: foram esses que o navegador esperou antes de pintar.
Diferença entre async e defer
| 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.
Dois detalhes da referência do MDN evitam surpresas. defer e async não têm efeito em scripts inline sem src. E os scripts com type="module" já são adiados por omissão, por isso acrescentar defer não muda nada. Os scripts adiados executam antes do evento DOMContentLoaded, e um fragmento inline que precise de jQuery adiado pode esperar por esse evento.
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, porque o WordPress verifica a cadeia antes de imprimir o atributo. A nota de desenvolvimento do WordPress 6.3 define as regras: para um script ser adiado, as suas dependências podem ser adiadas ou bloqueantes, mas todos os scripts que dependem dele têm de ser adiados. Se algum deles for bloqueante, o script e as suas dependências voltam a carregar de forma bloqueante. Um script async pode descer para defer ou para bloqueante, nunca o contrário. E se lhe juntar código inline na posição after com wp_add_inline_script(), a estratégia é removida. Uma galeria adiada com jQuery bloqueante é portanto permitida, porque o jQuery corre primeiro de qualquer forma.
Para scripts de um plugin, a mesma nota indica wp_script_add_data( $handle, 'strategy', 'defer' ), chamado depois de o plugin registar o handle. Versões anteriores à 6.3 simplesmente ignoram a chave. Desde o WordPress 6.9, segundo a referência da função, o mesmo array aceita também a chave fetchpriority.
Para instalações mais antigas, ou para um plugin que não pode 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;
}Repare 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í tem um defeito que não escreveu.
Como carregar CSS e JS de plugins só nas páginas onde são usados
Antes de adiar, pergunte 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.
O mesmo vale para as folhas de estilo do WooCommerce, que só fazem falta nas páginas da loja:
add_action( 'wp_enqueue_scripts', 'pt_css_da_loja_so_na_loja', 100 );
function pt_css_da_loja_so_na_loja(): void {
if ( function_exists( 'is_woocommerce' ) && ! is_woocommerce() && ! is_cart() && ! is_checkout() ) {
wp_dequeue_style( 'woocommerce-general' );
wp_dequeue_style( 'woocommerce-layout' );
wp_dequeue_style( 'woocommerce-smallscreen' );
}
}Ferramentas como o PurgeCSS ou o UnCSS vão mais longe e removem, dentro de cada ficheiro, os seletores que não aparecem na página. Isso reduz o que o navegador tem de descarregar e analisar, mas traz o risco descrito mais abaixo nos erros comuns.
Carregar CSS de forma assíncrona sem FOUC
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.
O padrão com preload e onload é o que o web.dev recomenda para CSS não crítico.
Há três coisas que decidem o resultado. O tamanho: o guia do web.dev sobre CSS crítico recomenda manter o conteúdo acima da dobra abaixo de 14 KB comprimidos, para reduzir as viagens de ida e volta até à primeira renderização. Acima disso passa a pagar uma resposta HTML maior em cada visita e apenas mudou 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 gere para as duas larguras e junte o resultado. E o teste em ligação lenta: ponha as DevTools em “Slow 3G” e recarregue. Se vir texto sem estilos durante meio segundo antes de o layout encaixar, falta conteúdo no bloco crítico.
Gerar Critical CSS: ferramentas e plugins
O mesmo guia do web.dev cita duas ferramentas. A Critical extrai, minifica e embute o CSS acima da dobra e funciona com Gulp, Grunt ou webpack. O Penthouse serve para sites com muitos estilos ou estilos injetados dinamicamente, mas não deteta as folhas de estilo sozinho: é o utilizador que indica os ficheiros HTML e CSS.
No WordPress, o trabalho fica normalmente a cargo de plugins:
WP Rocket: Na opção “Otimizar a entrega de CSS”, gera Critical CSS para cada página e embute-o inline.
Autoptimize com CriticalCSS.com: O Autoptimize liga-se ao serviço CriticalCSS.com. Precisa de uma chave de API, e a partir daí a geração é automática.
LiteSpeed Cache: Gera Critical CSS através do serviço QUIC.cloud, e a cache de página funciona em servidores LiteSpeed.
Perfmatters: Um plugin leve cujo Script Manager desativa scripts e estilos por página, útil para plugins que carregam os seus ficheiros em todo o site.
Font-display swap e preload de fontes
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. Junte um preload, com crossorigin, que é exigido para fontes mesmo quando o ficheiro está no seu 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.
Há também a questão da privacidade. O carregamento dinâmico do Google Fonts envia o endereço IP do visitante para servidores da Google. Em janeiro de 2022, o Landgericht München I, um tribunal alemão, considerou ilícita essa transmissão sem consentimento, e a sentença deu origem a uma vaga de reclamações na Alemanha.
Para alojar o Google Fonts localmente:
- Descarregue os ficheiros de fonte a partir do repositório do Google Fonts ou com a ferramenta google-webfonts-helper.
- Envie os ficheiros WOFF2 para o seu servidor, porque o WOFF2 oferece a melhor compressão.
- Atualize as regras
@font-faceno CSS para apontarem para os ficheiros locais. - Remova do HTML as referências a fonts.googleapis.com e fonts.gstatic.com.
No WordPress, o plugin OMGF (Optimize My Google Fonts) automatiza todo este processo.
Minificar e combinar CSS e JS com HTTP/2
O que a minificação traz
A minificação remove espaços, comentários e caracteres desnecessários dos ficheiros CSS e JavaScript sem alterar o seu funcionamento. A poupança depende de quantos comentários e espaços o ficheiro original tem, e com gzip ou Brotli ativos no servidor a diferença transferida é menor do que a do ficheiro sem compressão.
No WordPress, a minificação fica normalmente a cargo dos plugins de cache: o WP Rocket minifica CSS e JS com uma caixa de verificação, o Autoptimize é especializado em minificar e combinar ficheiros, e o LiteSpeed Cache inclui minificação integrada.
Combinar ficheiros CSS e JS com HTTP/2
Juntar vários ficheiros CSS num só (e vários ficheiros JS num só) reduz o número de pedidos HTTP. Com HTTP/2 e HTTP/3 isto pesa menos do que antes, porque estes protocolos permitem várias descargas em simultâneo numa só ligação.
Combinar pode até ser contraproducente se invalidar a cache com demasiada frequência. Se juntar 15 ficheiros CSS num só e um plugin mudar uma única linha, o ficheiro combinado inteiro tem de ser descarregado de novo. Com ficheiros separados, só o ficheiro alterado precisa de ser atualizado.
A recomendação atual é minificar sempre, mas combinar apenas quando o site ainda usa HTTP/1.1 ou quando o waterfall mostra dezenas de ficheiros pequenos à espera da sua vez.
Preload, prefetch e fetchpriority no WordPress
Além de async, defer e Critical CSS, os navegadores modernos aceitam indicações de prioridade que dizem que recursos são críticos e quais podem esperar.
Quando usar preload
<link rel="preload" href="/critical-script.js" as="script">
<link rel="preload" href="/imagem-hero.webp" as="image">O preload diz ao navegador para descarregar um recurso o mais cedo possível, antes de o descobrir naturalmente durante o parsing do HTML. Use-o para recursos críticos como a imagem LCP, a fonte principal ou um script essencial.
Quando usar prefetch
<link rel="prefetch" href="/pagina-seguinte/">
<link rel="prefetch" href="/estilos-pagina2.css">O prefetch descarrega um recurso com prioridade baixa nos tempos mortos, antecipando que o utilizador vai precisar dele em breve. É útil para recursos da página seguinte mais provável.
Quando usar fetchpriority
<img src="/hero.webp" fetchpriority="high">
<img src="/decorativa.webp" fetchpriority="low">O atributo fetchpriority ajusta a prioridade de descarga de recursos individuais: mais alta para a imagem LCP, mais baixa para imagens decorativas ou fora do ecrã. Segundo o web.dev, funciona em link, img e script com os valores high, low e auto. Preload e fetchpriority fazem trabalhos diferentes: o preload serve para o navegador descobrir o recurso mais cedo, o fetchpriority muda a prioridade mas não ajuda a descobri-lo. Uma imagem LCP com preload pode continuar a receber prioridade baixa, e uma imagem de fundo em CSS que seja o elemento LCP continua a precisar de preload.
Como atrasar scripts de terceiros e widgets de chat
Pode optimizar o seu 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. Carregue 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 use 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.
Lighthouse ou dados de campo no Search Console para render blocking
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. Registe 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.
Erros comuns ao adiar CSS e JS
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 14 KB comprimidos, cada visita paga a conta.
- Purgar CSS sem verificação visual. As ferramentas automáticas podem remover estilos que só se ativam com interação (hover, estados ativos, modais) ou que se aplicam a conteúdo carregado dinamicamente. Verifique sempre o resultado, sobretudo no telemóvel.
- Testar só no computador. Um site pode carregar depressa numa ligação de fibra e falhar no telemóvel com 4G. Teste na vista móvel do PageSpeed Insights e em aparelhos reais.
- Perseguir os 100 pontos. O PageSpeed Insights é uma ferramenta de diagnóstico, não um objetivo. Otimize para a experiência real de quem visita, não para um número.
- 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 seu.
- 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.







