Melhorando o desempenho do site ao enviar mais CSS
Há um instinto arraigado no trabalho de frontend moderno que diz que a página mais rápida envia o mínimo de tudo: menos JavaScript, menos fontes, menos requisições. Então, quando a equipe de engenharia do GitHub escreveu sobre melhorar o desempenho do site enviando mais CSS, isso soou como uma contradição. A correção para uma interface lenta e pesada em JavaScript não foi remover o estilo do aplicativo. Foi recolocar mais estilo nele, como folhas de estilo simples, estáticas e armazenáveis em cache, e excluir o JavaScript que existia apenas para gerar estilos no navegador.
A distinção é o cerne do artigo. Enviar "mais CSS" não significa uma folha de estilos maior inchando seu caminho crítico. Significa mover a geração de estilos do runtime para o build e, então, deixar o navegador fazer aquilo em que ele é genuinamente excelente: analisar uma folha de estilos uma vez, armazená-la em cache e reutilizá-la em cada navegação.
O custo oculto da estilização em runtime
As bibliotecas CSS-in-JS resolveram problemas reais. Elas deram aos autores de componentes escopo, co-localização e a capacidade de interpolar props nos estilos sem inventar uma convenção de nomenclatura. A troca foi arquitetural: elas moveram um problema de tempo de compilação para o navegador.
A geração de estilos é trabalho de JavaScript
Toda página renderizada no servidor que usa uma biblioteca CSS-in-JS em runtime pede ao navegador que faça o mesmo trabalho duas vezes. O servidor serializa os estilos em tags <style>, então a biblioteca do cliente desperta, percorre a árvore de componentes, faz hash dos nomes de classe novamente e reconcilia o resultado. Esse trabalho acontece na thread principal, competindo com as próprias etapas de layout e pintura que você está tentando acelerar.
// Runtime CSS-in-JS: this stylesheet does not exist until hydration runs
const Button = styled.button`
background: ${(p) => (p.primary ? "#1f883d" : "transparent")};
border: 1px solid #d0d7de;
padding: 6px 16px;
border-radius: 6px;
font: inherit;
`;
O mesmo componente expresso como uma folha de estilos de tempo de build remove tanto a dependência de runtime quanto o trabalho de serialização:
/* button.module.css, parsed once, cached forever */
.button {
padding: 6px 16px;
border: 1px solid #d0d7de;
border-radius: 6px;
font: inherit;
background: transparent;
}
.buttonPrimary {
background: #1f883d;
color: #ffffff;
}
import styles from "./button.module.css";
export function Button({ primary, ...props }) {
const className = primary
? `${styles.button} ${styles.buttonPrimary}`
: styles.button;
return <button className={className} {...props} />;
}
Nada aqui é novo. O que é fácil subestimar é quanto de um bundle uma pilha CSS-in-JS madura possui silenciosamente: a própria biblioteca, os utilitários de interpolação de props, o provedor de tema, o serializador e o código duplicado de framework que cresce quando cada pacote depende de sua própria cópia.
Ela fica diretamente no caminho crítico de renderização
Um <link rel="stylesheet"> bloqueia a renderização, mas bloqueia uma vez, e o navegador pode analisar o arquivo enquanto o fluxo HTML ainda está chegando. O CSS em runtime funciona de forma diferente: o navegador não pode nem começar a construir os estilos dos componentes até que o JavaScript tenha sido baixado, analisado, compilado e executado. Em um celular de categoria intermediária, isso é um segundo ou mais de trabalho na thread principal antes que um único botão receba sua cor de fundo.
Ela fragmenta seu cache
Nomes de classe gerados mudam sempre que o código-fonte do componente muda, o que significa que a folha de estilos é invalidada em quase todo deploy. Divida isso entre dezenas de bundles no nível de rota e você terá uma taxa de acerto de cache que parece saudável em um painel, mas raramente é acionada em visitas repetidas. Uma folha de estilos estática com hash de conteúdo, em contraste, pode ser servida com Cache-Control: public, max-age=31536000, immutable e efetivamente nunca ser baixada duas vezes.
Ela dobra o trabalho durante a hidratação
Incompatibilidades de hidratação entre estilos injetados pelo servidor e gerados pelo cliente são uma fonte clássica de cintilação. O navegador pinta um conjunto de regras, então a biblioteca do cliente as substitui no meio da interação, forçando um recálculo de estilo e, às vezes, um deslocamento de layout. Nomes de classe estáticos calculados no tempo de build são idênticos dos dois lados, então a primeira pintura também é a pintura final.

O que o GitHub realmente mudou
A migração é melhor entendida como cinco movimentos arquiteturais, e não como uma única refatoração. Nenhum deles exige abandonar um modelo de componentes, e nenhum exige uma reescrita completa.

