Micro-frontends
The storefront and admin UI live in frontend/web: an Nx 23.2.1 monorepo of React 18 applications composed at runtime with Webpack Module Federation. The original Angular single-page app is kept in frontend/legacy-angular for reference and is no longer developed (see frontend/README.md).
Applications
Section titled “Applications”| App | Dev port | Role | Federation |
|---|---|---|---|
host |
4200 | Shell: layout, routing, MSAL sign-in, loads remotes | consumer; no static remotes in production |
store |
4201 | Product browsing and search | exposes ./ConsoleMicroApp |
checkout |
4202 | Cart and checkout | exposes ./ConsoleMicroApp |
account |
4203 | Profile and order history | exposes ./ConsoleMicroApp |
admin |
4204 | Product management, analytics, activity feed | exposes ./ConsoleMicroApp |
Every remote exposes the same module name, ConsoleMicroApp, which points at its src/remote-entry.ts (for example frontend/web/store/module-federation.config.ts). A uniform contract means the host needs no per-remote code.
Runtime composition
Section titled “Runtime composition”The host does not declare remotes at build time. frontend/web/host/webpack.config.prod.ts sets remotes: []. Remotes are resolved at runtime from a registry:
frontend/web/host/src/config/microFrontendRegistry.tslists each app’sname,remoteName,exposedModuleandbasePath, with URLs per environment. In dev, each remote ishttp://localhost:420x. In staging and production, remotes live under the same origin at/remotes/<name>.frontend/web/host/src/microFe/MicroFrontendApp.tsxcallsinit()andloadRemote()from@module-federation/runtimewith${url}/remoteEntry.js, then calls the remote’sinject(containerId, { config }), and callsunmounton cleanup.
sequenceDiagram
autonumber
actor U as User
participant H as Host shell (4200)
participant Reg as microFrontendRegistry
participant MF as MF runtime
participant R as Remote (store) remoteEntry.js
U->>H: navigate to /store/products?brand=x
H->>Reg: look up "store"
Reg-->>H: remoteName, exposedModule, url for env
H->>MF: init({ remotes: [{ name, entry: url/remoteEntry.js }] })
H->>MF: loadRemote("store/ConsoleMicroApp")
MF->>R: fetch remoteEntry.js and chunks
R-->>H: { inject, unmount }
H->>R: inject(containerId, { appContext: basePath, initialSearchParams, onSearchChange })
Note over H,R: Remote renders its own React root (createRoot).<br/>URL search params sync back through onSearchChange.
U->>H: navigate away
H->>R: unmount(containerId)
The injector contract
Section titled “The injector contract”frontend/web/packages/app-injector/src/createAppInjector.tsx wraps any React component into { inject, unmount }. It uses React 18’s createRoot on the container element and keeps the root so it can unmount it later. Each remote’s injector.ts is three lines around it. The design lets remotes own their own React tree, router (TanStack Router in the remotes) and state, so the host only needs a DOM element and a config object.
Shared packages
Section titled “Shared packages”| Package | Purpose |
|---|---|
frontend/web/packages/app-injector |
createAppInjector and createEnhancedAppInjector: the mount/unmount contract |
frontend/web/packages/auth-provider |
EcommerceAuthProvider, which switches between an MSAL (Azure AD B2C) provider and an internal provider, plus the useAuth hook and an error boundary |
frontend/web/packages/shared-layout |
Navbar, Footer, Layout, CartPreview, LanguageSwitcher |
The packages are built with tsup (npm run build:packages) and copied into node_modules/@ecommerce-platform/* during the Amplify build, so every app uses the local build.
React 18.3, TanStack Router and Query, Zustand, Axios, Ant Design 5, MSAL (@azure/msal-browser, @azure/msal-react), Nx 23.2.1 with @nx/react and @nx/module-federation, and webpack 5.104.1. The configuration only passes NX_* environment variables to the bundle, through DefinePlugin. NX_API_BASE_URL points at the Ocelot gateway and defaults to http://localhost:8010 (frontend/web/host/src/config/env.config.ts).
Build and deploy
Section titled “Build and deploy”frontend/web/amplify.yml builds everything on AWS Amplify: npm ci, build:packages, then nx run-many --target=build --configuration=production --all. It then assembles a single artifact with the host at the root and each remote copied to dist/deploy/remotes/<name>. frontend/web/host/src/_redirects serves /remotes/* as-is and falls back to index.html for client-side routes. Serving remotes from the same origin removes CORS from remote loading and lets one deploy ship all apps. The trade-off is that the remotes are no longer deployed independently in this setup.
In CI, the frontend-quality job runs nx affected lint, test and build (see CI/CD). The micro-frontend Playwright suites are described in Testing.