← Blog Arquitectura

Microfrontends con Angular y Native Federation: lo que aprendí llevando más de 15 módulos a producción

10 de octubre de 2026 · 6 min de lectura

Hace unos meses me tocó llevar una plataforma corporativa a una arquitectura de microfrontends: un portal que carga más de 15 módulos, cada uno con su propio repositorio, su propio equipo y su propio despliegue. Hoy esos módulos salen a producción por separado, sin coordinar ventanas de despliegue entre equipos.

No voy a explicar qué es un microfrontend, de eso ya hay bastante escrito. Quiero contar lo que no sale en los tutoriales, lo que solo aparece cuando el segundo, el quinto y el décimo módulo ya están en producción.

Primero: cuándo NO usar microfrontends

Empiezo por aquí porque es la pregunta que más me hacen y la respuesta honesta es "casi nunca al inicio".

Te convieneNo te conviene
Varios equipos que necesitan desplegar sin esperarseUn solo equipo trabajando en un solo producto
Dominios de negocio claramente separados (ventas, calidad, RRHH)Pantallas que comparten casi todo el estado
Módulos que crecen a ritmos distintosUn producto que todavía está buscando su forma
Una plataforma que va a vivir añosUn MVP que tiene que salir en semanas

Si estás en la columna de la derecha, un monolito bien modularizado te da el 80 % del beneficio con el 20 % del costo. Los microfrontends son, sobre todo, una decisión de organización. Pagan cuando el cuello de botella es la coordinación entre equipos, no la calidad del código.

La arquitectura, en una imagen

                    ┌──────────────────────────────┐
   usuario  ───────▶│  HOST (portal)               │
                    │  · login único (OIDC)        │
                    │  · menú según permisos       │
                    │  · lee el manifiesto         │
                    └──────────────┬───────────────┘
                                   │ carga en tiempo de ejecución
            ┌──────────────────────┼──────────────────────┐
            ▼                      ▼                      ▼
     remoteEntry.json       remoteEntry.json       remoteEntry.json
     ┌──────────────┐       ┌──────────────┐       ┌──────────────┐
     │  Ventas      │       │  Calidad     │       │  RRHH        │
     │  repo propio │       │  repo propio │       │  repo propio │
     │  deploy      │       │  deploy      │       │  deploy      │
     │  propio      │       │  propio      │       │  propio      │
     └──────────────┘       └──────────────┘       └──────────────┘

El stack es Angular con Native Federation (@angular-architects/native-federation). Es el sucesor de Module Federation para el builder moderno de Angular basado en esbuild: en lugar de depender de webpack, usa los estándares del navegador (módulos ES e import maps).

El host no conoce a los módulos en tiempo de compilación. Los descubre al arrancar, desde un manifiesto:

// federation.manifest.json (uno por ambiente)
{
  "ventas":  "https://ventas.ejemplo.com/remoteEntry.json",
  "calidad": "https://calidad.ejemplo.com/remoteEntry.json"
}

// main.ts del host
initFederation('federation.manifest.json')
  .then(() => import('./bootstrap'));

// rutas del host
{
  path: 'ventas',
  loadChildren: () =>
    loadRemoteModule({ remoteName: 'ventas', exposedModule: './Routes' })
      .then(m => m.routes)
}

Hasta aquí es lo que trae cualquier tutorial. Lo que sigue lo aprendí en producción.

Lo que rompe en producción (y cómo lo resolví)

1. Las rutas absolutas dentro de un módulo

Un módulo se desarrolla solo, en su propio puerto, donde /detalle/42 funciona perfecto. Embebido en el portal, ese módulo vive bajo /ventas, y /detalle/42 manda al usuario a una ruta que no existe.

La regla: dentro de un módulo, toda navegación es relativa. routerLink="detalle/42" y router.navigate(['detalle', id], { relativeTo: route }). Es una línea en la guía del equipo y una revisión en cada pull request, pero ahorra el bug más repetido de toda la plataforma.

2. El CORS de remoteEntry.json

El host pide el remoteEntry.json y los chunks de cada módulo desde otro dominio. Si el servidor del módulo no responde con Access-Control-Allow-Origin, el navegador bloquea la carga y el usuario ve una pantalla en blanco sin ningún error útil en la interfaz.