Os tokens de design se tornaram propriedades personalizadas do CSS
Escalas de cor, espaçamento, raio e tipografia foram movidas para um bloco global :root como propriedades personalizadas. Os componentes fazem referência a var(--color-bg-default) em vez de importar um objeto de tema JavaScript. É isso que torna o CSS estático expressivo o suficiente para substituir estilos dinâmicos na maioria dos casos, e permite temas em runtime (modo escuro, alto contraste) com zero JavaScript.
Estilos de componentes compilados antecipadamente
Cada componente ganhou uma folha de estilos real, emitida no tempo de build e com escopo definido por um hash ou convenção de módulo. A saída é CSS comum que qualquer navegador pode analisar na velocidade de análise.
CSS crítico embutido no head do documento
Apenas as regras necessárias para a primeira viewport são embutidas no HTML. Isso normalmente representa 8–14 KB compactados com gzip, e significa que a primeira pintura nunca espera uma ida e volta de rede pela folha de estilos que molda a página visível.
<head>
<!-- Critical: exactly what the first viewport needs, nothing more -->
<style>
/* reset + tokens + header + hero + above-the-fold grid */
</style>
<!-- Everything else: one hashed, immutable, long-cached file -->
<link
rel="stylesheet"
href="/assets/app.8f3a1c2d.css"
media="print"
onload="this.media='all'"
/>
<noscript>
<link rel="stylesheet" href="/assets/app.8f3a1c2d.css" />
</noscript>
</head>
A marcação renderizada no servidor carrega os nomes de classe finais
Como os nomes de classe existem no tempo de build, o HTML que o servidor retorna já está estilizado. Não há segunda passagem, nem etapa de reconciliação, nem flash de conteúdo sem estilo em conexões lentas.
Camadas de cascata substituíram hacks de especificidade
Um sistema de design compartilhado vive ou morre pela ordem de cascata previsível. Declarar a ordem das camadas uma vez, desde o início, torna as substituições determinísticas e remove o incentivo de recorrer a !important ou a seletores cada vez mais profundos.
@layer reset, tokens, base, components, utilities;
@layer components {
.card {
background: var(--color-canvas-default);
border: 1px solid var(--color-border-default);
border-radius: var(--radius-2);
padding: var(--space-4);
}
}
@layer utilities {
.mt-4 { margin-top: var(--space-4); }
}
Os números: o que muda quando você envia mais CSS
Mover a geração de estilos para o tempo de build muda o formato do que você envia, não apenas o volume. Os bytes que permanecem são baratos: eles comprimem bem, são compartilhados entre todas as rotas e ficam em cache por um ano.

O retorno arquitetural aparece em quatro lugares ao mesmo tempo: menos JavaScript para baixar, menos trabalho na thread principal antes da primeira pintura, uma folha de estilos armazenável em cache compartilhada entre navegações e marcação idêntica no servidor e no cliente.
| Approach | JS shipped (gzip) | CSS shipped (gzip) | Runtime style work | Cacheable across deploys |
|---|---|---|---|---|
| Runtime CSS-in-JS | ~180 KB | ~12 KB | High | No |
| Single monolithic stylesheet | ~96 KB | ~88 KB | None | Yes |
| Inline critical + split static sheets | ~78 KB | ~54 KB | None | Yes |
| Utility-first CSS | ~85 KB | ~40 KB | None | Yes |
A folha de estilos mais rápida é aquela que o navegador analisa uma vez, armazena em cache por um ano e nunca precisa gerar novamente.
Um guia prático de migração
Você não precisa da base de código do GitHub para aplicar o mesmo raciocínio. A migração é incremental e cada etapa pode ser enviada de forma independente.

1. Faça um inventário do que realmente é renderizado em runtime
Abra o DevTools, carregue uma página e conte document.styleSheets. Se você encontrar dezenas de tags <style> injetadas com nomes de classe com hash, você encontrou seu alvo. Faça referência cruzada com o painel Performance: procure tarefas longas entre DOMContentLoaded e a primeira pintura com conteúdo.
2. Extraia o CSS acima da dobra
Comece com o menor bloco embutido possível. Uma heurística confiável é embutir o reset, os tokens e as regras usadas pelas duas primeiras alturas de viewport. Tudo abaixo da dobra carrega a partir da folha de estilos adiada.
3. Converta componentes de runtime em classes estáticas
Trabalhe componente por componente, começando pelos de maior tráfego. Variantes controladas por props se tornam classes modificadoras ou propriedades personalizadas definidas em um wrapper. Mantenha a API do componente idêntica para que os locais de chamada não mudem.
// Genuinely dynamic values still belong in an inline style, not a generated sheet
<div
className={styles.progressBar}
style={{ "--progress": `${percent}%` }}
role="progressbar"
aria-valuenow={percent}
/>
.progressBar {
width: var(--progress, 0%);
transition: width 200ms ease-out;
}
Esse padrão é o cavalo de batalha de toda a migração: a estrutura do estilo é estática e armazenável em cache, enquanto o valor permanece dinâmico e não custa nada atualizar.
4. Estabeleça a ordem das camadas desde cedo
Declare a ordem de @layer no topo da sua folha de estilos de entrada antes de ter um problema de especificidade, não depois. Adaptar camadas a uma cascata legada é muito mais doloroso do que começar com elas.
5. Aplique hash aos nomes de arquivo e armazene em cache de forma imutávelHashing de conteúdo mais immutable é o que transforma “mais CSS” em “CSS de graça”. A segunda visualização de página em uma sessão deve atingir o cache HTTP para cada regra de estilo do site.
6. Mantenha o estilo verdadeiramente dinâmico como dinâmico
Transform, posição de rolagem e valores orientados por medição pertencem a estilos inline, requestAnimationFrame ou à Web Animations API. Não reintroduza um mecanismo de estilo em runtime para resolver um problema que as propriedades personalizadas do CSS já resolvem.
Como medir a melhoria
Meça as mesmas três coisas antes e depois: tempo de pintura, tarefas longas na thread principal e contagem de folhas de estilo. Um pequeno script com PerformanceObserver fornece todas as três sem adicionar uma dependência de monitoramento.
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log(entry.name, Math.round(entry.startTime));
}
}).observe({ type: "paint", buffered: true });
new PerformanceObserver((list) => {
const long = list.getEntries().filter((e) => e.duration > 50);
console.log(`${long.length} long tasks after load`);
}).observe({ type: "longtask", buffered: true });
console.log("stylesheets:", document.styleSheets.length);
Espere a maior mudança no tempo até a primeira pintura em visitas repetidas e em perfis móveis com limitação de desempenho, onde o custo eliminado de análise de JavaScript domina. O Largest Contentful Paint tende a melhorar mais gradualmente, a cada versão.

