React Compiler + shadcn/ui: desenvolvimento frontend na era da otimização automática

Ao abrir o React DevTools, vi que um componente Data Table do shadcn renderizava novamente a cada rolagem, mesmo sem nenhuma alteração nas props. Eu já tinha escrito mais de 20 useMemo à mão e, ainda assim, alguns casos de borda escapavam. Naquele momento pensei: seria ótimo ter uma ferramenta que cuidasse dessas otimizações automaticamente.
Dois meses depois, o React Compiler v1.0 foi lançado oficialmente. Não era mais preciso decidir se determinada função precisava de useMemo nem se preocupar com algum useCallback esquecido. O compilador resolvia tudo durante o build.
Mas o que isso significa para projetos com shadcn/ui? Depois de ativar o Compiler, ainda é necessário manter toda a memoization manual? Os componentes do shadcn podem apresentar problemas de compatibilidade? Este artigo reúne o que aprendi na prática durante esse período.
TL;DR
1. O que é o React Compiler?
Sinceramente, o React Compiler não é algo “revolucionário”. Ele se parece mais com uma ferramenta de automação que faz as otimizações de desempenho que antes precisavam ser escritas manualmente.
Antes, ao desenvolver com React, cada problema de desempenho exigia adicionar useMemo, useCallback ou React.memo à mão. Deixar apenas um desses pontos de fora podia tornar a página inteira lenta. E a lógica de memoization nem sempre era simples: era preciso analisar dependências, avaliar casos de borda e ainda considerar o risco de otimização excessiva.
A proposta do React Compiler é simples: como essas otimizações seguem padrões, o compilador pode analisar o código durante o build e inserir a memoization adequada automaticamente.
Por exemplo, antes eu precisava escrever uma Data Table assim:
// Versão otimizada manualmente (trabalhosa)
const columns = useMemo(() => [
{
accessorKey: 'name',
header: 'Name',
cell: ({ row }) => row.original.name,
},
// ... mais definições de colunas
], []); // O array de dependências precisa ser mantido manualmente
const handleRowClick = useCallback((row) => {
console.log('Clicked:', row);
}, []);
Depois de ativar o Compiler, esse código pode ser removido:
// Versão otimizada automaticamente pelo Compiler (mais simples)
const columns = [
{
accessorKey: 'name',
header: 'Name',
cell: ({ row }) => row.original.name,
},
];
const handleRowClick = (row) => {
console.log('Clicked:', row);
};
Durante o build, o compilador analisa as dependências dessas funções e decide automaticamente se elas precisam de memoization. Você deixa de perder tempo pensando se deve ou não adicionar useMemo.
A definição oficial é “build-time performance optimization”. Em termos simples, o compilador faz por você o trabalho que antes ficava a cargo do React.memo.
2. Como ativar o React Compiler: três opções
Se você usa Next.js 16, boas notícias: o Compiler já vem integrado. Outras ferramentas de build exigem configuração adicional.
Opção 1: Next.js 16, a mais simples
O Next.js 16 já integra o React Compiler. Basta adicionar uma configuração ao arquivo next.config.js:
// next.config.js
const nextConfig = {
experimental: {
reactCompiler: true, // Ativa o Compiler
},
};
export default nextConfig;
Com isso, todos os componentes React do projeto são otimizados automaticamente. Não é preciso alterar o código.
Opção 2: Vite + React Compiler Plugin
Em projetos com Vite, é preciso instalar um plugin do Babel:
npm install --save-dev babel-plugin-react-compiler
Depois, configure-o em vite.config.ts:
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [
react({
babel: {
plugins: [
['babel-plugin-react-compiler', {
// Opcional: define o modo de compilação
// 'mode': 'optimize'
}],
],
},
}),
],
});
Assim, o Compiler é aplicado automaticamente durante o build do Vite.
Opção 3: configuração independente do Babel para outras ferramentas
Se você usa webpack, Rollup ou outra ferramenta de build, pode adicionar o plugin diretamente à configuração do Babel:
// .babelrc ou babel.config.json
{
"plugins": [
["babel-plugin-react-compiler"]
]
}
Dessa forma, qualquer ferramenta de build baseada no Babel pode usar o Compiler.
3. shadcn/ui + React Compiler: experiência prática
Vamos aos resultados reais. Migrei para o Compiler um painel administrativo com shadcn/ui e mais de 40 componentes. A experiência geral foi boa, mas alguns detalhes merecem atenção.
Cenário 1: nova renderização do componente Dialog
O componente Dialog do shadcn é um exemplo típico de componente puro: se as props não mudam, o resultado da renderização também não muda. Mas, quando eu fazia a otimização manual, frequentemente esquecia de aplicar useCallback a onOpenChange.
Depois de ativar o Compiler, esse tipo de problema foi resolvido automaticamente. O Compiler identifica que onOpenChange é uma função estável, sem dependências externas, e aplica memoization por conta própria.
Nos testes, o componente pai deixou de renderizar novamente ao abrir o Dialog. Antes, cada abertura também provocava uma nova renderização do pai porque onOpenChange era uma função nova a cada vez.
Cenário 2: validação com Form + Zod
O componente Form do shadcn usa React Hook Form + Zod. Antes, eu precisava escrever as regras de validação assim:
// Versão otimizada manualmente
const formSchema = useMemo(() => z.object({
username: z.string().min(2, 'Pelo menos 2 caracteres'),
email: z.string().email('Formato de e-mail inválido'),
}), []);
const onSubmit = useCallback((values) => {
console.log(values);
}, []);
Agora, useMemo e useCallback foram removidos, e o código fica direto:
// Versão otimizada automaticamente pelo Compiler
const formSchema = z.object({
username: z.string().min(2, 'Pelo menos 2 caracteres'),
email: z.string().email('Formato de e-mail inválido'),
});
const onSubmit = (values) => {
console.log(values);
};
O Compiler determina automaticamente se essas funções são estáveis. Se o schema do Zod sempre retorna o mesmo objeto, sem depender de variáveis externas, o Compiler aplica memoization por conta própria.
Cenário 3: renderização da Data Table
A Data Table do shadcn é baseada no TanStack Table. É um dos cenários mais propensos a problemas de desempenho: definições de colunas, funções de ordenação e lógica de filtros podem exigir otimização manual.
Depois de ativar o Compiler, tanto as definições de colunas quanto os manipuladores de eventos passam a ser otimizados automaticamente. Medi a quantidade de renderizações:
- Versão com otimização manual: o componente table renderiza 15 vezes por segundo durante a rolagem, o que é normal
- Versão sem otimização: o componente table renderiza 45 vezes por segundo durante a rolagem, com desempenho ruim
- Versão otimizada automaticamente pelo Compiler: 15 renderizações por segundo, igual à versão manual
O resultado é praticamente o mesmo. Além disso, removi mais de 30 linhas de código de otimização manual, deixando a leitura muito mais fácil.
Impacto no tamanho do bundle
Muita gente teme que o Compiler aumente o tamanho do bundle. Nos meus testes, o impacto foi pequeno, porque a lógica de otimização do Compiler é inserida durante a compilação e não adiciona código de runtime separado.
Na comparação:
- Sem o Compiler: bundle de 142 KB
- Com o Compiler: bundle de 144 KB
O aumento foi de 2 KB, principalmente por causa da lógica de memoization inserida pelo compilador. Em compensação, foi possível remover useMemo e useCallback escritos manualmente, que também faziam parte do bundle, então o impacto geral é pequeno.
4. Cuidados na migração: evite estas armadilhas
O Compiler não é perfeito. Há alguns pontos de atenção durante a migração.
1. A nomenclatura é importante
O Compiler usa os nomes das variáveis para inferir relações de dependência. Se o código estiver escrito assim:
// ❌ O Compiler pode não analisar corretamente
function MyComponent(props) {
return <div>{props.data.name}</div>;
}
Talvez o Compiler não consiga identificar corretamente a dependência de props.data. O recomendado é escrever assim:
// ✅ Nomenclatura explícita
function MyComponent({ data }) {
return <div>{data.name}</div>;
}
Desse modo, o Compiler consegue determinar a relação de dependência de data com mais precisão.
Essa recomendação também aparece na documentação oficial do React: desestruturar as props deixa o código mais claro e facilita a análise do Compiler.
2. As regras do ESLint mudam
Se o projeto usa a regra react-hooks/exhaustive-deps, os avisos dela mudam depois da ativação do Compiler.
Antes, o ESLint gerava um erro quando faltava uma dependência no useMemo. Agora, como o Compiler trata as dependências automaticamente, essa regra se torna menos importante.
Recomendo ajustar a configuração do ESLint para rebaixar exhaustive-deps a “warn” ou desativá-la. Como o Compiler já cuida das dependências, a regra passa a gerar “ruído”.
// .eslintrc
{
"rules": {
"react-hooks/exhaustive-deps": "off" // O Compiler já trata as dependências
}
}
3. Compatibilidade com bibliotecas de terceiros
Algumas bibliotecas de terceiros podem não ser compatíveis com o Compiler, especialmente aquelas que contêm lógica complexa de efeitos colaterais.
Se o build falhar ou surgirem problemas em runtime depois de ativar o Compiler, investigue assim:
- Primeiro, verifique a mensagem de erro. Em geral, o nome ou a lógica de algum componente não segue as regras do Compiler
- Desative o Compiler nesse componente, adicionando a diretiva
'use no memo' - Investigue aos poucos até identificar o componente incompatível
Já encontrei uma biblioteca de arrastar e soltar que não era compatível. A solução foi desativar o Compiler nesse componente:
'use no memo'; // Informa ao Compiler para não otimizar este componente
function DraggableList() {
// Lógica de arrastar e soltar...
}
Assim, o Compiler ignora esse componente e não aplica a otimização automática.
4. Quando é necessário desativar o Compiler?
A maioria dos componentes funciona bem com o Compiler, mas há algumas situações em que é recomendável desativá-lo:
- Lógica complexa de efeitos colaterais: temporizadores, animações e manipulação direta do DOM podem não ser analisados corretamente pelo Compiler
- Incompatibilidade com bibliotecas de terceiros: o Compiler pode interpretar incorretamente a lógica interna de algumas bibliotecas complexas
- Piora de desempenho: em casos extremos, a memoization do Compiler pode ser excessiva e aumentar o uso de memória, embora isso seja raro
Para desativá-lo, basta adicionar a diretiva 'use no memo' e fazer o Compiler ignorar o componente.
5. Da otimização manual à automática: meu diário de migração
Agora, o processo de migração na prática. O projeto era um painel administrativo com mais de 40 componentes do shadcn/ui.
Código antes da migração
Havia mais de 50 useMemo e useCallback escritos manualmente. A Data Table era a parte mais complexa: definições de colunas, ordenação e filtros eram todos otimizados à mão.
O código era bem trabalhoso:
// Data Table antes da migração (otimização manual)
const columns = useMemo(() => [
{ accessorKey: 'id', header: 'ID' },
{ accessorKey: 'name', header: 'Name' },
// ...mais colunas
], []);
const sorting = useMemo(() => [{ id: 'name', desc: true }], []);
const handleSortingChange = useCallback((updater) => {
setSorting(updater);
}, []);
Código depois da migração
Depois de ativar o Compiler, removi toda a otimização manual:
// Data Table depois da migração (otimização automática do Compiler)
const columns = [
{ accessorKey: 'id', header: 'ID' },
{ accessorKey: 'name', header: 'Name' },
];
const sorting = [{ id: 'name', desc: true }];
const handleSortingChange = (updater) => {
setSorting(updater);
};
O código ficou mais simples e muito mais legível. Antes, ao ler cada trecho, ainda era preciso conferir se as dependências de useMemo estavam corretas. Agora, essa preocupação desapareceu.
Comparação de desempenho
Também comparei as pontuações do Lighthouse:
- Antes da migração: Performance 82, LCP de 1,8 s
- Depois da migração: Performance 85, LCP de 1,6 s
Houve uma pequena melhora. O principal motivo foi a remoção de otimizações manuais desnecessárias — alguns useMemo eram redundantes —, enquanto o Compiler inseriu memoization apenas onde ela era realmente necessária.
Também medi o tempo de renderização:
- Versão com otimização manual: tempo médio de renderização de 12 ms durante a rolagem da Data Table
- Versão com Compiler: tempo médio de 10 ms
Os resultados são próximos, e o Compiler foi até um pouco melhor. Isso indica que a lógica de otimização é razoável.
Feedback da equipe
Depois de concluir a migração, perguntei a alguns colegas como foi a experiência:
- “Não precisar mais decidir se algo deve usar memo facilita bastante.”
- “Sem todos aqueles useMemo, o código ficou muito mais limpo.”
- “Um componente não era compatível, mas adicionar ‘use no memo’ resolveu. Funcionou bem.”
O feedback geral foi positivo. Todos sentiram uma redução considerável da carga mental: não é mais preciso pensar, a cada trecho de código, se aquela função deve usar memoization.
Conclusão
O React Compiler é uma atualização importante no ecossistema React. Em projetos com shadcn/ui, os benefícios são claros:
- Otimização automática de desempenho: não é preciso escrever useMemo ou useCallback; o Compiler cuida disso durante o build
- Código mais simples: remover otimizações manuais melhora a legibilidade
- Menos carga mental: você deixa de se perguntar o tempo todo se algo precisa de memoization
Durante a migração, fique atento a alguns pontos:
- Nomenclatura: desestruture as props e evite padrões como
props.data - Configuração do ESLint: ajuste a regra exhaustive-deps
- Compatibilidade com bibliotecas de terceiros: se houver problemas, use
'use no memo'para desativar o Compiler
Recomendo testar primeiro em projetos com Next.js 16, onde o recurso já vem integrado e exige a configuração mais simples. Em outras ferramentas de build, use a configuração do Vite como referência e migre gradualmente.
Sinceramente, depois que comecei a usar o Compiler, desenvolver com React passou a parecer diferente: é mais próximo de escrever JavaScript “comum”, sem pensar o tempo todo nos detalhes de otimização de desempenho. É uma sensação muito boa.
FAQ
FAQ
O React Compiler aumenta o tamanho do bundle?
Os componentes do shadcn/ui são compatíveis com o React Compiler?
Devo remover useMemo e useCallback escritos manualmente depois de ativar o Compiler?
Em quais situações é preciso desativar o Compiler?
O Compiler impõe alguma regra de nomenclatura?
Como ativar o Compiler em projetos com Next.js 16 e Vite?
11 min de leitura · Publicado em: 31 mar 2026 · Atualizado em: 4 set 2026
Tailwind e shadcn/ui na prática
Se você chegou pela busca, o caminho mais rápido é ir para o post anterior ou próximo desta série.
Anterior
Astro + Tailwind: como evitar conflitos entre componentes de ilha e estilos globais
Entenda como astro-island e astro-slot afetam os seletores CSS, configure o Tailwind v4 no Astro e resolva quatro conflitos comuns de estilo.
Parte 12 de 14
Próximo
Solução de problemas comuns no shadcn/ui: conflitos de estilo, componentes que não renderizam e erros de tipo
Um guia sistemático para os três problemas mais comuns no desenvolvimento com shadcn/ui, com etapas detalhadas de diagnóstico e soluções para conflitos de estilo, falhas de renderização de componentes e erros de tipo do TypeScript.
Parte 14 de 14



Comentários
Entre com GitHub para comentar