O React Compiler chegou à versão 1.0 estável em 7 de outubro de 2025, funciona de React 17 para cima e hoje vem ligado por padrão no Expo SDK 54, além de aparecer como opção nos modelos do create-vite e do create-next-app. A promessa é conhecida: memoização automática em tempo de build, sem useMemo, sem useCallback, sem React.memo escritos à mão.
A posição deste post: a decisão de configuração que mais afeta o resultado não é nenhuma das que a discussão sobre o compilador costuma citar. É o panicThreshold, cujo padrão é 'none', ou seja, o componente que o compilador não conseguiu compilar é pulado e o build segue verde. O padrão está certo. O erro é adotar o compilador sem montar o segundo sinal que mostra o que ficou de fora.
O padrão é pular, não falhar
O panicThreshold aceita três valores. Em 'none', que é o padrão e a recomendação da documentação para produção, componentes que não podem ser compilados são pulados e a build continua. Em 'critical_errors', a build falha apenas em erro crítico do compilador. Em 'all_errors', qualquer diagnóstico derruba a build.
Existe uma boa razão para o padrão ser esse. O compilador é uma otimização, e otimização que quebra deploy é troca ruim: o componente pulado continua funcionando, só que sem memoização automática, exatamente como funcionava antes de você instalar o plugin. Falhar a build por isso seria transformar melhoria opcional em bloqueio de entrega.
O problema não está no padrão. Está na assimetria que ele cria. Compilar com sucesso e ser pulado produzem o mesmo resultado visível: build verde, aplicação funcionando, teste passando. A diferença aparece só no perfil de renderização, semanas depois, quando alguém pergunta por que aquela tela continua lenta mesmo “com o compilador ligado”.
Você não instalou um otimizador que avisa quando desiste. Instalou um que desiste em silêncio, por escolha de desenho, e delega o aviso para outra ferramenta.
O aviso mora no ESLint
A outra ferramenta é o eslint-plugin-react-hooks. As regras movidas do compilador para o lint entraram nos presets recommended e recommended-latest, e os diagnósticos do compilador são apresentados por elas.
A lista de regras do preset é longa e vale ler pelos nomes, porque cada nome é uma razão concreta de bailout: purity, immutability, refs, set-state-in-render, set-state-in-effect, preserve-manual-memoization, static-components, component-hook-factories, error-boundaries, globals, incompatible-library, unsupported-syntax, use-memo, gating e config, ao lado dos veteranos rules-of-hooks e exhaustive-deps.
Repare no que essa lista diz sobre a ordem de adoção. O compilador só otimiza código que respeita as Rules of React. As regras de lint são o lugar onde a violação vira mensagem legível, com arquivo e linha. Ligar o compilador antes do lint é ligar a parte silenciosa e deixar a parte que fala para depois.
Instalação, na ordem que importa
npm install -D eslint-plugin-react-hooks@latest
npm install -D babel-plugin-react-compiler@latest
No babel.config.js, o plugin do compilador precisa vir primeiro na lista de plugins. Isso não é preferência de estilo: ele precisa ver o código antes das outras transformações mexerem nele.
O outro padrão que explica os buracos
O compilationMode tem quatro valores: 'infer', 'annotation', 'syntax' e 'all'. O padrão é 'infer', e a regra dele é mais estreita do que a maioria das pessoas assume.
Em 'infer', o compilador pega duas classes de função. As anotadas com a diretiva "use memo". E as que têm nome de componente, em PascalCase, ou nome de hook, com prefixo use, e produzem JSX ou chamam outros hooks. As duas condições valem juntas, não separadas.
Aqui está a origem de metade das surpresas. Aquele seu createColumns() que devolve um vetor de definições de coluna com JSX dentro, extraído do componente para deixar o arquivo mais limpo, não tem nome de componente. Nunca será compilado, em nenhum modo padrão, e o trabalho dele roda de novo a cada renderização.
A documentação é explícita sobre os dois limites que decorrem disso: o compilador não memoiza funções utilitárias soltas, apenas componentes e hooks, e a memoização não é compartilhada entre componentes. A mesma função cara chamada em três telas executa três vezes.
O escape existe nos dois sentidos. A diretiva "use memo" força a compilação de uma função que a heurística ignoraria, e "use no memo" exclui uma função em qualquer modo, inclusive em 'all'. A segunda deve ser temporária, com comentário dizendo por que está ali.
Como verificar de verdade
Existem duas checagens objetivas, e nenhuma delas é “a build passou”.
A primeira é o React DevTools, que marca com o selo Memo os componentes efetivamente compilados. É a verificação de olho, tela por tela, e serve para conferir suspeita pontual.
A segunda é olhar o código gerado. Módulo compilado importa o runtime de memoização:
import { c as _c } from "react/compiler-runtime";
Essa é a que vale automatizar. Um passo do pipeline que conta em quantos módulos do bundle esse import aparece te dá um número, e número comparado entre dois commits vira regressão detectável. Sem isso, a única forma de descobrir que uma refatoração tirou trinta componentes da compilação é alguém reclamar de lentidão.
Se você está em React 17 ou 18
Funciona, com dois passos a mais: instalar o pacote react-compiler-runtime e definir target como '18' ou '17' na configuração. Em React 19 nada disso é necessário, basta declarar o plugin.
No Next.js, a documentação recomenda a versão 15.3.1 ou superior, onde o compilador é invocado pelo swc e o custo de build fica menor.
Perguntas frequentes
Devo apagar meus useMemo existentes?
Não como primeiro passo. Existe uma regra de lint chamada preserve-manual-memoization, e o nome diz a intenção: a memoização escrita à mão é tratada como informação a preservar, não como lixo a remover. Ligue o compilador com ela no lugar, meça, e só então avalie o que sai.
Vale usar o panicThreshold em all_errors para não perder nada?
Em desenvolvimento, para diagnosticar, faz sentido. Em produção, a própria documentação recomenda 'none', porque um diagnóstico novo em uma atualização do compilador viraria build quebrada sem que nada no seu código tenha mudado.
O compilador remove a necessidade de conhecer as Rules of React?
Ele faz o contrário. A memoização automática depende dessas regras valerem, então cada violação agora tem uma consequência de desempenho além da consequência de correção que já tinha.
Dá para adotar aos poucos?
Sim, e é para isso que serve o compilationMode: 'annotation'. Nesse modo só as funções marcadas com "use memo" são compiladas, o que permite ligar arquivo por arquivo em vez de tudo de uma vez.
Fontes