Forced reflow no Lighthouse: o que significa e como corrigir
Um script que altera a página e pede de seguida uma dimensão faz o browser refazer o layout de imediato. Quando isso faz os toques encravar e quando é inofensivo.
O que significa Forced reflow no Lighthouse?
Um forced reflow significa que um script alterou a página e pediu de seguida uma dimensão, obrigando o browser a recalcular o layout de imediato. O Lighthouse lista esses scripts no insight Forced reflow ("Um reflow forçado ocorre quando o JavaScript consulta propriedades geométricas"). Uma vez é normal; num ciclo, faz os toques encravar. Deixe o CSS fazer o trabalho do script, ou leia todas as dimensões antes de alterar o que quer que seja.
Table of Contents!
O que lista o aviso de Forced reflow?
O Lighthouse 13 e o PageSpeed Insights descrevem-no assim: "Um reflow forçado ocorre quando o JavaScript consulta propriedades geométricas (como offsetWidth) após os estilos terem sido invalidados por uma alteração ao estado do DOM. Isto pode resultar num fraco desempenho. Saiba mais sobre reflows forçados e possíveis mitigações." Fica vermelho quando os scripts forçam 30 ms ou mais de trabalho de layout de uma só vez durante o carregamento da página.
O relatório abaixo vem de uma página de listagem de cabanas que criei à semelhança de um site WordPress de alojamento de férias, cujo script do tema corta cada descrição para três linhas e deixa todos os cartões com a mesma altura.

Lighthouse 13.5 no telemóvel, throttling do DevTools (Slow 4G, 4x CPU).
- Top function call: a função exterior que iniciou o trabalho de layout. Passe o cursor sobre o caminho encurtado do ficheiro para ver o caminho completo, que identifica o tema, plugin ou app. Um ficheiro noutro domínio costuma ser uma tag de terceiros, como um gravador de sessões (Scripts que não pode alterar).
- Source: cada linha de código que pediu uma dimensão.
- Total reflow time: o tempo de layout causado por essa função ou linha.
- Unscored: o insight não tem peso na pontuação de Performance. As long tasks associadas (períodos superiores a 50 ms em que a página não consegue responder) descem a pontuação através do Total Blocking Time.
A linha superior (388 ms) é inferior à soma das linhas de Source (743 ms) porque o script das cabanas é executado duas vezes durante o carregamento, e a linha superior mostra apenas a execução maior.
Porque é que um script força o browser a refazer o layout?
Na listagem de cabanas acima, um toque no filtro "All" congela a página durante um terço de segundo antes de surgirem os 48 cartões.
O layout é a etapa em que o browser calcula a posição e o tamanho de cada caixa. Normalmente faz isso uma vez por frame, depois de os scripts correrem. Quando um script altera a página (um texto, uma classe, uma altura) e a seguir lê uma dimensão, como offsetHeight, getBoundingClientRect(), scrollTop ou getComputedStyle(), o browser tem de calcular o layout de imediato, dentro do script, para responder. O Paul Irish mantém a lista completa das leituras que o forçam.
Um único forced reflow é, quase sempre, trabalho de layout que o browser faria instantes depois. Num ciclo que altera e volta a ler, passa a ser um layout completo em cada iteração: layout thrashing. O tema das cabanas corta cada descrição desta forma:
while (el.offsetHeight > max && words.length > 1) {
words.pop();
el.textContent = words.join(' ') + '…';
} Cada palavra removida altera o texto, pelo que a leitura seguinte de offsetHeight força um novo layout: um por palavra, em cada cartão.
Quanto é que um forced reflow atrasa uma página?
Medi a listagem de cabanas com o script do tema (Antes) e com CSS a cortar o texto e a igualar a altura dos cartões (Depois). Ambas as versões têm o mesmo aspeto visual.
| Antes | Depois | |
|---|---|---|
| Pontuação do Lighthouse | 84 | 92 |
| Total Blocking Time | 360 ms | 0 ms |
| Total reflow time | 743 ms | nenhum |
| Um toque em "All" | 320 ms | 16 ms |

Antes

Depois
Lighthouse 13.5 no telemóvel, throttling do DevTools.
Os 8 pontos vêm do Total Blocking Time: os layouts forçados mantêm a página bloqueada durante mais tempo. O toque é o que o visitante sente, e conta para o INP (Interaction to Next Paint), a Core Web Vital que mede a rapidez com que a página responde a toques e cliques. 320 ms ultrapassa o limite de 200 ms para um bom INP, e 298 ms desse tempo foram layout forçado pelo script; 16 ms correspondem a uma única frame. O Lighthouse nunca simula toques, pelo que medi os toques no Chrome com o CPU abrandado para a velocidade de um telemóvel.
Quando é que um forced reflow é inofensivo?
- Uma única linha pequena durante o carregamento. Uma linha ligeiramente acima de 30 ms é, muitas vezes, o primeiro layout da página, antecipado porque um script pediu uma dimensão. O script do tema da listagem de cabanas, reescrito para ler primeiro todas as alturas, apresentou uma linha de 37 ms numa em cada quatro execuções do Lighthouse, e o toque em "All" demorou 16 ms.
- Uma linha "[unattributed]". Não indica nenhuma linha de código: execute o Lighthouse novamente e, se a linha desaparecer, ignore.
- Um script que inicia uma animação. Os modais do Bootstrap e alguns plugins de popups leem uma dimensão de propósito para que a transição CSS arranque.

