React Compiler + shadcn/ui: desarrollo frontend en la era de la optimización automática

Abres React DevTools y ves que un Data Table de shadcn se vuelve a renderizar en cada scroll, aunque las props no cambian. Ya tienes más de 20 useMemo escritos a mano y aun así se escapan casos límite. En ese momento piensas: ojalá existiera una herramienta que se encargara de esto sola.
Dos meses después, React Compiler v1.0 sale oficialmente. Ya no tienes que decidir si memoizar una función ni temer olvidar un useCallback. El compilador lo resuelve en tiempo de build.
¿Qué implica esto para un proyecto shadcn/ui? ¿Sigues necesitando la memoización manual? ¿Habrá problemas de compatibilidad con los componentes de shadcn? En este artículo comparto lo que aprendí en la práctica.
TL;DR
1. ¿Qué es React Compiler?
React Compiler no es algo «revolucionario»: es más bien una herramienta de automatización que hace por ti las optimizaciones de rendimiento que antes escribías a mano.
Antes, cada vez que aparecía un problema de rendimiento en React, tocaba añadir useMemo, useCallback y React.memo. Si se te escapaba uno, la página podía ir a tirones. Además, la lógica de memoización no siempre es trivial: hay que analizar dependencias, casos límite y evitar sobreoptimizar.
La idea del React Compiler es simple: si esas reglas son predecibles, que el compilador las analice en el build e inserte la memoización adecuada.
Por ejemplo, al escribir un Data Table antes hacía algo así:
// Versión optimizada a mano (engorrosa)
const columns = useMemo(() => [
{
accessorKey: 'name',
header: 'Name',
cell: ({ row }) => row.original.name,
},
// ... más definiciones de columnas
], []); // el array de dependencias lo mantienes tú
const handleRowClick = useCallback((row) => {
console.log('Clicked:', row);
}, []);
Con el Compiler activado, puedes eliminar ese código:
// Versión optimizada por el Compiler (más limpia)
const columns = [
{
accessorKey: 'name',
header: 'Name',
cell: ({ row }) => row.original.name,
},
];
const handleRowClick = (row) => {
console.log('Clicked:', row);
};
El compilador analiza las dependencias de estas funciones en el build y decide si memoizar. Ya no te preguntas «¿añado useMemo aquí?».
La documentación oficial lo resume como «build-time performance optimization». En la práctica, el compilador hace el trabajo de React.memo por ti.
2. Activar React Compiler: tres formas
Si usas Next.js 16, el Compiler ya viene integrado. Con otras herramientas de build hace falta configurarlo.
Forma 1: Next.js 16 (la más sencilla)
Next.js 16 integra React Compiler por defecto. Solo necesitas una línea en next.config.js:
// next.config.js
const nextConfig = {
experimental: {
reactCompiler: true, // activar el Compiler
},
};
export default nextConfig;
Con eso, todos los componentes React del proyecto se optimizan automáticamente. No hace falta tocar el código.
Forma 2: Vite + React Compiler Plugin
En Vite instala un plugin de Babel:
npm install --save-dev babel-plugin-react-compiler
Y configúralo en 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: especificar el modo de compilación
// 'mode': 'optimize'
}],
],
},
}),
],
});
Así Vite aplica el Compiler en cada build.
Forma 3: Configuración Babel independiente (otras herramientas)
Si usas webpack, Rollup u otra herramienta, añade el plugin en la configuración de Babel:
// .babelrc o babel.config.json
{
"plugins": [
["babel-plugin-react-compiler"]
]
}
Cualquier herramienta que use Babel puede soportar el Compiler.
3. shadcn/ui + React Compiler: experiencia real
Te cuento el resultado en un panel de administración con shadcn/ui, unas 40 componentes. La experiencia global fue buena, pero hay detalles a tener en cuenta.
Escenario 1: re-renderizados del Dialog
El Dialog de shadcn es un componente puro típico: si las props no cambian, el resultado tampoco. Antes, al optimizar a mano, a menudo olvidaba envolver onOpenChange en useCallback.
Con el Compiler activado, ese problema desaparece. Identifica que onOpenChange es una función estable (sin dependencias externas) y la memoiza.
En pruebas, al abrir el Dialog el componente padre ya no se re-renderiza. Antes, cada apertura provocaba un re-render del padre porque onOpenChange era una función nueva en cada render.
Escenario 2: Form + validación con Zod
El Form de shadcn usa React Hook Form + Zod. Antes escribía las reglas así:
// Versión optimizada a mano
const formSchema = useMemo(() => z.object({
username: z.string().min(2, 'Al menos 2 caracteres'),
email: z.string().email('Formato de correo inválido'),
}), []);
const onSubmit = useCallback((values) => {
console.log(values);
}, []);
Ahora eliminé esos useMemo/useCallback y escribo directamente:
// Versión optimizada por el Compiler
const formSchema = z.object({
username: z.string().min(2, 'Al menos 2 caracteres'),
email: z.string().email('Formato de correo inválido'),
});
const onSubmit = (values) => {
console.log(values);
};
El Compiler decide si estas funciones son estables. Si el schema de Zod devuelve el mismo objeto (sin variables externas), lo memoiza automáticamente.
Escenario 3: renderizado del Data Table
El Data Table de shadcn se basa en TanStack Table. Es el escenario más propenso a problemas de rendimiento: definiciones de columnas, funciones de ordenación y lógica de filtrado suelen requerir optimización manual.
Con el Compiler, las definiciones de columnas y los manejadores de eventos se optimizan solos. Medí el número de renderizados:
- Versión optimizada a mano: al hacer scroll, la tabla se renderiza 15 veces/s (normal)
- Versión sin optimizar: al hacer scroll, 45 veces/s (rendimiento pobre)
- Versión con Compiler: igual que la manual, 15 veces/s
El resultado es equivalente. Además eliminé más de 30 líneas de optimización manual; el código se lee mucho mejor.
Impacto en el tamaño del bundle
Muchos temen que el Compiler infle el bundle. En la práctica el impacto es mínimo: la optimización ocurre en compilación y no añade runtime extra.
Comparativa:
- Sin Compiler: bundle de 142 KB
- Con Compiler: bundle de 144 KB
Solo 2 KB más, sobre todo por la lógica de memoización insertada. A cambio eliminas useMemo/useCallback manuales (que ya estaban en el bundle); el balance es pequeño.
4. Migración: trampas que conviene evitar
El Compiler no es perfecto. En la migración hay varios puntos delicados.
1. La convención de nombres importa
El Compiler infiere dependencias a partir de los nombres de variables. Si tu código es así:
// ❌ El Compiler puede analizar mal las dependencias
function MyComponent(props) {
return <div>{props.data.name}</div>;
}
Puede que no identifique bien la dependencia de props.data. Mejor así:
// ✅ Nombres explícitos
function MyComponent({ data }) {
return <div>{data.name}</div>;
}
Así el Compiler juzga con más precisión la dependencia de data.
La documentación oficial de React también lo recomienda: desestructurar props aclara el código y ayuda al Compiler.
2. Cambian las reglas de ESLint
Si usas react-hooks/exhaustive-deps, al activar el Compiler los avisos cambian.
Antes, olvidar una dependencia en useMemo disparaba un error de ESLint. Ahora el Compiler gestiona las dependencias y la regla pierde peso.
Conviene bajar exhaustive-deps a «warn» o desactivarla: el Compiler ya cubre eso y la regla se vuelve ruido.
// .eslintrc
{
"rules": {
"react-hooks/exhaustive-deps": "off" // el Compiler ya gestiona las dependencias
}
}
3. Compatibilidad con librerías de terceros
Algunas librerías pueden no ser compatibles, sobre todo si tienen efectos secundarios complejos.
Si tras activar el Compiler falla el build o en runtime, sigue este orden:
- Lee el mensaje de error: suele señalar un componente cuya lógica o nombres no cumplen las reglas del Compiler
- Desactiva el Compiler en ese componente (comentario
'use no memo') - Investiga de forma gradual hasta localizar los componentes problemáticos
Tuve un caso con una librería de arrastrar y soltar incompatible. La solución fue desactivar el Compiler solo en ese componente:
'use no memo'; // indica al Compiler: no optimices este componente
function DraggableList() {
// lógica de drag and drop...
}
El Compiler omite ese componente y no aplica optimización automática.
4. ¿Cuándo desactivar el Compiler?
En la mayoría de componentes funciona bien. Pero conviene desactivarlo en estos casos:
- Efectos secundarios complejos: timers, animaciones o manipulación directa del DOM; el Compiler puede analizar mal
- Librerías de terceros incompatibles: lógica interna compleja que provoca falsos positivos
- Rendimiento peor: en casos extremos la memoización puede ser excesiva y aumentar el uso de memoria (es poco frecuente)
El método es el mismo: comentario 'use no memo' para que el Compiler salte el componente.
5. De la optimización manual a la automática: mi bitácora de migración
El proyecto era un panel de administración con más de 40 componentes shadcn/ui.
Código antes de migrar
Había unas 50 instancias de useMemo/useCallback escritas a mano. La parte del Data Table era la más densa: columnas, ordenación y filtrado, todo optimizado manualmente.
El código se veía pesado:
// Data Table antes de migrar (optimización manual)
const columns = useMemo(() => [
{ accessorKey: 'id', header: 'ID' },
{ accessorKey: 'name', header: 'Name' },
// ...más columnas
], []);
const sorting = useMemo(() => [{ id: 'name', desc: true }], []);
const handleSortingChange = useCallback((updater) => {
setSorting(updater);
}, []);
Código después de migrar
Tras activar el Compiler eliminé toda la optimización manual:
// Data Table tras migrar (optimización automática del Compiler)
const columns = [
{ accessorKey: 'id', header: 'ID' },
{ accessorKey: 'name', header: 'Name' },
];
const sorting = [{ id: 'name', desc: true }];
const handleSortingChange = (updater) => {
setSorting(updater);
};
El código es más limpio y legible. Antes había que revisar si cada useMemo tenía las dependencias correctas; ahora esa carga desaparece.
Comparativa de rendimiento
Medí la puntuación de Lighthouse:
- Antes de migrar: Performance 82, LCP 1.8 s
- Después de migrar: Performance 85, LCP 1.6 s
Una mejora moderada. Sobre todo porque quité optimizaciones manuales innecesarias (algunos useMemo eran redundantes) y el Compiler memoiza solo donde hace falta.
También medí el tiempo de renderizado:
- Versión manual: al hacer scroll en el Data Table, ~12 ms de media
- Versión con Compiler: ~10 ms de media
Prácticamente igual, o ligeramente mejor. La lógica del Compiler es sólida.
Feedback del equipo
Tras terminar la migración pregunté al equipo:
- «Ya no hay que pensar si memoizar o no; es un alivio»
- «Al quitar los useMemo el código se ve mucho más limpio»
- «Un componente no era compatible; con ‘use no memo’ se resolvió al momento»
La sensación general fue positiva. Menos carga mental: ya no piensas en cada función si conviene memoizarla.
Resumen
React Compiler es una actualización importante del ecosistema React. En proyectos shadcn/ui, activarlo tiene ventajas claras:
- Rendimiento automático: sin useMemo/useCallback manuales; el Compiler lo hace en el build
- Código más simple: menos ruido de optimización, mejor legibilidad
- Menos carga mental: no debes decidir en cada función si memoizar
En la migración, ten en cuenta:
- Convención de nombres: desestructura props; evita
props.data - ESLint: ajusta la regla exhaustive-deps
- Compatibilidad con terceros: usa
'use no memo'donde haga falta
En Next.js 16 es el mejor punto de partida: viene integrado y la configuración es mínima. En otras herramientas, la configuración de Vite sirve de guía para migrar paso a paso.
Después de usar el Compiler, escribir React se siente distinto: más como JavaScript «normal», sin obsesionarte con el rendimiento en cada línea. Es una sensación muy agradable.
FAQ
FAQ
¿React Compiler aumenta el tamaño del bundle?
¿Los componentes de shadcn/ui son compatibles con React Compiler?
¿Debo eliminar useMemo/useCallback escritos a mano tras activar el Compiler?
¿En qué escenarios conviene desactivar el Compiler?
¿Qué convenciones de nombres exige el Compiler?
¿Cómo activar el Compiler en Next.js 16 y en proyectos Vite?
9 min de lectura · Publicado el: 31 mar 2026 · Actualizado el: 21 ago 2026
Tailwind y shadcn/ui en práctica
Si llegaste desde búsqueda, lo más rápido es ir al artículo anterior o siguiente de esta misma serie.
Anterior
Astro + Tailwind: configurar estilos sin conflictos con los componentes islands
¿Conflictos de estilos Tailwind CSS con la arquitectura islands de Astro? Esta guía explica astro-island/astro-slot, la integración correcta de Tailwind v4 y cuatro escenarios habituales de conflictos CSS con sus soluciones
Parte 12 de 14
Siguiente
Solución de problemas comunes de shadcn/ui: conflictos de estilos, componentes que no renderizan y errores de tipos
Repaso sistemático de las tres grandes categorías de problemas al desarrollar con shadcn/ui, con pasos de diagnóstico y soluciones para localizar y corregir conflictos de estilos, fallos de renderizado y errores de TypeScript
Parte 14 de 14



Comentarios
Inicia sesión con GitHub para dejar un comentario