This repository is a runnable companion to post/FINAL.md. It demonstrates webpack 5 Module Federation with one shell and three independently runnable remotes.
This is production-foundation ready, not fully production-ready. The repo now has the fundamentals a production microfrontend setup needs: shared contracts, environment-driven remote URLs, CSS Modules for stronger isolation, and small shared UI primitives.
| Workspace | Default URL | Responsibility |
|---|---|---|
apps/shell |
http://localhost:3000 |
Host shell, remote composition, shared shell state, remote error boundaries |
apps/dashboard |
http://localhost:3001 |
Dashboard remote, URL-state pattern |
apps/profile |
http://localhost:3002 |
Profile remote, shared shell-store pattern |
apps/cart |
http://localhost:3003 |
Cart remote, custom DOM event pattern |
packages/contracts |
n/a | Cross-app event, store, URL, and remote-config contracts |
packages/ui |
n/a | Stateless shared UI primitives |
nvm use
npm install
npm run devOpen:
http://localhost:3000
If port 3000 is already occupied, run the shell on another port:
PORT=3004 npm run devThen open:
http://localhost:3004
npm test
npm run buildRuntime smoke checks:
curl -fsSI http://localhost:3001/remoteEntry.js
curl -fsSI http://localhost:3002/remoteEntry.js
curl -fsSI http://localhost:3003/remoteEntry.jsCopy .env.example to .env to override local defaults:
cp .env.example .envThe shell reads remote URLs from:
DASHBOARD_REMOTE_URLPROFILE_REMOTE_URLCART_REMOTE_URL
Example:
DASHBOARD_REMOTE_URL=https://cdn.example.com/dashboard/remoteEntry.js
PROFILE_REMOTE_URL=https://cdn.example.com/profile/remoteEntry.js
CART_REMOTE_URL=https://cdn.example.com/cart/remoteEntry.js- The shell consumes
dashboard/App,profile/App, andcart/Appat runtime. - Every app shares
react,react-dom,@final-mfe/contracts, and@final-mfe/uias Module Federation singletons. - Remotes may import
@final-mfe/contractsand@final-mfe/ui. - Remotes should not import shell internals or each other.
- CSS is isolated with CSS Modules for component styles. Keep global CSS limited to body/reset-level rules.
If the browser shows:
Cannot read properties of undefined (reading 'app')
check webpack.shared.cjs. CSS Modules must disable named-only exports so import styles from "./File.module.css" returns a default class map:
modules: {
namedExport: false,
localIdentName: "[name]__[local]__[hash:base64:5]"
}After changing webpack config, restart the dev server. Hot reload will not reliably apply loader option changes.
Before using this in production, add:
- TypeScript or runtime schema validation for contracts
- Real deployment environments and CDN cache policies
- Remote health checks and observability
- Rollback or canary strategy per remote
- CSP, stricter CORS, and dependency audit cleanup
- E2E tests against the composed shell and remote entries