Alternar tema

Otimização de desempenho do Tailwind: JIT, configuração de content e controle do tamanho em produção

Easton editorial illustration: signal tracing instrument

Você abre o Chrome DevTools e encontra um arquivo CSS de 3,5 MB.

Na página de um recurso recém-lançado, o tempo de carregamento tinha subido de 800 ms para 3,2 segundos. Depois de investigar o problema por todos os lados, descobri que a causa estava justamente nesse arquivo CSS, lotado de classes do Tailwind que não eram usadas em lugar nenhum.

O Tailwind não dizia ser “amigável ao desempenho”? Como ele acabou fazendo o contrário?

Só depois entendi que o problema não estava no Tailwind, mas na configuração. Quando você entende como o modo JIT funciona, acerta a configuração de content e acrescenta algumas camadas de otimização à build de produção, é possível reduzir o CSS da casa dos MB para a dos KB — o site da Netflix chegou a usar apenas 6,5 KB.

Hoje quero compartilhar os erros que cometi nesse caminho.


1. Modo JIT: a revolução de desempenho do Tailwind

1.1 O problema do modo tradicional

Antes de encontrar aquele arquivo CSS de 3,5 MB, meu entendimento do Tailwind ainda se limitava à ideia de um “framework CSS utility-first”.

Depois descobri que, antes do Tailwind v2, o “modo tradicional” gerava antecipadamente todas as combinações possíveis de classes — incluindo todas as cores, todos os espaçamentos e todas as variantes, como hover, focus e disabled. Em um projeto de complexidade média, o arquivo CSS no ambiente de desenvolvimento podia chegar a 10 MB ou mais.

10 MB+
Tamanho do CSS de desenvolvimento no modo tradicional

Um arquivo CSS de 10 MB pode não parecer um problema no desenvolvimento local, já que a largura de banda não é uma preocupação. Ainda assim, o navegador precisa analisar uma folha de estilos tão grande, o que afeta o uso de memória e o desempenho do DevTools.

Eu mesmo já passei por isso ao depurar no Firefox: mudar uma classe travava o DevTools por vários segundos, e atualizar a página era lento a ponto de testar a paciência.

O mais incômodo era que, no modo tradicional, dava medo de alterar muitas configurações. Se você quisesse adicionar um novo breakpoint ou ativar a variante focus-visible, precisava primeiro calcular mentalmente quantas combinações de classes aquilo acrescentaria. No fim, a equipe era forçada a escolher entre desempenho e flexibilidade.

1.2 Como o JIT realmente funciona

O modo JIT (Just-in-Time) foi introduzido no Tailwind v2.1 e vem ativado por padrão a partir da v3. Em resumo, ele gera sob demanda.

O modo tradicional cria durante a build todas as combinações de classes possíveis, mesmo que você nunca as use. O modo JIT faz o inverso: primeiro examina os arquivos de template, como HTML, JSX e Vue, identifica quais classes realmente aparecem neles e gera apenas esses estilos.

Veja um exemplo. No modo tradicional, o Tailwind geraria um CSS como este:

