Los límites del React Compiler están en lo que omite en silencio

Imagina el despliegue. El equipo pone reactCompiler: true en next.config.ts, la build pasa y una pull request borra todos los useMemo y useCallback del repositorio. Dos semanas después, un campo de un formulario con react-hook-form deja de mostrar lo que escribe el usuario, y un effect de analítica se dispara en cada render. En la salida de la build no había nada que apuntara a ninguno de los dos.
Los límites del React Compiler son silenciosos. El React Compiler es una herramienta de build que añade memoización a componentes y hooks de forma automática, estable desde la versión 1.0 del 7 de octubre de 2025. Cuando encuentra código que no puede demostrar seguro, se lo salta y sigue con la build, y con la configuración por defecto no avisa. Así que los límites reales están en dos sitios: los componentes que el compilador nunca optimizó y los que optimizó pero dependían de la memoización para funcionar bien.
Cómo se ve el error en un código real
El despliegue de arriba se apoya en tres supuestos. Los tres son falsos, y la documentación de React explica por qué.
- Ahora todos los componentes están compilados. En el modo
infer, el que viene por defecto, el compilador solo toma las funciones con nombre de componente (PascalCase) o de hook (prefijouse) que crean JSX o llaman a hooks. YpanicThresholdvalenonepor defecto: cualquier función que no consigue compilar se salta y la build continúa. - El código compilado se comporta igual que antes. La guía de depuración de React señala el código que depende de la memoización para ser correcto como una de las principales formas en que el compilador puede romper una app.
- La memoización manual sobra. La documentación de React recomienda dejar en su sitio los
useMemoyuseCallbackque ya existen, porque quitarlos puede cambiar lo que genera el compilador.
Cuándo sigue valiendo la pena memoizar a mano lo contamos en nuestro artículo anterior sobre el compilador. Este va del código al que el compilador no llega y de cómo verlo.
¿Cuándo se salta un componente el React Compiler?
El compilador está pensado para código que respeta las Rules of React. Cuando un componente puede incumplirlas, la documentación dice que se salta la optimización antes que arriesgarse a cambiar el comportamiento de la app. El componente sigue funcionando, sin memoización, igual que antes de activar el compilador. Casi todos los saltos vienen de cuatro patrones.
Refs leídos o escritos durante el render
Leer ref.current en el cuerpo del render, o asignarle un valor, rompe la regla de que el render sea puro. El lint refs marca los dos casos. El compilador reconoce un ref por useRef(), por una prop ref en JSX o por cualquier nombre que termine en Ref y toque .current. Hay una excepción permitida: la inicialización perezosa, cuando rellenas ref.current una sola vez mientras vale null. Para el resto, la lectura pasa a un effect o a un manejador de eventos, o el valor se convierte en estado si la interfaz depende de él.
Librerías con mutabilidad interna
Algunas librerías devuelven un objeto o una función cuya referencia no cambia aunque cambie su contenido. La memoización comprueba la referencia, así que el valor parece congelado. El lint incompatible-library pone dos ejemplos: watch de react-hook-form y useReactTable de TanStack Table. El compilador se salta los componentes que llaman a las API de su lista. El observer de MobX rompe el mismo supuesto, y la documentación reconoce que el linter todavía no lo detecta: un código con MobX puede compilarse y comportarse mal. Con react-hook-form la solución es useWatch, que devuelve un valor con una referencia nueva cuando cambia el campo.
Funciones que no parecen React
Una función auxiliar llamada calculateTotal no se compila en modo infer, cueste lo que cueste ejecutarla. Tampoco un componente escrito con minúscula inicial. Incluso cuando un cálculo pesado está dentro de un componente, la introducción lo deja claro: el compilador solo memoiza componentes y hooks, y esa memoización no se comparte entre ellos. La misma llamada costosa en cinco componentes se ejecuta cinco veces.
Efectos secundarios durante el render
Un render que escribe en el DOM, modifica un valor que ha recibido o llama a algo no determinista queda fuera de lo que el compilador puede tratar como puro. Las reglas purity e immutability de eslint-plugin-react-hooks los señalan. Eran bugs latentes antes de que llegara el compilador. El compilador los saca a la luz, o los rodea.
¿Cuándo se comporta distinto el código compilado?
Saltarse un componente es el fallo seguro. El peligroso es un componente que el compilador sí ha compilado, escrito sobre un supuesto que el compilador no comparte. La página de resolución de problemas de React enumera tres formas:
- Effects que cuentan con la igualdad de referencias. Un effect depende de un objeto o un array y espera la misma referencia entre renders.
- Arrays de dependencias que necesitan referencias estables. Una dependencia inestable hace que el effect se dispare demasiado, o en bucle.
- Lógica que decide según una comparación de referencias. Código que compara
prev === nextpara decidir si recalcula o vuelve a pedir datos.
Los síntomas son effects que se disparan de más, bucles infinitos y actualizaciones que nunca llegan. La documentación propone una prueba limpia. Quita toda la memoización manual del componente y ejecútalo sin compilador. Si el bug sigue ahí, el componente incumple una Rule of React y el compilador solo lo ha destapado. Si desaparece, el código dependía de la memoización para ser correcto, y esa dependencia es lo que hay que eliminar.
Next.js añade un ajuste que conviene conocer. enablePreserveExistingMemoizationGuarantees decide si el compilador mantiene las garantías de los useMemo, useCallback y React.memo que ya tienes. La documentación de Next.js avisa de que su valor por defecto puede cambiar entre implementaciones y versiones del compilador, y aconseja fijarlo de forma explícita. Una actualización que lo invierta cambia el comportamiento justo en los componentes descritos arriba.
Lo que el compilador te deja a ti, por diseño
Algunos límites son decisiones, escritas en la documentación.
- Trabaja sobre los re-renders. La memoización del compilador apunta al rendimiento en actualización: evitar re-renders en cascada y cálculos repetidos. Una primera carga lenta por el tamaño del bundle, las imágenes o la carga de datos es otro problema, el que desmontamos en por qué una web va lenta.
- Las mejoras se midieron en la app de otro. En la Meta Quest Store, el equipo de React informa de cargas iniciales y navegaciones entre páginas hasta un 12 % más rápidas y algunas interacciones más de 2,5 veces más rápidas, con la memoria sin cambios. El mismo artículo avisa de que los resultados pueden variar. Mide tus propias pantallas.
- Cuesta tiempo de build. El compilador se ejecuta como plugin de Babel. Next.js filtra con SWC y solo lo aplica a los archivos con JSX o hooks, y aun así cuenta con builds algo más lentas que con su compilador de Rust por defecto.
- Los Server Components sacan poco de él. Un Server Component se renderiza una vez por petición y no vuelve a renderizarse en el navegador, así que la memoización del compilador sirve a tus componentes de cliente. Dónde cae esa frontera lo explicamos en las notas sobre Server Components en producción.
Cómo encontrar los componentes saltados en un código existente
El dato que hay que conocer es la cobertura, y la build por defecto no lo muestra. Se llega a él en cinco pasos.
- Activa primero el lint. Desde la 1.0, las reglas del compilador están en
eslint-plugin-react-hooks, en el presetrecommended. El linter no necesita el compilador instalado, así que puedes pasarlo por un código antes de activar nada. Cada aviso es un componente que el compilador se saltará o un bug que va a destapar. - Registra lo que hace el compilador. Donde la build usa el plugin de Babel directamente (Vite, Metro, una configuración de Babel sencilla), añade un
logger. EmiteCompileSuccess,CompileErroryCompileSkippor cada función. Cuéntalos por carpeta y tienes un informe de cobertura. - Revisa en DevTools las pantallas que importan. React DevTools marca un componente compilado con la insignia Memo ✨. Abre las cinco pantallas donde tus usuarios pasan más tiempo y busca los componentes que no la llevan.
- Aísla un bug en tiempo de ejecución con
"use no memo". La directiva excluye una función, o un archivo entero si va arriba del todo. La documentación la define como una herramienta de depuración temporal, así que acompáñala siempre de un comentario que diga por qué y enlace la incidencia. - Sustituye las API incompatibles una a una.
watchpasa a seruseWatch. La lectura de un ref en el render se mueve a un effect. Cada arreglo devuelve un componente al compilador.
Cómo evitar que vuelva a pasar
La prevención es casi toda configuración, y se hace una vez.
- Lint como error en CI. Una pull request que añade la lectura de un ref en el render falla antes de la revisión.
- Un umbral más estricto solo en desarrollo.
panicThreshold: 'critical_errors'durante el trabajo en local hace aflorar los fallos de compilación. En producción se queda ennone, como pide la documentación, para que un problema del compilador nunca bloquee un despliegue. - Despliegue por carpetas. La guía de adopción gradual ofrece tres caminos: overrides de Babel por carpeta,
compilationMode: 'annotation'con un"use memo"explícito, o una comprobación en tiempo de ejecución detrás de un feature flag para una comparación A/B. - Memoización manual donde un effect depende de ella. En código nuevo dejamos decidir al compilador y añadimos
useMemosolo donde un valor alimenta un effect. La memoización existente se queda hasta que una prueba demuestre que puede irse, y el lintpreserve-manual-memoizationcomprueba que sus dependencias estén completas. - Fija el ajuste de Next.js. Escribe
enablePreserveExistingMemoizationGuaranteesen la configuración con el valor que has probado, para que una actualización no lo cambie por ti.
En la mayoría de los códigos React el compilador se gana su sitio. Sus límites son los componentes que nunca tocó y los que solo funcionaban porque algo estaba memoizado. Los dos se encuentran en una tarde con el lint, un logger y DevTools, y los dos siguen invisibles para quien solo lee el log de la build.
Preguntas frecuentes
En el código existente no, al menos no de golpe. La documentación de React recomienda dejar la memoización que ya hay, porque quitarla puede cambiar lo que genera el compilador, o probar con cuidado antes de cada eliminación. En código nuevo, escribe valores y funciones normales y deja que el compilador los memoice. Mantén useMemo o useCallback donde un valor es dependencia de un effect y tiene que conservar la misma referencia: es el caso que la documentación sigue citando como motivo para un control manual.
Algo. El compilador se ejecuta como plugin de Babel, mientras que Next.js compila normalmente con SWC, su compilador escrito en Rust. Para contener el coste, Next.js usa una pasada de SWC que aplica el React Compiler solo a los archivos con JSX o hooks. Aun así, la documentación de Next.js habla de builds algo más lentas que por defecto, con un impacto pequeño y localizado. Mide el tiempo de build en CI antes y después en lugar de fiarte de una cifra general.
Sí. El equipo de React indica que el compilador es compatible desde React 17. Con React 17 o 18 fijas un target mínimo en la configuración del compilador y añades el paquete react-compiler-runtime como dependencia. Con React 19 no hace falta ninguno de los dos pasos. Las reglas de salto son las mismas en todas las versiones, así que el lint y las comprobaciones de cobertura de este artículo sirven igual en un código más antiguo.
Artículos relacionados
9 de septiembre de 2026 · 7 min de lectura
23 de abril de 2026 · 7 min de lectura
18 de abril de 2026 · 7 min de lectura