Errores Comunes & Trampas en Arquitectura React
Una guía de consulta para los errores más comunes que cometen los arquitectos React y líderes técnicos en proyectos reales - agrupados por área y ordenados de lo más fundamental a lo específico. Úsalo en una revisión previa al lanzamiento, una lista de verificación de auditoría de código, o un documento de integración para nuevos contratados senior.
Cómo usar esta lista
- Recorre cada elemento - si no puedes decir con confianza "no hacemos esto," añade un ticket.
- No la trates como una guía de estilo - estos son modos de fallo arquitectónico, no reglas de lint.
- Revísita en cada hito. Los errores que duelen en MVP son diferentes a los que duelen a escala.
Estado & Datos (1–6)
-
Estado global por defecto - Recurrir a Redux, Zustand, o un átomo global en el momento en que dos componentes comparten datos.
- Por qué sucede: Los leads asumen "compartido = global" y saltan estado local, estado elevado, y estado en URL como opciones.
- Solución: Comienza con estado local; eleva solo cuando un hermano realmente lo necesita; promueve a global solo cuando 3+ ramas no relacionadas lo leen.
-
Context para valores en actualización continua - Poner estado de formulario, contadores, o posición del cursor en un proveedor Context.
- Por qué sucede: "Context resuelve prop drilling" se overgeneraliza.
- Solución: Context es para valores leídos ampliamente con baja frecuencia (tema, usuario autenticado, idioma). Para valores en actualización continua, usa Zustand, Jotai, o estado local.
-
Fetching en useEffect - Manual
fetch+setState+ booleanos loading/error en cada componente.- Por qué sucede: Es lo que la mayoría de tutoriales todavía muestran.
- Solución: Usa TanStack Query, SWR, o RSC. Caching, deduplicación, reintentos y manejo de errores son problemas resueltos.
-
Sin estrategia de invalidación de caché - Las mutaciones tienen éxito pero la vista de lista sigue mostrando datos obsoletos.
- Por qué sucede: Los equipos conectan una librería de datos pero nunca codifican convenciones de invalidación.
- Solución: Define una taxonomía de query-key desde el principio y usa
invalidateQueries/mutateconsistentemente en cada mutación.
-
Fetching solo en cliente en Next.js App Router - Cada página comienza con
"use client"y obtiene datos en el cliente.- Por qué sucede: Memoria muscular del Pages Router o días de SPA.
- Solución: Por defecto componentes servidor; obtén datos en el servidor; cambia a cliente solo para interactividad.
-
Estado de URL almacenado en React state - Filtros, pestañas, paginación, y banderas dialog-open guardadas en
useState.- Por qué sucede:
useStatees la ruta más corta. - Solución: Pon estado compartible/back-forward-able en la URL (
searchParams, parámetros de ruta). ReservauseStatepara estado de UI verdaderamente efímero.
- Por qué sucede:
Componentes & Abstracciones (7–12)
-
Abstracción prematura - Extraer un componente "reutilizable" después del segundo uso, fijando una API deficiente.
- Por qué sucede: Dogma DRY e impulso de verse ordenado.
- Solución: Espera al tercer uso real. Tres copias similares son más baratas que una abstracción incorrecta.
-
Envolturas sobre envolturas - Un
Buttonpersonalizado envuelve elButtonde shadcn que envuelve elButtonde Radix - nadie sabe qué props llegan al DOM.- Por qué sucede: Los equipos cubren cada API de terceros "por si acaso".
- Solución: Usa componentes de terceros directamente a menos que tengas una razón concreta para envolverlos. Cuando envuelvas, re-exporta el tipo subyacente con
ComponentPropsWithoutRef.
-
Prop drilling ignorado - Un prop pasa por 6 componentes que no lo usan.
- Por qué sucede: Refactorizar cadenas de props se siente arriesgado.
- Solución: Usa composición (
children, props de ranura), un Context colocalizado, o promueve el consumidor hacia arriba en el árbol.
-
Sobre-memoización -
useMemo,useCallback, yReact.memorociados por todas partes.- Por qué sucede: Cargo-culto como "mejor práctica de rendimiento".
- Solución: Memoiza solo después de perfilar un problema real de re-renderizado. La memoización tiene un costo - tanto en tiempo de ejecución como cognitivo.
-
Falta de memoización en rutas realmente activas - Una tabla de 10,000 filas se re-renderiza cada celda en cada pulsación.
- Por qué sucede: "React es rápido" se overgeneraliza; el perfilado se salta.
- Solución: Perfila con React DevTools, virtualiza listas largas, divide context, y memoiza el componente realmente activo.
-
Componentes de página masivos - Archivos de página de 600 líneas haciendo fetching, estado, layout, y lógica de negocio.
- Por qué sucede: Comienza como 80 líneas y crece una característica a la vez.
- Solución: Extrae módulos de característica con una API pública clara. Una página debe orquestar principalmente - no implementar.
Límite Cliente/Servidor (13–17)
-
"use client"en la parte superior del árbol - Marcar el layout raíz como cliente tira todo el árbol al paquete del cliente.- Por qué sucede: Un hijo necesita un hook y el lead añade
"use client"al ancestro más cercano. - Solución: Empuja
"use client"tan profundo como sea posible - aislalo a la hoja que realmente usa hooks/eventos.
- Por qué sucede: Un hijo necesita un hook y el lead añade
-
Tratar RSC como SSR - Esperar
useStateouseEffecten un componente servidor.- Por qué sucede: El modelo mental del Pages Router se mantiene.
- Solución: Los componentes servidor son una primitiva diferente - sin hooks, sin estado, sin manejadores de eventos. Aprende el límite antes de escribir código.
-
Sorpresas de serialización - Pasar una instancia de clase, función, o objeto que envuelve Date del componente servidor al componente cliente.
- Por qué sucede: Los tipos se compilan bien; el error aparece en tiempo de ejecución.
- Solución: Pasa datos planos serializables (strings, números, arrays, POJOs). Reconstruye instancias de clase en el componente cliente si es necesario.
-
Mezclar patrones de Pages Router en App Router - Convenciones
getServerSideProps,getStaticProps, o_app.tsxfiltrándose enapp/.- Por qué sucede: Tutoriales, Stack Overflow, y datos de entrenamiento de LLM antiguos todos asumen Pages Router.
- Solución: Lee
node_modules/next/dist/docs/directamente. Obedece avisos de deprecación. No pegues desde fuentes anteriores a 2024.
-
Cascadas de datos en componentes servidor anidados -
awaits secuenciales que bloquean el renderizado.- Por qué sucede: Cada componente obtiene sus propios datos, sin conocimiento de hermanos.
- Solución: Paraleliza con
Promise.all, alza fetches a un padre compartido, o usaloading.tsxa nivel de ruta + streaming.
Rendimiento (18–21)
-
Sin presupuesto de paquete - Sin comprobación de CI en tamaño JS; cada dependencia crece silenciosamente el paquete.
- Por qué sucede: "Nos ocuparemos de esto después."
- Solución: Añade un presupuesto de tamaño a CI (p. ej.,
@next/bundle-analyzer,size-limit) y falla PRs que lo superen.
-
Trampas tree-shaking en archivos barril - Importar desde
index.tsque re-exporta todo.- Por qué sucede: Los barriles se sienten limpios en tiempo de desarrollo.
- Solución: Importa desde la ruta de fuente (
lib/date/format) o audita que tu bundler realmente hace tree-shaking de tus barriles (muchos no lo hacen de forma confiable).
-
Componentes cliente para contenido estático - Páginas de marketing marcadas
"use client"por una única animación.- Por qué sucede: Un widget interactivo tira toda la página al paquete del cliente.
- Solución: Mantén la página como componente servidor; renderiza el widget interactivo como una pequeña isla cliente.
-
Sin monitoreo de Core Web Vitals - Enviar sin telemetría LCP/INP/CLS.
- Por qué sucede: Las pruebas sintéticas pasan; las métricas de usuario real son un pensamiento posterior.
- Solución: Conecta Vercel Analytics,
web-vitals+ tu proveedor de analytics, o PostHog Web Vitals desde el primer día.
Calidad & Testing (22–25)
-
Probar detalles de implementación - Las pruebas aseveran llamadas
setState, nombres de método interno, o estado privado.- Por qué sucede: Hábitos de la era Enzyme y uso excesivo de snapshot testing.
- Solución: Prueba comportamiento visible al usuario con Testing Library. Asevera lo que el usuario ve y hace, no cómo el estado se actualiza.
-
Mockear la red en pruebas de integración -
jest.mock("axios")stubs dispersos por la suite.- Por qué sucede: MSW se siente como más configuración inicial.
- Solución: Usa MSW (Mock Service Worker) una vez - capa de red real, manejadores realistas, compartidos entre unit y E2E.
-
Sin límites de error - Un error de tiempo de ejecución desmonta toda la app.
- Por qué sucede: Los error boundaries se sienten como una preocupación "la añadiremos después".
- Solución: Envuelve el layout raíz, cada segmento de ruta, y cualquier widget arriesgado (gráficos, embebidos de terceros) en error boundaries con fallbacks sensatos.
-
Sin presupuesto de accesibilidad - La navegación por teclado nunca se prueba; axe-core /
jsx-a11ynunca se habilita.- Por qué sucede: A11y se reprioriza detrás de características hasta una auditoría de cumplimiento.
- Solución: Activa
eslint-plugin-jsx-a11ydesde el primer día, ejecuta axe-core en E2E, y prueba navegación por teclado como parte de la revisión de PR.
Equipo & Proceso (26–30)
-
Elecciones de stack sin ADR - Cada nuevo hire revisa "¿por qué no Remix?"
- Por qué sucede: Las decisiones viven en hilos de Slack que se desplazan al olvido.
- Solución: Compromete Architecture Decision Records (ADRs) a
docs/adr/con contexto, decisión, consecuencias, y una fecha de revisión.
-
Monorepo como bala de plata - Turborepo/Nx adoptado para una app y un paquete compartido.
- Por qué sucede: El contenido de influenciadores FAANG hace que los monorepos suenen universales.
- Solución: Mantente en repo único hasta que tengas al menos dos superficies desplegables compartiendo código no trivial. El overhead solo se paga en ese punto.
-
Reescribir en lugar de migrar - "Vamos a reescribirlo en Remix/TanStack Start/Next.js 15."
- Por qué sucede: Las migraciones se sienten más lentas que greenfield, y los leads subestiman el riesgo de regresión de características.
- Solución: Migra de forma incremental detrás de una bandera de características; prueba paridad ruta por ruta antes de retirar el código antiguo.
-
Deuda DX - Type-check lento, HMR lento, pruebas inestables - ignoradas hasta que la velocidad del equipo se colapsa.
- Por qué sucede: DX no tiene un PM empujando por ello.
- Solución: Presupuesta tiempo de DX explícito cada trimestre. Rastrea tiempo de
tsc --noEmit, latencia HMR, y duración de test-suite como métricas visibles.
-
Contratar por familiaridad de framework sobre fundamentos - Un experto en Next.js que no puede explicar reconciliación, claves, o memoización.
- Por qué sucede: Las entrevistas sobre-indexan en el framework caliente actual.
- Solución: Entrevista para modelo mental de React (renderizado, estado, efectos, claves) y una habilidad senior en JS/TS (tipos, async, perf). Las APIs de framework son aprendibles en una semana; los fundamentos no.
Aplicando la lista
- Tier 1 (1–6): Estado y datos. Equivocarse en estos es la causa #1 de deuda técnica de frontend - fíjalos primero.
- Tier 2 (7–12): Forma de componente. Afecta la velocidad de cada nueva característica; aborda durante cualquier refactor importante.
- Tier 3 (13–17): Higiene RSC / límite. Crítico en Next.js 15 / App Router - no envíes sin revisar.
- Tier 4 (18–21): Rendimiento. Añade presupuestos y telemetría antes de que el paquete sea irreparable.
- Tier 5 (22–25): Calidad & testing. Barato de configurar en una nueva base de código; caro de retroajustar.
- Tier 6 (26–30): Equipo & proceso. Los elementos de mayor apalancamiento - pero los más fáciles de posponer.
Relacionado
- Lista de Verificación de Decisión de Arquitectura React - Decisiones a tomar antes de que las trampas siquiera se apliquen.
- Mejores Prácticas de Fundamentos React - Reglas a nivel de componente que previenen muchos de estos errores.
- Flujo de Trabajo de Decisión de Componente - Cómo decidir dónde vive un componente y qué posee.
- Rendimiento - Técnicas concretas para las trampas de rendimiento anteriores.