La regla: el CORS de los archivos estáticos del módulo es parte de la plantilla del proyecto, no algo que cada equipo descubre por su cuenta. Todo módulo nuevo nace con la configuración de su servidor web ya resuelta.

3. La caché que esconde la versión nueva

Despliegas, todo sale en verde, y el usuario sigue viendo la versión anterior. El culpable casi siempre es remoteEntry.json: es el único archivo cuyo nombre no cambia entre versiones, así que si el navegador o un CDN lo guarda, apunta a chunks viejos.

La regla: remoteEntry.json se sirve sin caché; los chunks, que llevan un hash en el nombre, se pueden cachear para siempre. Así cada despliegue llega al usuario en la siguiente carga, sin pedirle a nadie que borre su caché.

4. Un manifiesto por ambiente… y la vez que se mezclaron

Cada ambiente (pruebas y producción) tiene su propio manifiesto con las URLs de sus módulos. Una vez, una construcción manual horneó los valores por defecto y el portal de producción terminó cargando módulos de pruebas. El síntoma fue un error de CORS; la causa, un ambiente mezclado.

La regla: el pipeline valida, antes de construir, que todas las URLs correspondan al ambiente destino, y aborta si no. Desde entonces ese error ya no puede repetirse, porque el pipeline no lo deja pasar.

5. Una sola versión del Design System en todos los módulos

Los módulos comparten librerías: Angular, el router y el Design System propio que usan 17 aplicaciones. Native Federation las comparte como singleton, pero si un módulo trae otra versión del Design System, el portal muestra dos estilos distintos en la misma pantalla o, peor, componentes que se rompen al navegar entre módulos.

La regla: cada cambio en el Design System se publica como paquete versionado y se actualiza en todos los módulos a la vez, no solo en el que lo necesitaba. Y no basta con subir el rango en el package.json: si la imagen se construye con npm ci, manda el lockfile, así que hay que reinstalar y versionar el lock también.

6. Un módulo en el menú que todavía no existe

El portal arma su menú desde un catálogo de módulos. Registrar un módulo antes de que esté desplegado en todos los ambientes hace que el portal intente cargar un remoteEntry.json que no existe, y esa ruta queda rota.

La regla: cada entrada del catálogo tiene un indicador de disponibilidad. El módulo aparece en el menú con una página de "próximamente" hasta que su servicio existe donde corre el portal.

El login: uno solo, en el host

La decisión que más simplificó todo: solo el host autentica. El portal maneja el inicio de sesión con OIDC (Keycloak) y los módulos reciben el token ya resuelto. Ningún módulo define su propia autorización ni su propio login.

Eso tiene dos efectos. Para el usuario, un solo inicio de sesión para toda la plataforma. Para los equipos, un módulo nuevo no empieza escribiendo pantallas de login, sino pantallas de negocio. Los permisos viajan en el token y el menú se arma según ellos.

Si cada módulo resuelve la seguridad a su manera, tienes tantos modelos de seguridad como módulos. Centralizarla es la única forma de poder auditarla.

Qué haría distinto si empezara hoy

  • El Design System antes que el segundo módulo. Cada componente que un equipo construye "mientras tanto" es un componente que después hay que migrar.
  • Las barreras del pipeline desde el día uno. La validación de ambientes la agregué después de un incidente. Pudo estar desde el inicio.
  • Automatizar la actualización del Design System. Actualizar una librería en más de 15 repositorios a mano no escala: es exactamente el tipo de tarea que debe hacer un script, no una persona.
  • Una plantilla de módulo. CORS, rutas relativas, configuración de federación y pipeline listos al crear el repositorio. Cada regla de este artículo debería ser un archivo de la plantilla, no un párrafo de la guía.

Para cerrar

Los microfrontends funcionan cuando resuelven un problema de organización: equipos que necesitan avanzar sin esperarse. La parte técnica —federación, manifiestos, rutas— se resuelve en una semana. Lo difícil es todo lo demás: que cada regla quede escrita, que las que importan se conviertan en barreras automáticas y que cualquier persona del equipo pueda sostener la plataforma con lo que está documentado.

Si estás evaluando microfrontends para tu equipo o ya estás peleando con alguno de estos problemas, escríbeme por LinkedIn. Me interesa saber cómo lo están resolviendo otros.

microfrontendsAngularNative Federationarquitectura de softwareDesign SystemCI/CD