As nossas paginas Next.js ficaram demasiado pesadas para o Bing. O payload RSC era 58 % do HTML
Tres paginas Next.js presas em descoberta mas nao rastreada no Bing. O App Router envia cada byte duas vezes. Reduzimos uma em 48 KB e o Bing indexou-a.
8 de outubro de 2026
O Katto é uma ferramenta de clipping de vídeo com IA. Dá-lhe um vídeo longo, ele encontra os melhores momentos e corta-os em clips verticais legendados. Construo-o sozinho, em público, com Next.js e o App Router. Esta é uma medição que mudou a minha forma de escrever páginas, um número que anunciei como certo à frente de toda a gente e que estava errado, e uma hipótese minha que valia 108 bytes.
O sintoma
O Bing Webmaster Tools listava várias das nossas páginas como descobertas mas nunca rastreadas. Não estavam bloqueadas pelo robots.txt, não tinham noindex, não eram lentas, não redirecionavam. Descobertas e depois abandonadas. O Google indexava-as. O nosso sitemap listava-as. Devolviam 200 a quem as pedisse.
As páginas recusadas tinham uma coisa em comum, e não era o conteúdo.
O limite, e o pouco que sabemos sobre ele
A Microsoft não publica nenhum tamanho a partir do qual o seu rastreador desiste. O que temos são as nossas próprias páginas, cada uma com o estado lido no Bing Webmaster Tools e o peso servido medido no mesmo dia, sem compressão, com o agente do bingbot:
/contact, 46 111 bytes: indexada./changelog, 103 058 bytes: indexada./roadmap, 116 239 bytes: indexada./compare/opusclip, 127 182 bytes: indexada./compare/ai-video-clippers, 127 951 bytes: indexada./compare/, 140 686 bytes: descoberta mas não rastreada.
Uma única página do outro lado de uma linha não é uma lei. Mas há nesta lista uma observação que vale mais do que todo o resto, porque é duas vezes o mesmo URL. O /compare/ai-video-clippers servia 172 295 bytes e ficou semanas preso em descoberta mas não rastreada. Reduzimo-lo para cerca de 124 KB. Hoje está indexado, com 127 951 bytes. Mesmo URL, mesmo modelo, mesmo tipo de conteúdo: grande demais, depois pequeno que chegue.
Há uma semana eu ter-lhe-ia dado outro número, e teria estado errado. Tinha três leituras de estado e um buraco bem visível na distribuição dos nossos tamanhos, e li ali um limite perto dos 125 KB. Duas dessas páginas foram entretanto indexadas com pouco menos de 128 KB. O que o nosso próprio site pode mesmo dizer é que a linha está entre 128 e 140 KB, e que o outro lado dela continua a ter exatamente uma página de largura.
Se veio à procura de um número, a resposta honesta é medir o seu. A afirmação que consigo defender é mais estreita e mais útil de qualquer maneira: uma página parada em descoberta merece ser pesada antes de ser reescrita, e uma página que começa a ser rastreada depois de emagrecer é a única prova que conta a sério.
Pesar o ficheiro de origem não diz nada
Aqui está a parte específica dos React Server Components. O App Router serializa a árvore renderizada dentro do mesmo documento HTML, como uma série de etiquetas self.__next_f.push(...), para que uma navegação do lado do cliente possa retomar sem outra ida e volta. Esse payload não é um pedido separado que um rastreador possa saltar. Está no documento.
Medido numa das nossas páginas: 125 981 bytes servidos, dos quais 73 571 são esse payload. Cinquenta e oito por cento do documento é a cópia serializada daquilo que os outros quarenta e dois por cento já dizem.
Portanto uma classe utilitária escrita uma só vez dentro de um ciclo de 24 linhas não é enviada 24 vezes. É enviada 48.
A parte que demorei mais a ver
Não duplica a página. Duplica cada representação que a página já transporta.
A nossa roadmap renderizava de propósito cada entrada duas vezes: uma como texto visível, outra dentro de um bloco ItemList de JSON-LD, para que os leitores máquina recebessem o nome, a data e o estado como dados em vez de prosa a interpretar. Razoável. Depois contei as ocorrências da descrição de uma única entrada no documento servido e encontrei quatro.
Duas representações, cada uma duplicada. Quatro cópias da mesma frase, e nunca me tinha lembrado de contar.
A correção não era comprimir. O JSON-LD levava uma description que copiava palavra por palavra um texto que o leitor vê trinta linhas mais abaixo, na mesma língua. Não trazia nada e custava duas das quatro cópias. Removida, o bloco mantém o nome, a data e o estado, que é precisamente o que a prosa não diz explicitamente a uma máquina. Essa página passou de 121 196 para 115 350 bytes sem que saísse nenhuma informação. Os dois números vêm do mesmo build, antes e depois dessa única alteração, que é a única forma de um antes e depois querer dizer alguma coisa.
Se levar daqui um só hábito: antes de tentar aligeirar seja o que for, conte quantas vezes uma frase do seu conteúdo aparece no documento servido. Responde a uma pergunta a que pesar as secções não responde.
As duas alavancas maiores
Os atributos de classe repetidos, passados a regras de folha de estilo. Os utilitários do Tailwind são maravilhosamente baratos de escrever, e seguem em cada elemento, duas vezes. Mover os mais repetidos para regras @apply levou o nosso índice de preços de 172 295 bytes para cerca de 142 000. Eram à volta de 51 por cento da marcação.
Os blocos não interativos, fora da árvore serializada. Para ser preciso, porque isso importa: são Server Components, logo nunca são hidratados no cliente. O custo não é a hidratação, é a serialização. Cada elemento, cada prop e cada chave é escrito no fluxo para que uma navegação do lado do cliente possa reconstruir a árvore sem ida e volta. Uma tabela estática sem qualquer comportamento de cliente paga isso na mesma, e quando a restrição é o tamanho do documento não há razão para continuar a ser elementos React. Passámos a construir esses blocos como uma cadeia HTML a partir dos mesmos dados, colocada com dangerouslySetInnerHTML, escapando cada valor interpolado no momento da construção para que o escape não dependa da proveniência do dado. Mais 18 KB no índice de preços. Uma lista de 54 linhas na roadmap passou de 157 308 para 119 031 pelo mesmo caminho.
O que não resultou
Antes de tudo isso, tinha a certeza de que o problema estava nas fronteiras de componente. Cinquenta e quatro componentes next/link numa lista, cada um a acrescentar o seu nome serializado, as suas props e as suas chaves ao payload. Substituí os 54 por âncoras simples.
Poupou 108 bytes em 157 200.
O Link é barato. O conteúdo que ele envolve não é. Teria passado um dia a reescrever componentes com base nessa teoria se não tivesse medido antes e depois, e esse resultado negativo foi o número mais útil do dia.
Há um chão, e não é baixo
A nossa página real mais leve, /contact, serve 46 111 bytes com apenas um título, um formulário curto e algumas perguntas. O resto é navegação, rodapé, analítica e tipos de letra. Abaixo desse número já não há nada a ganhar página a página: seria um trabalho de casco, não de página. Conhecer o chão diz-lhe quando parar.
Uma tradução custa bytes, não palavras
Pusemos essa roadmap em dez línguas. Mesma marcação, mesma estrutura, mesma informação. A página inglesa serve 116 239 bytes, a francesa 119 838, a japonesa 122 155 e a hindi 135 379.
O devanágari ocupa três bytes por carácter em UTF-8 onde o texto latino ocupa um, e o payload paga-o uma segunda vez. Nenhuma técnica ao nível da página toca nisso: mesma marcação, mesma informação, dezanove kilobytes de diferença.
A página hindi é a interessante, porque com 135 379 bytes cai mesmo dentro da faixa que não conseguimos resolver. Saber se o Bing a processa é a experiência mais barata de que dispomos, e custa uma inspeção de URL.
Tinha escrito aqui um parágrafo a defender que esse excesso não importava muito, por cair no fim do documento e por o fim dessa página ser uma lista cujos títulos ficam em inglês de qualquer forma. Cortei-o, porque pressupõe que o rastreador lê até esgotar o seu orçamento e então para. Tudo o que observei de facto diz apenas que as páginas acima de um certo tamanho não são processadas. Se o documento é rejeitado por inteiro, a sua ordem não compra nada, e não tenho prova em nenhum dos sentidos. É uma teoria confortável sobre a minha própria página, e é desse tipo que mais vale apagar.
É um orçamento, não uma correção
O índice de preços que levei de 172 295 para 124 202 bytes está hoje em 127 951, porque continuámos a acrescentar-lhe coisas: mais ferramentas, mais colunas, dez traduções. Continua indexado, que é o essencial, e também voltou a ficar a poucos kilobytes da linha. Nada correu mal. Simplesmente voltámos a gastar o orçamento sem o vigiar.
É essa a conclusão a sério. Num framework que serializa a sua própria saída, o peso de uma página não é uma limpeza que se faz uma vez. É um número que sobe de cada vez que alguém faz um bom trabalho, e a única defesa é medir o documento servido em vez da origem, uma alteração de cada vez.
Como medir as suas
- Peça o URL e conte os bytes do corpo da resposta, sem compressão. É o que o rastreador recebe, e não é o que o seu ficheiro de origem sugere.
- Conte as ocorrências de uma frase distintiva do seu conteúdo nesse corpo. Mais do que uma significa que a está a pagar mais do que uma vez, e aponta para o duplicado.
- Volte a medir depois de cada alteração, separadamente. Duas alterações ao mesmo tempo escondem aquela que não fez nada.
- Depois releia o estado no Bing Webmaster Tools, no mesmo URL. Um limite deduzido das páginas dos outros é um palpite. Uma página sua que começa a ser rastreada depois de emagrecer é uma medição.
Artigos relacionados
Pronto para transformar seus vídeos em clipes virais?
O Katto corta, legenda e reenquadra automaticamente seus vídeos longos em conteúdo de formato curto.
Experimente o Katto grátis →