Alternar tema

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

Easton editorial illustration: cost-quality-speed triangle

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:

15 vezes/s
Renderizações (normal)
Source: Teste prático: Compiler vs. otimização manual
  • 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:

  1. Primeiro, verifique a mensagem de erro. Em geral, o nome ou a lógica de algum componente não segue as regras do Compiler
  2. Desative o Compiler nesse componente, adicionando a diretiva 'use no memo'
  3. 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:

  1. Otimização automática de desempenho: não é preciso escrever useMemo ou useCallback; o Compiler cuida disso durante o build
  2. Código mais simples: remover otimizações manuais melhora a legibilidade
  3. 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?
Não de forma significativa. Nos meus testes, o bundle aumentou apenas cerca de 2 KB depois da ativação do Compiler, principalmente por causa da lógica de memoization inserida pelo compilador. Em compensação, foi possível remover useMemo e useCallback escritos manualmente, então o impacto total é pequeno.
Os componentes do shadcn/ui são compatíveis com o React Compiler?
A maioria dos componentes do shadcn/ui é pura, e o Compiler consegue identificar e otimizar bem suas dependências. No entanto, alguns componentes com lógica interna complexa podem exigir a diretiva 'use no memo' para desativar o Compiler. Recomendo migrar aos poucos e desativá-lo nos casos problemáticos.
Devo remover useMemo e useCallback escritos manualmente depois de ativar o Compiler?
Sim, é recomendável removê-los. O Compiler insere memoization automaticamente onde ela é necessária, por isso useMemo e useCallback manuais podem se tornar redundantes. Após a migração, o código fica mais simples, com desempenho semelhante ou até melhor.
Em quais situações é preciso desativar o Compiler?
Em lógica complexa de efeitos colaterais, como temporizadores, animações e manipulação do DOM; quando há incompatibilidade com bibliotecas de terceiros; ou nos raros casos em que o desempenho piora. Para desativá-lo, adicione a diretiva 'use no memo' no início do componente.
O Compiler impõe alguma regra de nomenclatura?
É recomendável desestruturar as props e evitar o uso direto de props.data. Prefira, por exemplo, const { data } = props ou function MyComponent({ data }), para que o Compiler analise as dependências com mais precisão.
Como ativar o Compiler em projetos com Next.js 16 e Vite?
No Next.js 16, adicione experimental: { reactCompiler: true } ao next.config.js. No Vite, instale babel-plugin-react-compiler e configure babel.plugins dentro do plugin React no vite.config.ts.

11 min de leitura · Publicado em: 31 mar 2026 · Atualizado em: 4 set 2026

Comentários

Entre com GitHub para comentar

Easton BlogEaston Blog