Uma landing page pode abrir rápido e ainda travar quando alguém toca no menu, digita no formulário ou aceita o banner de consentimento. O HTML já apareceu, mas a thread principal está ocupada demais para responder.

Isso costuma acontecer quando o código gerado por IA acumula bibliotecas, listeners, animações e scripts externos sem medir o custo de execução. Baixar menos bytes ajuda, mas não basta. O navegador ainda precisa analisar, compilar e executar o JavaScript que chegou.

O conserto começa com uma interação reproduzível. Grave a execução, encontre a tarefa que bloqueou a próxima pintura e reduza o trabalho naquele caminho. Só depois faz sentido republicar e medir a mesma URL.

O que roda na thread principal

A thread principal cuida de boa parte do trabalho que transforma os arquivos em uma página interativa:

  • analisa HTML e CSS;
  • calcula estilos e layout;
  • executa JavaScript;
  • processa eventos de clique, toque e teclado;
  • prepara pintura e renderização.

Ela processa uma tarefa por vez. Segundo o guia do web.dev sobre long tasks, o navegador considera longa uma tarefa que ultrapassa 50 milissegundos. O trecho acima desse limite é o período de bloqueio.

O número não vira uma ordem para dividir qualquer função de 51 ms. Ele ajuda a localizar blocos que impedem o navegador de atender trabalho mais urgente, principalmente interações e pintura.

Separe transferência de execução

Dois scripts com o mesmo tamanho podem ter custos bem diferentes. Um apenas registra um componente pequeno. O outro analisa uma árvore grande, mede elementos, cria listeners e dispara um widget externo.

Revise quatro custos separadamente:

Etapa Pergunta prática Evidência
transferência quantos bytes chegaram pela rede? Network, bytes transferidos e compressão
análise e compilação quanto trabalho o navegador fez antes de executar? trace de Performance
execução quais funções ocuparam a thread principal? Main track e Bottom-up
renderização o callback forçou estilo, layout ou pintura? eventos de rendering no trace

O guia de compressão Brotli e gzip cobre transferência. Este artigo trata do trabalho que continua depois do download.

Comece pela interação ruim

Não abra o bundle e remova dependências ao acaso. Reproduza o atraso que afeta uma pessoa.

Exemplos úteis:

  • abrir o menu no celular;
  • focar e digitar no campo principal;
  • enviar o formulário;
  • expandir uma pergunta frequente;
  • trocar uma opção de preço;
  • aceitar ou recusar cookies;
  • abrir um chat ou vídeo.

Registre a URL, a versão publicada, o dispositivo e a sequência de ações. Em seguida, grave um trace no painel Performance do Chrome DevTools com desaceleração de CPU compatível com um celular intermediário.

O painel mostra tarefas longas com um triângulo vermelho. Selecione a que cruza a interação e percorra a pilha de chamadas. A referência do painel Performance também permite separar trabalho próprio e de terceiros.

Anote antes de editar:

URL:
versão publicada:
dispositivo e desaceleração:
interação reproduzida:
duração total da interação:
script e função dominantes:
tarefa longa observada:
trabalho próprio ou de terceiro:

Leia INP como uma sequência

Interaction to Next Paint não mede apenas o tempo do listener. A interação tem três partes:

  1. atraso de entrada, enquanto a thread está ocupada antes do callback começar;
  2. duração de processamento dos callbacks;
  3. atraso de apresentação, até o navegador mostrar o próximo frame.

A orientação atual para otimizar INP usa 200 ms ou menos como faixa boa no 75º percentil, separada entre mobile e desktop. Uma única execução de laboratório ajuda a diagnosticar, mas não substitui dados de campo.

Esse recorte evita uma correção errada. Se o atraso acontece antes do callback, talvez outro script esteja bloqueando a thread. Se acontece durante o processamento, reduza o handler. Se a apresentação demora, investigue layout e pintura.

Remova trabalho antes de dividir tarefas

A melhor tarefa é a que não precisa rodar. Antes de aplicar técnicas de agendamento, pergunte se o código entrega algo visível ou necessário.

Procure:

  • biblioteca inteira usada por um efeito simples;
  • componente hidratado sem interação;
  • validação repetida a cada tecla quando poderia ocorrer no blur ou envio;
  • listener duplicado após cada renderização;
  • cálculo de layout em itens fora da tela;
  • analytics disparado várias vezes para o mesmo evento;
  • widget carregado antes de intenção ou consentimento;
  • código de experimento encerrado;
  • polyfill para navegadores fora do suporte real do projeto.

O artigo sobre scripts de terceiros ajuda a decidir o que manter, adiar ou remover. Se o script também recebe dados externos, preserve as verificações do guia de segurança para scripts externos.

Carregue cada recurso no momento certo

Scripts não críticos não precisam disputar a primeira dobra.