Armadilhas que vale a pena evitar
A abordagem não é isenta de riscos, e a maioria das falhas vem de aplicá-la de forma agressiva demais.

- **CSS crítico incompleto.**Se você errar sobre a primeira viewport, terá um reflow visível quando a folha adiada chegar. Gere o bloco crítico automaticamente a partir de uma renderização real, em vez de manualmente. -**Inlinar tudo.**Inlinar a folha de estilo completa em cada resposta HTML anula o cache e infla o tamanho do documento. Inline o mínimo, adie o restante. -**Instinto de empacotar tudo.**O conselho antigo de concatenar todo o CSS em um único arquivo gigante é anterior ao HTTP/2. Dividir por rota costuma ser melhor agora, desde que as primitivas compartilhadas sejam armazenadas em cache separadamente. -Misturar dois sistemas. Executar CSS-in-JS em runtime e folhas de estilo estáticas lado a lado por tempo demais dobra sua superfície de estilos e torna a depuração da cascata miserável. Defina um prazo de migração por diretório de componentes.
A armadilha não técnica mais comum é tratar isso como uma reescrita big-bang. As equipes que têm sucesso convertem um punhado de componentes de alto tráfego, medem o delta e usam esses dados para justificar o próximo lote.
Usando o DivMagic para fazer engenharia reversa de arquitetura CSS real
Uma parte subestimada dessa migração é ver como sites de produção maduros realmente estruturam suas folhas de estilo. Quando você inspeciona um site que entrega CSS estático bem, pode aprender mais em cinco minutos do que em uma hora lendo documentação: como eles estruturam as camadas da cascata, como escopam tokens, como separam o crítico do adiado e como nomeiam modificadores.
O DivMagic é feito exatamente para esse fluxo de trabalho. Em vez de copiar manualmente estilos de elementos do DevTools, uma regra por vez, você seleciona qualquer UI em qualquer página e puxa o HTML e o CSS resultantes diretamente para o seu editor, prontos para adaptação. Isso é genuinamente útil em duas situações: prototipar um componente a partir de um design de referência e estudar como um site de alto desempenho que você admira organiza sua cascata, para espelhar o padrão no seu próprio design system.
Isso também encurta a parte chata da etapa três acima. Quando você está convertendo um componente em runtime em classes estáticas, ter os estilos computados finais em formato editável, em vez de estilos inline espalhados e blocos <style> injetados, elimina a maior parte do trabalho de transcrição.
Principais conclusões
- A geração de estilos em runtime é trabalho de JavaScript na thread principal que acontece após o download do JavaScript, diretamente no seu caminho crítico de renderização.
- O CSS estático é analisado uma vez, armazenado em cache por um ano quando tem hash de conteúdo e compartilhado entre todas as rotas e navegações.
- Inclua inline apenas as regras de que a primeira viewport precisa; adie o restante por trás de um
<link>não bloqueante e uma política de cache longa. - Propriedades personalizadas do CSS mais
@layercobrem a grande maioria dos casos que antes exigiam interpolação de estilos orientada por props. - Migre de forma incremental, componente por componente, e meça o tempo de pintura p75 e tarefas longas em vez de médias.
Entregar mais CSS parece uma regressão até você perceber que está trocando uma folha de estilo armazenada em cache e analisada uma única vez por megabytes de JavaScript que a reconstruíam a cada carregamento de página.
A lição se generaliza muito além do GitHub. Toda abstração que roda no navegador é um custo pago pelo usuário, no dispositivo dele, na rede dele, todas as vezes. Mover a geração de estilos para o momento de build é uma das trocas de maior alavancagem disponíveis para um frontend orientado a componentes: você entrega um arquivo estático que o navegador já sabe manipular e elimina a maquinaria que costumava atrapalhar.

