Um site criado em várias rodadas de prompt pode terminar com uma árvore de HTML enorme para mostrar uma página simples. Cada ajuste adiciona um wrapper, uma versão mobile separada, um modal oculto ou uma lista inteira que ninguém vê de uma vez.

Contar elementos ajuda a localizar esse acúmulo. Não basta para provar um problema. Um DOM profundo pode continuar barato quando quase nada muda, enquanto uma interação em uma árvore menor pode disparar recálculos de estilo e layout caros.

O trabalho começa no trace. Descubra qual ação atualiza muitos elementos, encontre o padrão que criou essa árvore e reduza o trecho responsável. Apagar div ao acaso costuma trocar um alerta por CSS quebrado.

Entenda o custo que o DOM pode ampliar

O DOM é a representação dos elementos da página que JavaScript e CSS conseguem consultar e alterar. Uma árvore maior pode aumentar memória e o trabalho necessário para casar seletores, recalcular estilos e determinar tamanhos e posições.

O insight atual Optimize DOM size do Chrome mede:

  • total de elementos;
  • maior profundidade da árvore;
  • elemento com mais filhos.

Esses números descrevem a forma da página. O insight só falha quando encontra uma atualização grande de layout ou estilo com duração acima do limite documentado. Essa condição é útil porque impede uma conclusão preguiçosa: muitos elementos não provam, sozinhos, que o DOM causa a lentidão.

Registre estrutura e custo juntos:

Evidência O que responde
total de elementos quanto HTML existe no estado atual
profundidade máxima onde wrappers sucessivos se acumularam
maior grupo de filhos qual lista ou grade concentra elementos
Recalculate Style quanto tempo o navegador gastou aplicando estilos
Layout quanto tempo gastou calculando posição e tamanho
interação reproduzida qual ação tornou esse trabalho visível para a pessoa

Grave a interação que parece lenta

Abra a URL publicada no Chrome DevTools e escolha uma ação real:

  • abrir o menu;
  • expandir uma seção de perguntas;
  • filtrar uma grade;
  • trocar uma aba;
  • mostrar resultados de busca;
  • abrir um modal;
  • adicionar itens a uma lista.

No painel Performance, grave a ação com uma desaceleração de CPU declarada. Selecione os eventos longos de Recalculate Style ou Layout e confira quantos elementos foram afetados.

A orientação do web.dev sobre layouts grandes e layout thrashing explica que o custo depende da quantidade de elementos envolvidos e da complexidade do layout. Ela também recomenda separar leituras e escritas de medidas no JavaScript.

Anote a linha de base antes de editar:

URL e Version:
viewport e CPU:
interação:
total de elementos:
profundidade máxima:
elemento com mais filhos:
tempo em Recalculate Style:
tempo em Layout:
função ou seletor associado:

Repita a gravação. Um único trace pode conter ruído de extensões, DevTools ou tarefas de fundo.

Encontre o padrão que criou os elementos

O painel aponta o efeito. O código mostra a causa.

Procure primeiro por padrões comuns em páginas geradas e remendadas:

  • cada componente devolve dois ou três wrappers sem função;
  • desktop e mobile renderizam cópias completas do mesmo conteúdo;
  • carrossel mantém todos os slides e clones no DOM;
  • accordion monta o conteúdo de todas as seções fechadas;
  • tabela ou catálogo renderiza milhares de linhas;
  • modal oculto contém uma aplicação inteira já inicializada;
  • skeleton continua no DOM depois que os dados chegaram;
  • histórico de prompts deixou versões antigas escondidas com CSS;
  • decorativos usam elementos quando pseudo-elementos resolveriam.

Confirme no Elements qual padrão domina a rota. Depois encontre a fonte correspondente. Corrigir o HTML gerado sem corrigir o template faz o excesso voltar na próxima build.

Remova wrappers sem perder semântica

Um wrapper tem função quando participa do layout, define uma região, recebe um estado, agrupa controles ou fornece semântica necessária. Se ele existe apenas porque o componente precisava devolver um nó, talvez possa desaparecer.

Antes:

<div class="section-wrapper">
<div class="content-wrapper">
<div class="title-wrapper">
<h2>Recursos</h2>
</div>
</div>
</div>

Depois:

<section class="features" aria-labelledby="features-title">
<h2 id="features-title">Recursos</h2>
</section>

Não transforme tudo em display: contents para melhorar a contagem. O elemento continua no DOM, e esse valor pode mudar como semântica, acessibilidade e posicionamento funcionam. Remova a camada na fonte quando ela não tiver responsabilidade.

Preserve main, nav, section, listas, tabelas, labels e relações ARIA que descrevem o conteúdo. O guia de HTML semântico ajuda a distinguir estrutura útil de empilhamento visual.

Não duplique a página para cada breakpoint

Renderizar duas versões completas e esconder uma com media query dobra conteúdo, links, IDs e controles. Também cria risco de foco entrar na cópia invisível ou de um leitor de tela encontrar informação repetida.

Prefira uma árvore e altere o layout com Grid, Flexbox e media queries. Quando a ordem visual muda, confirme que a ordem do DOM continua coerente para teclado e leitura.

Se o conteúdo realmente muda entre contextos, compartilhe os dados e monte somente a variante ativa. Não mantenha as duas aplicações vivas por conveniência.