Use a semântica que corresponde ao comportamento:

  • defer baixa em paralelo e executa depois da análise do HTML, preservando a ordem entre scripts deferidos;
  • async executa assim que o download termina, sem preservar ordem com outros scripts assíncronos;
  • type="module" já tem comportamento deferido por padrão;
  • import() permite buscar um módulo quando a funcionalidade for acionada.

Um exemplo para um editor aberto sob demanda:

const button = document.querySelector("[data-open-editor]");
button?.addEventListener("click", async () => {
const { openEditor } = await import("./editor.js");
openEditor();
});

Não use importação dinâmica em conteúdo essencial só para melhorar uma auditoria. Menu, formulário e consentimento precisam continuar disponíveis no momento em que a interface promete.

O guia de priorização com preload e preconnect explica descoberta e prioridade de rede. Esses hints não reduzem o custo de execução do JavaScript.

Mantenha o callback curto

O handler deve produzir a resposta visual necessária e deixar trabalho secundário para depois.

button.addEventListener("click", () => {
panel.hidden = false;
button.setAttribute("aria-expanded", "true");
setTimeout(() => {
recordPanelOpened();
prepareRecommendations();
}, 0);
});

Mover trabalho para outra tarefa cria uma oportunidade para o navegador pintar. Não torna o trabalho gratuito. Se a função secundária ainda consome centenas de milissegundos, remova custo, divida o processamento ou leve cálculo pesado e independente para um Web Worker.

Evite trocar uma tarefa longa por centenas de timers minúsculos. Cada agendamento tem custo e pode alterar a ordem de efeitos. Divida apenas nos limites em que a interface pode responder entre blocos.

Cuidado com layout forçado

JavaScript pode misturar leitura e escrita do layout de um jeito que obriga o navegador a recalcular estilos repetidamente.

for (const card of cards) {
card.style.width = `${container.offsetWidth / 3}px`;
}

O loop lê uma medida e escreve um estilo a cada passagem. Leia primeiro, escreva depois:

const cardWidth = container.offsetWidth / 3;
for (const card of cards) {
card.style.width = `${cardWidth}px`;
}

Quando CSS Grid ou Flexbox resolve a distribuição, prefira CSS. Menos JavaScript no layout reduz trabalho e elimina estados intermediários difíceis de testar.

Debounce não é solução universal

Debounce pode reduzir validações, filtros ou buscas disparadas a cada digitação:

let timer;
search.addEventListener("input", () => {
clearTimeout(timer);
timer = setTimeout(() => updateResults(search.value), 150);
});

Ele também adiciona atraso deliberado. Não aplique debounce em foco, abertura de menu, feedback de botão ou outra resposta que precisa parecer imediata. Para animações ligadas a scroll, considere requestAnimationFrame e confirme no trace que o callback cabe no frame.

Preserve acessibilidade e medição

Uma melhora de INP não justifica remover teclado, foco, validação ou mensagem de estado.

Depois da alteração, teste:

  • mouse, toque e teclado;
  • foco visível e ordem de tabulação;
  • estado aria-expanded ou mensagem equivalente;
  • formulário com sucesso e erro;
  • consentimento antes de analytics;
  • evento de conversão uma única vez;
  • navegação com JavaScript indisponível quando houver fallback prometido.

O checklist de QA para landing pages ajuda a evitar uma troca de performance por regressão funcional.

Valide e republique a mesma versão lógica

Rode build e testes do projeto. Compare o tamanho dos bundles, mas volte ao trace da interação. O objetivo é reduzir o bloqueio observado. Um arquivo menor, sozinho, não comprova isso.

Para publicar no Site no Ar:

  1. gere o pacote final limpo;
  2. confirme que index.html aponta para os assets atuais;
  3. aplique a versão pelas file tools e publique pelo contrato de Publicação;
  4. preserve o mesmo Site e o mesmo slug na republicação;
  5. abra a URL final;
  6. repita a interação e grave outro trace comparável.

Um Site com domínio próprio também precisa ser testado no hostname final. O guia de Domínios explica a relação entre o endereço padrão e o Custom Hostname.

Prompt para uma revisão focada

Analise o custo de JavaScript desta interação sem publicar ainda.
URL e versão: [URL e commit]
Interação: [passos exatos]
Dispositivo: [modelo ou desaceleração]
Evidência: [INP, trace ou long task]
1. Identifique a tarefa que bloqueia a próxima pintura.
2. Separe transferência, execução e renderização.
3. Remova trabalho desnecessário antes de propor agendamento.
4. Preserve comportamento, acessibilidade, consentimento e analytics.
5. Faça uma mudança pequena e rode os checks do projeto.
6. Mostre como repetir o trace antes de republicar.

JavaScript menor é um bom efeito colateral. A meta é concreta. A pessoa toca, a página responde e o navegador consegue pintar sem esperar uma fila de trabalho que não precisava estar ali.