Micro-frontends y web components para escalar tu app
Cómo diseñar una arquitectura de micro-frontends y web components para escalar tu aplicación web a medida
Hay un momento concreto en la vida de todo frontend monolítico en el que las cosas empiezan a ir mal. No se rompe todo de golpe: simplemente, un cambio en un botón tarda dos semanas en llegar a producción. Los conflictos de merge se multiplican. Y tocar una línea de CSS provoca efectos secundarios que nadie entiende. Lo hemos visto decenas de veces. Los micro-frontends surgen como respuesta directa a ese dolor, trasladando al frontend la misma lógica de descomposición que los microservicios aplicaron al backend hace ya una decada.
Vamos a recorrer como disenar esta arquitectura combinada con web components, con los tradeoffs que rara vez aparecen en los tutoriales rapidos.
Que son los micro-frontends y que problema resuelven
Un micro-frontend es una porcion de la interfaz de usuario que se desarrolla, prueba y despliega de forma independiente. Cada uno pertenece a un equipo autonomo que controla su ciclo de vida completo, desde el repositorio hasta la URL de produccion.
IKEA, Spotify y Zalando llevan anos trabajando con variantes de esta arquitectura. Lo que ha cambiado de verdad es la madurez de las herramientas. Module federation, import maps nativos del navegador y el estandar de web components han hecho que implementar micro-frontends sea viable sin frameworks exoticos ni hacks que se rompen al primer viento.
Para que quede claro con un ejemplo: en un e-commerce, el carrito de compra podria ser un micro-frontend, el catalogo de productos otro, y el proceso de checkout un tercero. Cada uno con su propio stack, sus dependencias y su pipeline de CI/CD. Equipos distintos, despliegues distintos, responsabilidades claras.
Cuando tiene sentido esta arquitectura (y cuando conviene no complicarse)
Voy a ser directo: la mayoria de aplicaciones no necesitan micro-frontends. Adoptar esta arquitectura en un proyecto pequeno con un solo equipo es, por usar un simil que usamos mucho internamente, como alquilar un trailer para ir a comprar el pan.
Los micro-frontends encajan cuando se dan varias de estas condiciones a la vez:
- Tres o mas equipos trabajan sobre la misma aplicacion web.
- Los ciclos de despliegue se alargan porque un cambio en una zona arrastra a todo el frontend.
- Diferentes partes de la aplicacion tienen requisitos tecnicos distintos (una zona necesita renderizado en tiempo real, otra es mayoritariamente estatica).
- La organizacion quiere que cada equipo pueda elegir su propio stack tecnologico.
- El producto tiene una vida util larga (mas de cinco anos) y va a crecer de forma sostenida.
Si tu equipo tiene menos de cinco desarrolladores frontend, un monolito bien estructurado con modulos claros probablemente sea la decision acertada. La complejidad operativa tiene un coste real que solo se justifica a partir de cierta escala.
Web components como contrato de interfaz
Los web components son un estandar del W3C que permite crear elementos HTML personalizados con encapsulacion nativa. Un web component tiene su propio DOM (Shadow DOM), sus propios estilos y su propia logica, aislados del resto de la pagina.
En una arquitectura de micro-frontends, los web components funcionan como el contrato de interfaz entre las piezas. Cada micro-frontend expone uno o varios custom elements que el shell (la aplicacion contenedora) inserta en el DOM como si fueran etiquetas HTML de toda la vida:
<mf-carrito usuario-id="12345" idioma="es"></mf-carrito>
<mf-catalogo categoria="electronica"></mf-catalogo>
La gracia: el micro-frontend del carrito puede estar construido con React, el del catalogo con Vue y el de checkout con Lit. Desde fuera, todos son custom elements con atributos y eventos estandar.
Las tres APIs que componen el estandar de web components:
- Custom Elements: permiten registrar nuevas etiquetas HTML con comportamiento propio mediante
customElements.define(). - Shadow DOM: encapsula el DOM interno y los estilos para evitar colisiones con el resto de la pagina.
- HTML Templates y Slots: definen fragmentos de markup reutilizables con puntos de insercion dinamicos.
Lit, mantenida por Google, simplifica la creacion de web components con sintaxis declarativa y un bundle inferior a 5 KB comprimido.
Estrategias de integracion: tres caminos, tres compromisos
La decision mas determinante de toda la arquitectura es como se integran las piezas. No hay una opcion universalmente mejor; cada una sacrifica algo.
Integracion en build-time
Cada micro-frontend se publica como paquete npm. La aplicacion shell los importa como dependencias y genera un bundle unico en tiempo de compilacion.
Es la opcion mas sencilla de implementar. Pero tiene trampa: sacrifica la independencia de despliegue. Cada vez que un micro-frontend cambia, hay que recompilar y redesplegar la aplicacion completa. En la practica, terminas con un monolito disfrazado.
Puede tener sentido para equipos que quieren separacion organizativa sin necesitar despliegues independientes. Pero si el objetivo es velocidad de entrega, esta opcion se queda corta.
Integracion en run-time
Los micro-frontends se cargan dinamicamente en el navegador del usuario. La aplicacion shell conoce las URLs de los distintos micro-frontends y los carga bajo demanda mediante import maps, module federation o scripts dinamicos.
Esta es la estrategia que ofrece mayor independencia. Cada equipo despliega su micro-frontend de forma autonoma, y los cambios aparecen en produccion sin tocar el shell ni los demas micro-frontends.
El peaje: complejidad adicional en el cliente. Hay que gestionar la carga asincrona, los estados intermedios (loading, error), la compatibilidad de versiones y el rendimiento de red. No es trivial, pero para la mayoria de casos a escala, merece la pena.
Integracion server-side
Un servidor o proxy compone la pagina final combinando fragmentos HTML de distintos servicios. Tecnologias como ESI (Edge Side Includes), SSI (Server Side Includes) o frameworks como Podium (desarrollado por Finn.no) permiten ensamblar micro-frontends antes de que la respuesta llegue al navegador.
Esta estrategia ofrece el mejor rendimiento percibido (el HTML llega ya compuesto) y funciona bien con SEO. A cambio, anade complejidad en infraestructura y dificulta las interacciones dinamicas entre micro-frontends en el cliente.
Module federation con Webpack y Vite
Module Federation, introducido en Webpack 5, fue el primer mecanismo nativo de un bundler para compartir modulos entre aplicaciones independientes en tiempo de ejecucion. La idea: un micro-frontend expone modulos concretos y otros los consumen sin pasar por npm.
La configuracion basica en Webpack define un micro-frontend como "remote" que expone componentes:
// webpack.config.js del micro-frontend de catalogo
new ModuleFederationPlugin({
name: 'catalogo',
filename: 'remoteEntry.js',
exposes: {
'./ProductList': './src/components/ProductList',
'./ProductDetail': './src/components/ProductDetail',
},
shared: ['react', 'react-dom'],
})
El shell los consume declarando los remotes disponibles y realizando imports dinamicos:
const ProductList = React.lazy(() => import('catalogo/ProductList'));
Vite soporta module federation mediante @originjs/vite-plugin-federation. Desde 2025, el proyecto se ha independizado parcialmente de Webpack con @module-federation/enhanced, compatible con Webpack, Rspack y experimentalmente con Vite.
Un punto critico: la gestion de dependencias compartidas. Si cada micro-frontend carga su propia copia de React, el bundle total se dispara. La opcion shared con singleton: true garantiza una sola instancia de la biblioteca compartida. Hemos visto proyectos donde ignorar esto duplicaba el peso del JavaScript.
Gestion del estado compartido entre micro-frontends
Aqui es donde la mayoria de arquitecturas de micro-frontends acumulan deuda tecnica. La regla que seguimos siempre: compartir lo minimo imprescindible.
Varios mecanismos para comunicar estado entre micro-frontends sin crear acoplamiento:
- Custom Events del DOM: cada micro-frontend emite y escucha eventos nativos del navegador. Es la opcion mas desacoplada y compatible con cualquier framework.
- Event bus ligero: un modulo compartido que actua como intermediario de mensajes. Mas estructurado que los custom events, pero introduce una dependencia compartida.
- URL como fuente de verdad: parametros de ruta y query strings como mecanismo de comunicacion. Funciona especialmente bien para estado de navegacion y filtros.
- Shared state store: librerias como Zustand o stores reactivos minimalistas expuestos como modulo federado. Solo para datos que genuinamente necesitan ser reactivos y compartidos (datos del usuario autenticado, carrito de compra, preferencias globales).
La tentacion de crear un Redux global que todos los micro-frontends consuman es grande. Pero destruye la independencia que justifica toda la arquitectura. Nuestra recomendacion: si dos micro-frontends comparten demasiado estado, probablemente deberian ser uno solo.
Routing: dos niveles, una sola experiencia
El enrutamiento funciona en dos niveles. El shell gestiona las rutas de nivel superior y decide que micro-frontend montar. Cada micro-frontend gestiona sus propias subrutas internas.
Un enfoque habitual es asignar prefijos de ruta a cada micro-frontend:
/catalogo/*lo gestiona el micro-frontend de catalogo./checkout/*lo gestiona el micro-frontend de checkout./cuenta/*lo gestiona el micro-frontend de perfil de usuario.
El shell escucha los cambios de ruta (via popstate o un router ligero) y monta o desmonta los micro-frontends correspondientes. Frameworks como single-spa proporcionan esta orquestacion de forma declarativa, con registro de aplicaciones y gestion del ciclo de vida (bootstrap, mount, unmount).
El problema que mas nos encontramos es el de las transiciones. Cuando el usuario navega de un micro-frontend a otro, hay que gestionar la descarga del anterior, la carga del nuevo y el estado intermedio sin que la experiencia se resienta. Prefetch de los chunks del siguiente micro-frontend y transiciones animadas con la View Transitions API ayudan bastante a suavizar esos momentos.
Consistencia visual con un design system compartido
Si cada equipo construye sus propios botones, al final cada boton sera ligeramente diferente. Un design system compartido es imprescindible para que la aplicacion no parezca un Frankenstein hecho con retales.
La implementacion mas efectiva es publicar el design system como biblioteca de web components. Al ser estandar del navegador, cualquier micro-frontend puede consumirlos independientemente de su framework. Un boton definido como <ds-button variant="primary"> funciona igual en React, Vue, Angular o vanilla JavaScript.
El design system debe incluir:
- Componentes de UI (botones, formularios, modales, tablas).
- Tokens de diseno (colores, tipografias, espaciados) como custom properties de CSS.
- Guias de uso documentadas con ejemplos interactivos (Storybook es el estandar de facto).
El versionado del design system requiere atencion. Un breaking change en un componente base puede afectar a todos los micro-frontends. Versionado semantico estricto y mantener versiones anteriores durante un periodo de migracion reduce el riesgo de roturas en cascada.
Testing: tres niveles, responsabilidades claras
El testing se organiza en tres niveles: tests unitarios y de componentes dentro de cada micro-frontend (Vitest, Testing Library), tests de integracion que verifican la comunicacion entre micro-frontends (eventos, rutas, estado compartido), y tests end-to-end con Playwright o Cypress que recorren flujos de usuario completos atravesando varios micro-frontends.
Los tests end-to-end son los mas costosos de mantener, asi que conviene limitarlos a los flujos criticos de negocio. Un anti-patron que vemos con frecuencia: nadie se responsabiliza de los tests de integracion. Asignar esa responsabilidad a un equipo de plataforma evita que quede en tierra de nadie.
Independencia de despliegue y pipeline de CI/CD
Cada equipo publica su micro-frontend en su propia URL (normalmente un CDN) y la aplicacion shell lo consume en tiempo de ejecucion. Un pipeline tipico incluye lint y tests en cada push, build con hash en el nombre del archivo, deploy a un bucket de almacenamiento (S3, Cloud Storage), actualizacion de un manifiesto JSON que el shell consulta, e invalidacion de cache del CDN.
Los canary deployments funcionan especialmente bien aqui: puedes dirigir un porcentaje del trafico a la nueva version de un micro-frontend concreto sin afectar al resto de la aplicacion. Es una red de seguridad que en monolitos cuesta mucho mas conseguir.
Tradeoffs reales: lo que nadie te cuenta en los tutoriales
Toda decision arquitectonica tiene costes. Y preferimos que los conozcas antes de embarcarte:
Rendimiento: multiples bundles y potencialmente multiples copias de frameworks. Sin gestion cuidadosa de dependencias compartidas, el tamano total de JavaScript puede duplicarse respecto a un monolito.
Complejidad operativa: en lugar de un pipeline de CI/CD, tienes cinco o diez. Docker Compose y configuraciones de proxy inverso se convierten en companeros habituales para el entorno de desarrollo local.
Experiencia de desarrollo: un desarrollador que trabaja en una feature que cruza dos micro-frontends necesita levantar ambos en local y coordinar cambios en dos repositorios. La productividad individual puede bajar incluso cuando la productividad del equipo sube.
Debugging: rastrear un bug que cruza varios micro-frontends requiere correlacionar logs de multiples servicios. Herramientas de observabilidad distribuida dejan de ser opcionales.
Migracion progresiva: sin saltos al vacio
La migracion desde un monolito frontend no tiene por que ser de todo o nada. Un enfoque progresivo funciona mejor, y ademas te permite validar hipotesis antes de comprometerte con la arquitectura completa:
- Extraer una zona poco acoplada de la aplicacion como primer micro-frontend piloto.
- Construir el shell minimo que integre el monolito existente y el nuevo micro-frontend.
- Validar la arquitectura en produccion con trafico real durante semanas.
- Extraer progresivamente mas zonas segun las necesidades del equipo.
Este enfoque, conocido como "strangler fig pattern" aplicado al frontend, permite aprender de los errores con un radio de impacto pequeno antes de comprometer toda la aplicacion. Es la via que recomendamos siempre.
Si tu empresa esta evaluando como escalar una aplicacion web compleja y necesitas asesoramiento sobre si los micro-frontends encajan en tu caso concreto, contacta con Tangram Consulting. Analizamos tu contexto tecnico y organizativo para recomendar la arquitectura que realmente se adapta a tus necesidades, sin sobringenieria innecesaria.