Arquitectura por Funcionalidades en React (Feature-Based)
Arquitectura por Funcionalidades en React (Feature-Based)
La arquitectura por funcionalidades (Feature-Based) es uno de los enfoques más utilizados en aplicaciones React en producción, especialmente cuando el producto crece en módulos claros (autenticación, facturación, proyectos, usuarios, etc.). Su principio central es simple: organizar el código por dominio funcional, agrupando en un mismo lugar todo lo que evoluciona junto.
Qué reglas necesita para no degradarse con el tiempo y cómo combinarla con prácticas modernas de React y frameworks como Next.js.
Qué es la arquitectura Feature-Based
En lugar de agrupar por tipo técnico (components/, hooks/, utils/), se agrupa por módulos de negocio. Cada módulo contiene su UI, lógica, estado remoto, validaciones, tipos y pruebas. Esto mejora:
- Mantenibilidad: se encuentra el código de una funcionalidad en un solo lugar.
- Escalabilidad: nuevos módulos se agregan sin desordenar el resto.
- Trabajo en paralelo: los equipos pueden operar con límites más claros.
Estructura recomendada
Una base sólida (y fácil de mantener) suele verse así:
src/
app/ # bootstrap, providers, rutas, configuración global
features/ # dominios funcionales (módulos)
auth/
projects/
billing/
users/
components/
ui/ # componentes reutilizables (sin lógica de negocio)
services/ # clientes HTTP, SDKs, integraciones externas
lib/ # utilidades transversales (helpers, formatting, etc.)
types/ # tipos compartidos (si aplica)
assets/ # recursos estáticos
Por qué esta estructura funciona
features/concentra la evolución del producto: es donde vive el “trabajo real”.components/uiprotege la reutilización: evita que componentes “genéricos” terminen acoplados al negocio.services/centraliza integraciones: elimina llamadas dispersas y facilita cambios (por ejemplo, proveedor de pagos).app/mantiene la configuración global separada del dominio.
Cómo debe verse un módulo de feature
Ejemplo: src/features/projects/
src/features/projects/
api/ # requests del dominio (queries/mutations)
components/ # componentes propios del dominio (no genéricos)
hooks/ # hooks del dominio
routes/ # pages o route components del dominio (según framework)
types/ # tipos del dominio
utils/ # utilidades internas del dominio
validation/ # schemas (zod, yup, etc.)
tests/ # pruebas del dominio (unit/integration)
index.ts # export público del módulo
Regla práctica: si algo solo existe por “Projects”, debe vivir dentro del módulo.
Reglas de importación (lo que evita que se rompa)
Feature-Based funciona bien solo si existen límites claros. Estas reglas suelen ser suficientes:
1) components/ui no conoce dominios
Los componentes UI reutilizables no deben:
- Importar desde
features/* - Consumir APIs
- Tener reglas de negocio
Solo reciben props y renderizan.
2) Los módulos de features/ no se importan libremente entre sí
Evita que auth importe cosas internas de billing, o que projects dependa de users sin control.
Si se necesita compartir, hay 3 caminos comunes:
- Mover algo a
lib/otypes/si es transversal - Exponer un contrato estable vía
features/x/index.ts - Crear una capa compartida explícita (por ejemplo
shared/), si el proyecto lo justifica
3) Las integraciones viven en services/
El código de UI no debería conocer URLs, tokens, headers, ni detalles de SDKs.
Eso reduce el costo de cambios y mejora la trazabilidad.
Feature-Based + estado remoto (React Query / SWR)
En aplicaciones modernas, una gran parte del “estado” es realmente estado remoto (servidor). Buenas prácticas:
- Mantener queries/mutations dentro del dominio:
src/features/projects/api/getProjects.tssrc/features/projects/api/createProject.ts
- Evitar duplicar en
useStatedatos que ya gestiona React Query/SWR. - Definir claves de caché por dominio (
projectsKeys) para invalidación consistente.
Ejemplo conceptual:
// src/features/projects/api/projectsKeys.ts
export const projectsKeys = {
all: ['projects'] as const,
list: (filters: Filters) => [...projectsKeys.all, 'list', filters] as const,
detail: (id: string) => [...projectsKeys.all, 'detail', id] as const
}
Cómo se integra con Next.js o React Router
Hay dos enfoques comunes:
Opción A: rutas en app/ y lógica en features/
src/app/routes/*(oapp/en Next.js)- Cada ruta usa componentes de
features/*
Ventaja: mantiene routing como capa de “ensamble” y deja dominios limpios.
Opción B: rutas dentro de cada feature
src/features/projects/routes/*- Un router principal compone rutas desde features
Ventaja: cada dominio controla su entrada.
Riesgo: exige disciplina para no duplicar routing y layouts.
Pruebas: el principal beneficio en equipos
Cuando la arquitectura está bien aplicada:
- Las pruebas del dominio viven con el dominio.
- La UI genérica tiene pruebas aisladas.
- Los mocks de integraciones están centralizados.
Sugerencia práctica:
- Pruebas unitarias para validaciones y utilidades del dominio.
- Pruebas de integración para flujos críticos (formularios, navegación, permisos).
Errores frecuentes (y cómo evitarlos)
1) “features” se convierte en un nuevo “components”
Síntoma: todo termina en features/common/ o features/shared/ sin criterio.
Solución: lo compartido debe estar justificado y tener dueño (lib/, types/, services/).
2) UI reusable contaminada por negocio
Síntoma: Button conoce permisos, roles o estados internos de un módulo.
Solución: el negocio se resuelve en la feature y se pasa como props.
3) Dependencias circulares entre módulos
Síntoma: auth importa users, users importa auth.
Solución: contratos estables en index.ts, mover tipos a types/, o crear un módulo shared explícito.
Checklist de implementación (para equipos)
- Cada feature tiene un
index.tscon exports públicos (evitar imports internos). components/uino importa desdefeatures/.- Acceso a APIs centralizado en
services/. - Queries/mutations ubicadas dentro de cada feature.
- Tipos compartidos en
types/(no duplicados por módulo). - Reglas de importación verificadas (idealmente con ESLint boundaries).
- Documentación corta de “reglas del repo” para nuevos integrantes.
Recomendación empresarial
Para la mayoría de productos que están creciendo, una base equilibrada es:
Feature-Based + UI reutilizable + capa de servicios + reglas de importación
Esto permite evolucionar rápido sin perder control, reduce costos de mantenimiento y facilita que el equipo crezca sin reescribir la base del proyecto.
Conclusión
Feature-Based no es solo una estructura de carpetas. Es una forma de operar: el código se organiza según cómo evoluciona el producto, y se protegen límites para evitar acoplamientos. Cuando se aplica con reglas claras, es una de las arquitecturas más efectivas para React a escala.