Skip to content

ZALANDO 2024-10-16

Read original ↗

Zalando — Building a Modular Portal with Webpack Module Federation

Summary

Zalando's Transport teams inside the Logistics department describe the architecture of an internal portal they built to serve finance teams, warehouses, and (planned) third-party partners, using Webpack Module Federation as the runtime composition primitive. This is explicitly not the public zalando.com Fashion Store stack (which runs on Interface Framework — see sources/2021-03-10-zalando-micro-frontends-from-fragments-to-renderers-part-1); it is a separate, inward-facing portal in a different part of the company with different constraints (mixed greenfield + legacy iframe-hosted apps, five collaborating teams, small team count per app).

At the time of writing the portal hosts 11 applications developed by 4 teams. The architectural thesis is simple: each team should develop and deploy an application independently without rebuilding the host portal or coordinating a site-wide release. Module Federation delivers this by letting the host load a remote bundle (the team's application) at runtime from a URL the team controls, with shared libraries (React, React-DOM) resolved to a single version across host + remotes.

The interesting content is how the seams are drawn around Module Federation:

  1. A centralised backend proxy inside the portal acts as the single authentication + authorisation gatekeeper for all backend calls from every micro-frontend. No remote talks directly to a microservice; everything flows through the portal proxy.
  2. Manifest-driven application loading. On portal load, the frontend calls the proxy's /applications endpoint, which returns only applications the authenticated user can access, each with an OPA-style permission scope object, an activePath, and a path to a manifest.json. The frontend fetches each manifest, reads bundlePath (pointing to RemoteEntry.js), and lazily loads the remote when the user navigates to its route.
  3. A prop-based shell API. The host passes a single prop to every remote application, serving as the interface for session info, global settings, cross-remote actions (portal-wide logout, navigation, error reporting), and data sharing. Remotes stay independent but gain a typed channel to the portal.
  4. A shared UI-kit distributed as an internal npm package. Design consistency across the 11 applications is kept by a reusable component library — one place to update the design language, propagated by bumping the npm dependency.

The post positions Module Federation explicitly against two prior approaches the portal needed to escape: monolithic static builds (release coupling across teams, coordination delays) and iframe-embedded applications (non-scalable, lacking modern integration primitives like shared React instance, cross-frame navigation, unified auth).

Key takeaways

  1. Runtime code sharing is the distinguishing primitive. Webpack Module Federation's value vs. a shared-npm-package approach is that remotes are shared at runtime without host rebuilds: each team can expose components / utilities from its bundle, and other bundles consume them without re-publishing or re-deploying a shared library (Source: section 2; concepts/runtime-code-sharing).
  2. Shared dependencies must be declared as singletons to avoid runtime breakage. With multiple remotes, the primary class of bug is two remotes each bundling their own React, producing runtime errors or "unexpected behaviour". Webpack's shared configuration marks common libraries (e.g. React, React-DOM, lodash) as singletons so only one version loads at runtime; teams additionally align versions during development (Source: section 5; concepts/shared-singleton-dependency).
  3. A centralised backend proxy is the auth-gatekeeper seam. Every request from any micro-frontend goes through the portal proxy, which authenticates the user once and forwards authorised requests to the appropriate microservice. Micro-frontends don't implement auth flows; microservices don't implement authorisation logic. The proxy is the one place permissions are checked (Source: section 3; patterns/centralised-backend-proxy-for-micro-frontends).
  4. Manifest-driven loading gives personalised + permission- scoped navigation. The /applications endpoint returns only the apps the user can access, each with an opaScope object describing read/write permissions per scope. The portal fetches each app's manifest.json (declaring menuItems with requiredPermissions and bundlePath) and lazy-loads the app's RemoteEntry.js on activePath navigation — the UI shape follows the user's permissions (Source: section 4; patterns/manifest-driven-micro-frontend-loading).
  5. Communication between remotes is mediated by a single prop passed from the host. Rather than a global event bus or cross-bundle imports, each federated module receives a prop from the portal. The prop exposes session info, global settings (theme, locale), portal- wide actions (logout, navigate, errorReport), and data handoffs. This keeps each remote independent while giving it a typed, governed interface to the shell (Source: section 5 "Communication Between Apps"; patterns/host-shell-prop-api-for-remotes).
  6. Lazy loading + code splitting keep the shell fast despite many remotes. Federated modules are loaded only when the user navigates to their section. Webpack configuration enables code splitting, caching, and compression; preloading critical assets for likely-next interactions narrows perceived load time. Without this, the cost model gets worse with every added application (Source: section 5 "Performance Considerations").
  7. A shared UI-kit as internal npm package keeps design consistent across the 11 apps. Buttons, modals, input fields, typography all live in one reusable library; central design updates propagate to all remotes on dependency bump. Without this, each team re-creates UI primitives and the portal drifts visually (Source: section 7; patterns/shared-ui-kit-as-internal-npm).
  8. Interfaces must be designed early, not discovered. Lessons learned: even with Module Federation's runtime flexibility, "defining clear interfaces and communication methods helped avoid complexity later on". The prop API and manifest.json contract are load-bearing: retrofitting either after 11 apps exist is expensive (Source: section 6; the authors flag this as the top lesson).
  9. Module Federation is an integration path away from iframes. The legacy context matters: some of the portal's applications were still being used as iframes inside other portals. Module Federation let them become first-class remotes in the new portal while still running in iframe form elsewhere, avoiding a forced rewrite (Source: section 1).

Operational numbers

From the post:

  • 5 teams collaborated on the portal project.
  • 4 teams currently develop the portal's applications.
  • 11 applications currently hosted by the portal.
  • Host + remotes share React and React-DOM as singletons via Webpack's shared config.
  • Telemetry, QPS, latency, bundle-size, and per-app traffic numbers are not disclosed in the post — consistent with its framing as an internal-tooling case study rather than a production-scale performance writeup.

Systems extracted

  • systems/webpack-module-federation — Webpack feature for runtime code sharing between bundles; the portal's core composition primitive. Canonical wiki instance anchored by this source.
  • systems/zalando-logistics-portal — the Zalando Transport teams' internal portal; first wiki instance of a Module-Federation-based Zalando frontend, distinct from Interface Framework.
  • systems/webpack — underlying bundler (exists implicitly via many wiki pages; this source adds a Module Federation Seen-in dimension).
  • systems/react — shared singleton dependency across host and remotes; already a rich wiki system; this source adds the shared: { react: { singleton: true } } deployment.

Concepts extracted

Patterns extracted

Context on the wiki

This is the second micro-frontend architecture on the Zalando axis. It does not replace or succeed the Interface Framework described in sources/2021-03-10-zalando-micro-frontends-from-fragments-to-renderers-part-1 (which serves zalando.com customer traffic); the two are separate platforms in separate parts of the company with different constraints:

Platform Audience Composition primitive Article
Mosaic (2015) Fashion Store customers Fragments (frontend+backend pair per slot) 2021-03-10
Interface Framework (2018) Fashion Store customers Entity → Renderer (one React component per Entity type) 2021-03-10, 2023-07-10
Logistics Portal (2024) Internal Logistics / Transport users Webpack Module Federation remotes This post

Interface Framework is a unified Rendering Engine that picks Renderers at request time for consumer traffic; the Logistics Portal is a host shell that loads independently- built Webpack remotes at runtime for internal users. They share the phrase "micro-frontend" and not much else structurally.

Caveats

  • Not a performance writeup. The post is an architecture case study; it does not disclose QPS, latency, bundle sizes, fleet size, or production incident data. There is no measured comparison against the pre-Module-Federation (monolith / iframe) approach beyond qualitative claims.
  • Not customer-facing scale. 11 applications / 4 teams / internal users is a substantially smaller scale than the Fashion Store Interface Framework's "hundreds of Renderers". The patterns here fit internal-portal altitude; the tradeoffs may not transfer to a customer-traffic setting.
  • OPA-style permission scopes are only sketched. The opaScope object in the /applications response hints at OPA (Open Policy Agent) integration, but the post does not describe the OPA deployment, policy authorship flow, or the backend proxy's evaluation model.
  • No discussion of version-skew dynamics between host + remote. Shared dependencies are aligned during development but the post doesn't cover what happens when the host upgrades React while a team's remote hasn't yet rebuilt — a real operational concern in Module Federation deployments.
  • No discussion of runtime auth between remotes and the prop API. The prop mediates communication, but whether the prop itself is tamper-resistant (e.g. a frozen object or an iframe-sandboxed context) isn't described.

Source

Last updated · 501 distilled / 1,218 read