.bg-black { background-color: #000 }
.hover\:bg-black:hover { background-color: #000 }
.focus\:bg-black:focus { background-color: #000 }
.disabled\:bg-black:disabled { background-color: #000 }
/* ... além de dezenas de combinações de variantes */

Mesmo que seu projeto usasse apenas bg-black, os estilos de dezenas de outras variantes também seriam gerados.

No modo JIT é diferente. Ele examina seus templates, encontra apenas bg-black e hover:bg-black e gera somente estas duas regras:

.bg-black { background-color: #000 }
.hover\:bg-black:hover { background-color: #000 }

Assim, o arquivo CSS de desenvolvimento cai diretamente da casa dos 10 MB para a dos KB — e fica tão pequeno quanto o arquivo de produção.

1.3 Benefícios reais do JIT

Lembro que, na primeira vez em que ativei o modo JIT em um projeto, a página atualizou tão rápido que parecia até estranho: antes era preciso esperar vários segundos; depois, a resposta passou a ser quase instantânea.

Velocidade da build: antes, uma build completa levava de dois a três minutos; com o JIT, terminava em poucos segundos. Isso acontece porque ele não precisa gerar todos os estilos antecipadamente, apenas examinar os templates para extrair os nomes das classes.

Experiência de desenvolvimento: o DevTools parou de travar. Antes, ao mudar uma classe, era preciso esperar o navegador analisar novamente aquela folha de estilos de 10 MB; agora, ela tem apenas alguns KB e as alterações aparecem quase na hora.

Suporte a valores arbitrários: foi uma boa surpresa. No modo tradicional, para usar um valor arbitrário como text-[#facc15], era preciso habilitá-lo antes na safelist. O modo JIT oferece suporte direto:

// Não é preciso predefinir na configuração: basta usar
<h1 class="text-[2.5rem] mt-[1.35rem] text-[#facc15]">
  O JIT deixa tudo mais simples
</h1>

Nomes de classe dinâmicos: no modo tradicional, classes concatenadas como 'text-' + color não podiam ser detectadas. O modo JIT também tem limitações, mas permite lidar com cenários dinâmicos de maneira mais flexível quando combinado com safelist.

Depois de tudo isso, você talvez esteja se perguntando como ativar o modo JIT. Hoje, no Tailwind v3+, ele já é o padrão e não exige nenhuma configuração adicional. Se você ainda usa a v2, pode ativá-lo assim:

// tailwind.config.js
module.exports = {
  mode: 'jit',  // Na v2, é preciso ativar manualmente
  content: ['./src/**/*.{html,js,jsx,ts,tsx}'],
  // ...
}

2. Configuração de content: o segredo de uma varredura precisa

O modo JIT funciona muito bem, desde que a configuração de content esteja correta.

Content determina quais arquivos o Tailwind examina para extrair os nomes das classes. Uma configuração incorreta pode causar perda de estilos, quando o escopo é estreito demais, ou aumento do CSS, quando é amplo demais.

2.1 Fundamentos da configuração de content

A sintaxe básica é simples:

// tailwind.config.js
module.exports = {
  content: [
    './src/**/*.{html,js,jsx,ts,tsx}',  // Examina todos os arquivos de template em src
  ],
  // ...
}

Aqui, ** representa diretórios em qualquer nível, enquanto *.{html,js,jsx,ts,tsx} corresponde aos arquivos com essas extensões.

Se você usa um framework específico, talvez precise ajustar os caminhos:

// Projeto Next.js
content: [
  './pages/**/*.{js,ts,jsx,tsx}',
  './components/**/*.{js,ts,jsx,tsx}',
  './app/**/*.{js,ts,jsx,tsx}',  // App Router
]

// Projeto Astro
content: [
  './src/**/*.{astro,html,js,jsx,ts,tsx}',
]

O ponto principal é: inclua todos os arquivos que usam classes do Tailwind. Se um diretório ficar de fora, os estilos usados nele não serão gerados.

2.2 Os erros que cometi

Erro 1: um padrão glob amplo demais

Na época, para deixar a configuração “simples”, usei isto:

content: [
  './**/*.js',  // Isso examina node_modules!
]

O resultado foi que o Tailwind examinou todos os arquivos JS em node_modules. O tempo de build disparou e ainda foram gerados vários estilos que não faziam sentido.

A forma correta é restringir o escopo:

content: [
  './src/**/*.js',      // Examina apenas o diretório src
  './components/**/*.js', // Examina apenas o diretório components
]

Erro 2: esquecer o diretório de componentes

Em uma refatoração, movi os componentes para ./lib/components/. Esqueci de atualizar a configuração de content, e todos os estilos dos componentes no novo local desapareceram.

Levei um bom tempo para encontrar a causa. A lição é: sempre atualize a configuração de content quando mudar a estrutura do projeto.

Erro 3: nomes de classe criados dinamicamente

Considere este código:

const color = 'red';
const className = `text-$&#123;color&#125;-500`;  // O JIT não consegue detectar

Ao examinar o arquivo, o JIT encontra uma template string, e não o nome completo da classe. Por isso, esse estilo foi removido da build de produção.

A solução é usar safelist, como veremos adiante, ou um objeto de mapeamento:

const colors = {
  red: 'text-red-500',
  blue: 'text-blue-500',
};
const className = colors[color];  // Nome completo, que pode ser detectado

2.3 Safelist e nomes de classe dinâmicos

Alguns casos realmente exigem classes dinâmicas. É aí que entra a safelist.

// tailwind.config.js
module.exports = {
  safelist: [
    'text-red-500',
    'text-blue-500',
    'bg-red-500',
    // Ou use uma expressão regular para corresponder a um grupo de classes
    {
      pattern: /text-(red|blue|green)-(500|600)/,
      variants: ['hover', 'focus'],  // Preserva as variantes também
    },
  ],
}

A safelist força o Tailwind a gerar esses estilos, mesmo que eles não apareçam diretamente nos arquivos de template.

Mas atenção: quanto maior a safelist, maior o arquivo CSS. Use-a apenas quando for necessário; não a trate como um “cofre” onde cabe toda classe que talvez venha a ser usada.


3. Controle do tamanho em produção: estratégia de otimização em quatro camadas

O modo JIT possibilita um CSS pequeno no ambiente de desenvolvimento, mas a build de produção ainda precisa de outras otimizações.

Organizei uma estratégia em quatro camadas, avançando da configuração até a compactação.

Primeira camada
Configuração precisa de content
Examina apenas os arquivos realmente usados
Segunda camada
Remoção com PurgeCSS
Exclui automaticamente os estilos não utilizados
Terceira camada
Minify com cssnano
Compacta e otimiza o CSS
Quarta camada
Brotli/Gzip
Compacta a transferência pela rede
Source: Camadas da estratégia de otimização

3.1 Primeira camada: configuração precisa de content

Essa é a base que já vimos.

O princípio central é: examine apenas os arquivos que realmente usam classes do Tailwind. Quanto mais preciso o escopo, mais rápida a build e menor o CSS.

// Bom: escopo preciso
content: [
  './src/components/**/*.jsx',
  './src/pages/**/*.tsx',
]

// Ruim: escopo amplo demais
content: [
  './**/*.js',  // Examina node_modules
]

3.2 Segunda camada: remoção automática com PurgeCSS

O Tailwind v3+ ativa automaticamente o PurgeCSS nas builds de produção para remover estilos não utilizados.

O importante é garantir que os comandos de build diferenciem corretamente os ambientes de desenvolvimento e produção:

# Build de desenvolvimento (não remove os estilos não utilizados)
npm run dev

# Build de produção (aplica o PurgeCSS automaticamente)
npm run build

Se você usa PostCSS, pode controlar isso explicitamente na configuração:

// postcss.config.js
module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
    ...(process.env.NODE_ENV === 'production' ? { cssnano: {} } : {}),
  },
}

3.3 Terceira camada: minify com cssnano

Depois que o PurgeCSS remove os estilos não utilizados, o cssnano compacta ainda mais o CSS.

Essa compactação inclui remover comentários, combinar regras repetidas, simplificar seletores e reduzir valores, entre outras ações.

# Minify direto com a CLI do Tailwind
npx tailwindcss -i ./src/input.css -o ./dist/output.css --minify

# Ou pelo PostCSS, como mostrado acima

Um dado real: em meu projeto, o arquivo caiu de 150 KB, depois do PurgeCSS, para 45 KB após o minify.

3.4 Quarta camada: compactação de rede com Brotli/Gzip

Esta camada não otimiza o CSS em si, mas a transferência pela rede.

Quando o servidor compacta os recursos estáticos com Brotli ou Gzip, o arquivo CSS pode ficar de 60% a 80% menor.

# Configuração Brotli no Nginx (requer o módulo ngx_brotli)
brotli on;
brotli_comp_level 6;
brotli_types text/css application/javascript;

# Ou use Gzip (incluído no Nginx por padrão)
gzip on;
gzip_comp_level 6;
gzip_types text/css application/javascript;
6,5 KB
Tamanho de transferência do CSS da Netflix

Para comparar: um arquivo CSS de 45 KB fica com aproximadamente 8 KB depois da compactação Brotli.

Portanto, quando você lê que o CSS da Netflix tinha apenas 6,5 KB, esse número representa o tamanho transferido após a compactação Brotli, e não o tamanho do arquivo original.


4. Caso prático e comparação dos dados

4.1 Comparação antes e depois da otimização

3,5 MB → 28 KB
CSS da build de desenvolvimento
Antes e depois de ativar o modo JIT
320 KB → 48 KB
CSS da build de produção
Tamanho sem compactação
7,2 KB
Tamanho final transferido
Após compactação Brotli
3,2 s → 820 ms
LCP no Lighthouse
Desempenho de carregamento da página
Source: Dados medidos

Sinceramente, até eu me surpreendi quando vi essa comparação.

4.2 Solução de problemas comuns

O que fazer quando os estilos desaparecem?

Primeiro: verifique a configuração de content e confirme que todos os arquivos que usam classes do Tailwind estão no escopo da varredura.

Segundo: se houver nomes de classe dinâmicos, verifique se é necessário usar safelist.

Terceiro: confira o comando de build. Confirme que você está usando a build de produção, com npm run build, e não a de desenvolvimento.

E se o CSS continuar muito grande?

Verifique se a safelist tem itens demais, se o CSS de alguma biblioteca de terceiros foi incluído no artefato do Tailwind e se o escopo de content está amplo demais.

E se o DevTools continuar travando?

Confirme que a versão do Tailwind é ≥3, na qual o JIT já vem ativado por padrão, e verifique se a configuração de content é precisa. Se você ainda usa a v2, atualize-a ou ative o modo JIT manualmente.


5. Novos recursos do Tailwind v4

Depois das otimizações atuais, vale conhecer as mudanças do Tailwind v4, lançado no fim de 2024.

5.1 Mecanismo Oxide

Esta é a atualização mais empolgante: o Tailwind reescreveu o mecanismo interno em Rust.

182x
Aumento da velocidade da build incremental

Segundo os dados oficiais, a build incremental ficou 182 vezes mais rápida. Em termos simples, antes era preciso esperar alguns segundos pela recompilação depois de mudar uma classe; agora, a resposta acontece praticamente em milissegundos.

Isso é possível porque o mecanismo Oxide armazena em cache os resultados da varredura dos arquivos e reprocessa apenas as partes alteradas. É especialmente útil em projetos grandes: em um de nossos projetos, com mais de 200 arquivos de componentes, cada build levava cerca de 10 segundos; agora, termina em poucos segundos.

5.2 A meta de configuração zero

O Tailwind v4 introduz uma nova ideia: a maioria dos projetos já não precisa de tailwind.config.js.

A configuração padrão atende às necessidades comuns, incluindo recursos modernos de CSS, container queries, breakpoints adequados e um sistema completo de cores. Só é necessário criar um arquivo de configuração quando o projeto exige personalizações profundas.

Isso acelera o início de projetos novos e reduz as oportunidades de errar na configuração.

Mas atenção ao migrar para a v4: a sintaxe de configuração mudou. Por exemplo, theme.extend.colors agora deve ser escrito com propriedades personalizadas do CSS. Antes de migrar, vale consultar o guia oficial de atualização.


Conclusão

Depois daquela pane às três da manhã, revisei toda a configuração do Tailwind no projeto.

O modo JIT mantém o CSS igualmente pequeno em desenvolvimento e produção, a configuração de content define o escopo da varredura e as quatro camadas de otimização reduzem o CSS da casa dos MB para a dos KB. Com o mecanismo Oxide do Tailwind v4, a velocidade da build também deixa de ser um gargalo.

Se seu projeto ainda usa o modo tradicional ou se você tem dúvidas sobre a configuração de content, comece por estes pontos:

  1. A versão do Tailwind é ≥3, com o JIT ativado por padrão?
  2. A configuração de content inclui com precisão todos os arquivos de template?
  3. A build de produção ativa PurgeCSS e cssnano?
  4. O servidor está configurado para compactação Brotli/Gzip?

Depois dessas mudanças, é muito provável que você veja uma melhoria clara de desempenho.

Se tiver alguma dúvida, deixe um comentário. Você também pode consultar as seções sobre JIT e otimização para produção na documentação oficial do Tailwind, que são bastante claras.



Referências

Configuração para otimizar o desempenho do Tailwind CSS

Processo completo, da ativação do modo JIT ao controle do tamanho em produção.

⏱️ Estimated time: 30 min

  1. 1

    Step 1: Verifique a versão do Tailwind

    Confira a versão do Tailwind CSS no projeto:

    • O Tailwind v3+ ativa o modo JIT por padrão
    • No Tailwind v2, adicione mode: 'jit' manualmente à configuração
    • Recomenda-se atualizar para a v3+ para obter o melhor desempenho
  2. 2

    Step 2: Configure os caminhos de varredura de content

    Defina caminhos de varredura precisos em tailwind.config.js:

    • Next.js: ['./pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}']
    • Astro: ['./src/**/*.{astro,html,js,jsx,ts,tsx}']
    • Evite padrões glob amplos demais, como './**/*.js'
  3. 3

    Step 3: Trate nomes de classe dinâmicos

    Para nomes de classe criados dinamicamente, use safelist para forçar a geração:

    • Adicione os nomes de classe completos ao array safelist
    • Use uma expressão regular em pattern para corresponder a um grupo de classes
    • Use variants para preservar variações como hover e focus
  4. 4

    Step 4: Configure a otimização da build de produção

    Adicione cssnano à configuração do PostCSS:

    • Não ative cssnano no ambiente de desenvolvimento, para manter a build mais rápida
    • Ative automaticamente o minify do cssnano em produção
    • Use process.env.NODE_ENV para controlar a condição de ativação
  5. 5

    Step 5: Configure a compactação no servidor

    Configure a compactação Brotli ou Gzip no Nginx:

    • Brotli: brotli on; brotli_comp_level 6;
    • Gzip: gzip on; gzip_comp_level 6;
    • Inclua os tipos text/css e application/javascript em ambos

FAQ

Como ativar o modo JIT no Tailwind v3?
O modo JIT já vem ativado por padrão no Tailwind v3+, sem necessidade de configuração adicional. Se você ainda usa o Tailwind v2, adicione mode: 'jit' ao tailwind.config.js.
O que acontece quando um arquivo fica fora da configuração de content?
Os estilos das classes do Tailwind presentes nesse arquivo não serão gerados e desaparecerão da build de produção. Sempre atualize a configuração de content ao mudar a estrutura do projeto.
Por que o JIT não detecta nomes de classe dinâmicos?
O JIT examina o texto dos arquivos-fonte. Um nome de classe montado dinamicamente, como `text-$&#123;color&#125;-500`, aparece no código como uma template string, e não como um nome completo, por isso não pode ser detectado. Use safelist ou um mapeamento em objeto.
Os 6,5 KB de CSS da Netflix correspondem ao tamanho original ou ao tamanho compactado?
Os 6,5 KB correspondem ao tamanho transferido pela rede após a compactação Brotli. O arquivo CSS original fica com algumas dezenas de KB depois do PurgeCSS e do cssnano, e seu tamanho de transferência cai bastante após a compactação Brotli/Gzip.
Quais são as melhorias do mecanismo Oxide no Tailwind v4?
O mecanismo Oxide foi reescrito em Rust e tornou as compilações incrementais 182 vezes mais rápidas. Ele armazena em cache os resultados da varredura dos arquivos e reprocessa apenas o que mudou, reduzindo builds de projetos grandes, antes na casa dos 10 segundos, para poucos segundos.
Qual é o impacto de configurar itens demais em safelist?
A safelist força a geração dos estilos especificados; uma lista muito grande aumenta o arquivo CSS. Use-a apenas quando necessário para classes dinâmicas e evite incluir toda classe que talvez venha a ser usada.

12 min de leitura · Publicado em: 30 mar 2026 · Atualizado em: 4 set 2026

Comentários

Entre com GitHub para comentar

Easton BlogEaston Blog