O artigo sobre responsividade traz a matriz de larguras e estados que precisa continuar funcionando depois dessa consolidação.

Renderize listas de acordo com o que a pessoa usa

Uma grade com doze cards não precisa de virtualização. Uma tabela com milhares de linhas pode precisar.

Escolha pelo comportamento:

  • paginação quando a pessoa navega por conjuntos identificáveis;
  • botão “carregar mais” quando o histórico pode crescer aos poucos;
  • virtualização quando uma lista longa precisa de rolagem contínua;
  • resumo e busca quando mostrar tudo não ajuda a tarefa;
  • montagem sob demanda para painéis que só existem após interação.

Virtualização reduz nós simultâneos, mas aumenta complexidade. Teste rolagem, foco, leitores de tela, busca do navegador, impressão e preservação da posição. Não adote uma biblioteca grande para esconder doze itens.

Para conteúdo abaixo da dobra que continua no DOM, content-visibility: auto pode pular parte do trabalho de renderização até a região se aproximar da viewport. Isso não reduz a contagem de elementos. Meça o efeito e reserve tamanho para evitar saltos.

Evite layout forçado em árvores grandes

Reduzir nós ajuda pouco se cada frame alterna uma escrita de estilo e uma leitura geométrica.

Problema:

for (const card of cards) {
card.classList.add("compact");
card.dataset.height = String(card.offsetHeight);
}

Cada leitura pode obrigar o navegador a calcular o layout depois da escrita anterior. Leia medidas em um bloco e aplique mudanças em outro, ou deixe CSS resolver o tamanho.

O insight de Forced reflow no painel Performance ajuda a ligar a recalculação à função que a provocou. O guia de JavaScript na thread principal cobre long tasks e divisão de trabalho.

Revise seletores que alcançam elementos demais

Uma alteração de classe perto da raiz pode invalidar estilos em um ramo grande. Se o trace mostra Recalculate Style, use as estatísticas de seletores do DevTools e procure regras amplas, descendência longa e estados globais.

Evite trocar um DOM profundo por seletores ainda mais complexos. Dê responsabilidades claras aos componentes e aplique o estado no menor ramo que precisa reagir.

/* Estado global afeta uma área ampla. */
.page.is-filtering .catalog .card .price {
opacity: 0.6;
}
/* Estado local limita a região recalculada. */
.catalog[data-loading="true"] .price {
opacity: 0.6;
}

Meça novamente. A regra menor é mais fácil de entender, mas só o trace confirma a queda no custo.

Não use uma meta universal de elementos

Teto fixo sem contexto vira jogo de auditoria. Uma documentação extensa, uma tabela interativa e uma landing page têm necessidades diferentes.

Defina um orçamento ligado à rota e à interação:

Rota /catalogo
Interação: aplicar filtro no celular
Linha de base: 2.400 elementos, Layout 74 ms
Meta: montar apenas os resultados visíveis e manter Layout abaixo de 30 ms no cenário
Garantias: foco preservado, contagem correta e busca funcionando

Os números são um exemplo de formato. Meça a página. O guia de orçamento de performance mostra como registrar limites sem copiar valores de outro projeto.

Faça uma revisão funcional depois da poda

Teste o que costuma quebrar quando a árvore muda:

  • foco e ordem de tabulação;
  • labels e mensagens de erro;
  • IDs únicos e links de âncora;
  • scroll para seções;
  • menu, accordion, modal e carrossel;
  • analytics associados a elementos;
  • seletores usados por testes;
  • conteúdo em celular e desktop;
  • impressão e busca na página;
  • página com JavaScript indisponível, quando houver fallback.

Uma árvore menor não compensa um formulário sem label ou uma lista impossível de navegar pelo teclado.

Republique no mesmo Site e compare a URL

No Site no Ar, preserve o Site ID e a URL existente:

  1. chame get_context antes de editar;
  2. inspecione HTML, CSS e JavaScript dentro de public/;
  3. altere os arquivos pelas file tools;
  4. confirme public/sitemap.xml e os caminhos de assets;
  5. chame deploy com uma mensagem curta;
  6. acompanhe deploy_status até um estado terminal;
  7. repita o trace na URL publicada e no domínio próprio, quando existir.

A documentação de Publicação descreve o contrato das Sessions e Versions. O fluxo de republicação mantém a identidade do Site durante a comparação.

Prompt para revisar um DOM excessivo

Revise o DOM desta rota sem publicar ainda.
URL e Version: [URL e commit]
Interação lenta: [passos]
Cenário: [viewport e CPU]
1. Grave a interação três vezes no painel Performance.
2. Registre elementos, profundidade, maior grupo de filhos, estilo e layout.
3. Encontre o template ou componente que cria o maior custo.
4. Remova wrappers, duplicações e conteúdo oculto sem perder semântica.
5. Use paginação ou virtualização somente se a lista justificar.
6. Preserve foco, labels, âncoras, analytics e testes.
7. Compare o mesmo cenário antes de propor a republicação.

O ganho não está em exibir um contador menor no Lighthouse. Está em fazer uma interação atualizar menos elementos e devolver o próximo frame mais cedo, sem empobrecer o conteúdo que a página precisava mostrar.