Lighthouse 13.5 no telemóvel, throttling do DevTools.
Corrija quando o tempo total de reflow estiver na ordem das centenas de milissegundos, quando um ficheiro tiver muitas linhas, ou quando o script também for executado em toques: um filtro, um menu, um slider.
Que script causa o forced reflow num toque?
O Lighthouse apenas carrega a página, por isso grave no painel Performance os toques que encravam:
- Abra o DevTools (F12), vá a Performance e defina o abrandamento do CPU para 4x, para que o portátil se comporte como um telemóvel.
- Clique em Record, toque no elemento que parece lento e pare a gravação.
- Abra Forced reflow na barra lateral de Insights. "Call frames that trigger reflow" indica a função e a linha.

Um toque em "All" na listagem de cabanas: clampText em cabins.js.
A consola reporta o mesmo através de "[Violation] Forced reflow while executing JavaScript took 38ms", assim que ativar Verbose nos níveis de registo.
Quando o Lighthouse apenas indica jquery.min.js, foi o jQuery que fez a leitura em nome de um tema ou plugin. Grave um recarregamento no painel Performance e clique num bloco de Layout com um triângulo vermelho: no separador Summary, por baixo de Function stack, o ficheiro entre as linhas do jQuery, com um caminho como /wp-content/plugins/<name>/, é o plugin.
Os seus field data contêm os toques e cliques dos visitantes reais. No CoreDash, abra INP, Breakdown e agrupe por LOAF: o Chrome regista que scripts correram em cada frame lenta (Long Animation Frames), pelo que cada linha representa um script com o INP das interações em que participou. Um forced reflow no handler de um clique ou toque conta como processing, a parte roxa de cada barra; comece, por isso, por um script lento cuja parte roxa seja longa.

INP por script no CoreDash, desktop, p75: verde é input delay, roxo processing, azul presentation.
Como corrigir um forced reflow?
Comece pelo ficheiro em Top function call e veja se o CSS pode fazer o trabalho desse script. Texto cortado a três linhas (line-clamp), cartões com a mesma altura (grid), um cabeçalho fixo no topo (position: sticky) e uma caixa de imagem que mantém as proporções (aspect-ratio) não precisam de scripts; e sem o script, nada força o layout.
Depois de aplicar qualquer uma das correções abaixo, volte a gravar o mesmo toque: o Forced reflow deverá ter desaparecido da barra lateral de Insights. Em seguida, verifique no Lighthouse e nos seus field data; a Search Console demora até 28 dias.
Temas, plugins e page builders do WordPress
O caminho em Top function call indica o tema (/wp-content/themes/) ou o plugin (/wp-content/plugins/). Procure primeiro por uma opção nas definições. A opção "Stretch Section" do Elementor alarga uma secção com JavaScript em cada carregamento e redimensionamento; o layout de página Elementor Full Width com um contentor de largura total faz o mesmo em CSS. Um slider Swiper configurado com "auto" em slides per view mede cada slide; com um número fixo, calcula as larguras sem ter de as ler.
Adiar o JavaScript no WP Rocket ou num plugin semelhante limpa o aviso, mas o script passa a correr no primeiro toque ou scroll do visitante, com os mesmos layouts forçados. Em vez disso, corrija o script ou reporte-o ao programador do plugin e substitua o plugin se nada mudar.
Quando o tema corta texto com JavaScript, corte-o com CSS no seu child theme, usando a classe do tema para o texto:
.cabin .desc {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
line-clamp: 3;
overflow: hidden;
} Depois, desative o script de clamp do tema: através de uma opção do tema, ou com wp_dequeue_script() usando o respetivo handle.
Temas e apps Shopify
No editor do tema, em App embeds, desative as apps uma a uma e execute novamente o Lighthouse. Quando o aviso desaparecer, reporte essa app ao respetivo programador ou substitua-a. Um caminho com /assets/ em Top function call é um ficheiro do tema, seja do próprio tema ou de uma app que lhe adicionou código. O Dawn e os temas nele baseados normalmente leem a dimensão uma só vez e agrupam as alterações em lote; comece, por isso, por analisar as apps.
JavaScript próprio e React
Quando um script tem mesmo de medir, leia primeiro todas as dimensões e faça as alterações depois, para que um único layout responda a todas as leituras. Um acordeão que define a altura própria de cada painel:
const panels = [...document.querySelectorAll('.panel')];
// Um layout por painel: ler, escrever, ler, escrever
panels.forEach(p => { p.style.maxHeight = p.scrollHeight + 'px'; });
// Um só layout: ler todas as alturas, depois escrever todas
const heights = panels.map(p => p.scrollHeight);
panels.forEach((p, i) => { p.style.maxHeight = heights[i] + 'px'; }); - Resize e scroll: o ResizeObserver e o IntersectionObserver fornecem as dimensões do layout que o browser acabou de calcular. Utilize-os em vez de ler dimensões num handler de resize ou scroll.
- React: meça uma vez em
useLayoutEffecte atualize o estado uma só vez. Monte menus, popovers e tooltips apenas quando abrem, para que os elementos fechados não meçam nada durante o carregamento.
Scripts que não pode alterar
Os gravadores de sessões e as tags de mapas de calor leem dimensões de propósito: o Microsoft Clarity lê uma dúzia de dimensões da página ao arrancar e a posição de cada elemento em que o visitante clica. Remova a tag, grave apenas uma amostra das visitas ou carregue-a unicamente nas páginas que estiver a analisar.
Tem um forced reflow num plugin ou app que não pode substituir, ou toques que encravam nos telemóveis dos visitantes e não no seu? Numa power hour de Core Web Vitals, identificamos o script por trás dos toques mais lentos e decidimos em conjunto se a solução é corrigir, substituir ou remover.
Ponho sites a passar os Core Web Vitals.
Mais de 500K páginas para grandes editoras europeias e e-commerce. Escrevo os fixes e confirmo-os com dados RUM.
Como trabalho