Arquitectura Frontend: cómo escalar tu proyecto sin morir en el intento
La arquitectura frontend no es abstraerse en diagramas: es decidir hoy qué te va a salvar mañana cuando el proyecto crezca.
Equipo NUCBA
Por qué la arquitectura frontend importa más de lo que pensás
La arquitectura frontend no es un lujo ni un capricho de arquitectos de software que dibujan diagramas en Miro. Es la diferencia entre un proyecto que escala sin drama y uno donde cada feature nueva te hace sudar frío porque no sabés dónde va el archivo ni cómo evitar romper medio sitio.
Cuando arrancás un proyecto, todo parece simple: un par de componentes, una carpeta src, y listo. Pero a las pocas semanas tenés 80 archivos en la misma carpeta, lógica de negocio mezclada con componentes visuales, y tres formas distintas de hacer el mismo fetch. La arquitectura frontend es el conjunto de decisiones que tomás para que eso no pase.
No se trata de sobre-ingeniería ni de aplicar patrones porque sí. Se trata de preguntarte: ¿dónde va este código? ¿Cómo lo pruebo? ¿Qué pasa cuando otro dev lo toca en seis meses?
Principios clave de una arquitectura frontend escalable
Separación de responsabilidades
El error más común es meter todo en los componentes. Lógica de negocio, llamadas a APIs, validaciones, transformaciones de datos, todo amontonado en un useEffect de 200 líneas.
Una arquitectura frontend sólida separa claramente:
- Presentación: componentes visuales que reciben props y renderizan UI. No saben de dónde vienen los datos ni qué pasa cuando clickeás un botón.
- Lógica de negocio: reglas, validaciones, cálculos. Vive en hooks custom, servicios o utils.
- Gestión de estado: cómo y dónde vivís el estado de la app (local, global, server state).
- Acceso a datos: cómo te comunicás con APIs, cómo cachés, cómo manejás errores de red.
Ejemplo concreto: un formulario de checkout.
Mal:
function Checkout() { const [cart, setCart] = useState([]); const [loading, setLoading] = useState(false); useEffect(() => { fetch('/api/cart') .then(res => res.json()) .then(data => setCart(data)) }, []); const handleSubmit = async () => { setLoading(true); const total = cart.reduce((acc, item) => acc + item.price * item.quantity, 0); if (total < 100) { alert('Mínimo $100'); return; } await fetch('/api/orders', { method: 'POST', body: JSON.stringify(cart) }); setLoading(false); } return <div>...</div> }
Bien:
// hooks/useCart.js export function useCart() { return useQuery('cart', () => fetchCart()); } // services/orderService.js export function calculateTotal(cart) { return cart.reduce((acc, item) => acc + item.price * item.quantity, 0); } export function validateOrder(cart) { const total = calculateTotal(cart); if (total < 100) throw new Error('Mínimo $100'); } // components/Checkout.jsx function Checkout() { const { data: cart } = useCart(); const mutation = useMutation(createOrder); const handleSubmit = () => { try { validateOrder(cart); mutation.mutate(cart); } catch (error) { toast.error(error.message); } } return <div>...</div> }
Ahora calculateTotal lo podés testear sin renderizar nada. useCart lo reutilizás en cualquier componente. Y Checkout solo orquesta.
Estructura de carpetas predecible
No existe LA estructura perfecta, pero sí existen estructuras que te ayudan a encontrar las cosas y estructuras que te generan fricción constante.
Opciones probadas:
Por tipo de archivo (funciona en proyectos chicos):
src/
components/
hooks/
services/
utils/
pages/
Por feature (escala mejor):
src/
features/
auth/
components/
hooks/
services/
AuthPage.jsx
checkout/
components/
hooks/
services/
CheckoutPage.jsx
shared/
components/
hooks/
utils/
La clave: que el criterio sea consistente. Si empezás por feature, no mezcles. Si un dev nuevo tiene que adivinar dónde va un archivo, tu estructura ya perdió.
Gestión de estado sin drama
El estado es donde más se pudre la arquitectura frontend. Tres preguntas que te salvan:
- ¿Es estado local o global? No todo necesita Redux/Zustand. Si solo un componente lo usa,
useStatealcanza. - ¿Es estado de servidor o de cliente? Los datos del usuario que vienen de una API no son lo mismo que el estado del modal abierto. Usá librerías como React Query o SWR para server state.
- ¿Cuánto dura? Hay estado efímero (scroll position), estado de sesión (usuario logueado) y estado persistente (preferencias en localStorage).
Ejemplo de segregación clara:
// Estado de servidor -> React Query const { data: user } = useQuery('user', fetchUser); // Estado global de cliente -> Zustand const { theme, setTheme } = useThemeStore(); // Estado local -> useState const [isOpen, setIsOpen] = useState(false);
Tooling y configuración como código
Tu arquitectura frontend también incluye el setup: linters, formatters, bundlers, testing. Si cada dev tiene una config distinta, vas a tener PRs con 500 líneas de cambios de formato.
Checklist básico:
- ESLint + Prettier configurados y compartidos (
.eslintrc,.prettierrcen el repo) - Husky + lint-staged para hooks de pre-commit
- TypeScript (o al menos JSDoc) para tipar contratos críticos
- Vite/Turbopack en lugar de Webpack si arrancás hoy
- Testing: Vitest para unit, Playwright para e2e
No hace falta todo desde el día uno, pero dejalo documentado y automatizado. La arquitectura frontend no es solo código, es el proceso para escribirlo.
Errores comunes que pudren tu arquitectura frontend
Importar desde cualquier lado
Si un componente de features/checkout importa directo desde features/auth/services, estás acoplando features. Creá una carpeta shared para lo que se reutiliza.
Componentes de 500 líneas
Si tu componente tiene lógica compleja, múltiples useEffect, y maneja 10 props, partilo. Extraé custom hooks, componentes más chicos, y servicios.
Estado duplicado
Tenés user en Redux, en un contexto, y en localStorage. Definí una única fuente de verdad por cada dato.
Sin convenciones de naming
Archivos que se llaman utils.js, helpers.js, index.js por todos lados. Nombrá con intención: formatCurrency.js, useDebounce.js, Button/Button.jsx.
Migraciones y deuda técnica
La realidad: la mayoría de los proyectos frontend no arrancaron con arquitectura. Tenés código legacy, decisiones viejas, y no podés reescribir todo.
Estrategia de migración iterativa:
- Identificá puntos de dolor: ¿qué parte del código te frena más?
- Definí el destino: documentá cómo querés que se vea el código nuevo (estructura, patrones, libs).
- Migrá por feature: cada vez que tocás una feature, la llevás al nuevo estándar.
- Automatizá: codemods, scripts, linters custom para forzar el nuevo patrón.
No migres todo de golpe. Migrá lo que tocás.
Preguntas frecuentes
¿Cuándo aplicar arquitectura frontend en un proyecto?
Desde el primer día, pero empezá simple. Definí estructura de carpetas, separación básica entre componentes y lógica, y configurá linters. La arquitectura crece con el proyecto, no antes.
¿Qué hacer si ya tengo un proyecto sin arquitectura clara?
Empezá por auditar: identificá carpetas caóticas, componentes gigantes, estado duplicado. Priorizá lo que más duele. Documentá el estándar nuevo y aplicalo en cada PR. No necesitás un big bang.
¿Es necesario TypeScript para tener buena arquitectura frontend?
No es obligatorio, pero ayuda un montón. Los tipos documentan contratos, previenen bugs y facilitan refactors. Si no querés TS, al menos usá JSDoc en funciones críticas y props de componentes.