A personal engineering project exploring production-grade Micro-Frontend (MFE) architecture. The shell app loads independent plugin apps at runtime via Module Federation v2 — each plugin is a fully standalone React application with its own Redux store, RTK Query API layer, auth system, and Tailwind v4 design system.
Why I built this: Micro-frontends are easy to talk about and hard to get right. I wanted hands-on proof that independent deployability, shared auth, per-app code splitting, and a custom DevTools companion could all work together cleanly in a real monorepo setup.
| Capability | Technical detail |
|---|---|
| Runtime module loading | Shell fetches a registry.json, then calls __federation_method_setRemote + import() — no script tag injection, no eager bundling of remotes |
| Route-level code splitting with proof | Each route within a plugin is a separate Vite chunk. DevTools includes a chunk-hash comparison tool that proves adding a new route creates exactly one new chunk without changing any existing hashes |
| Dual authentication pattern | Two independent auth layers: shell SSO (RBAC portal access) and per-app login (own JWT for the app's API). Apps can opt into shell-auth-only via the window.__nexus_auth bridge |
| Custom DevTools | Full web UI + REST API for scaffolding new plugin apps, adding routes, building, deploying, chunk comparison, and AI-assisted code generation — no terminal required |
| Shared design system | Signal & Flame token set: color scales, spacing, typography — consumed by all apps via a single CSS import |
| Cross-app event bus | NexusBus wraps CustomEvent for typed cross-MFE communication without a shared runtime dependency |
| E2E test suite | Playwright Python test suite covering login, portal RBAC, remote loading, CSS injection, dual-auth, shell-auth-only, and back-navigation |
| MF v2 migration | Project was migrated from @originjs/vite-plugin-federation (v1) to @module-federation/vite (v2) — the migration notes document every breaking change |
┌─────────────────────────────────────────────────────────────┐
│ Shell (port 3000) │
│ React + @repo/auth + sessionStorage routing │
│ Loads remotes dynamically via registry │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ RemoteLoader → import("http://localhost:300x/ │ │
│ │ remoteEntry.js") │ │
│ │ CSS auto-injects via vite-plugin-css-injected-by-js │ │
│ └───────────────────────────────────────────────────────┘ │
└──────────┬──────────────┬──────────────┬────────────────────┘
│ │ │
┌──────────▼───┐ ┌───────▼──────┐ ┌───▼────────────┐ ┌──────────────┐
│Analytics(3001│ │Workflow(3002)│ │Content (3003) │ │ Assets(3004) │
│ Dual Auth │ │ Dual Auth │ │ Dual Auth │ │ Shell Auth │
│ RTK: metrics │ │ RTK: checks │ │ RTK: articles │ │ RTK: files │
│ Redux store │ │ Redux store │ │ Redux store │ │ No auth slice│
└──────────────┘ └──────────────┘ └────────────────┘ └──────────────┘
│
┌──────────▼──────────────┐
│ DevTools (5001 / 5173) │
│ Scaffold · Build │
│ Compare chunk hashes │
│ Mock API (all data) │
│ AI code generation │
└─────────────────────────┘
| App | Port | Auth Pattern | Domain |
|---|---|---|---|
| Analytics | 3001 | Shell + own login | Metrics, alerts, incidents |
| Workflow | 3002 | Shell + own login | Automation rules, QC checks |
| Content | 3003 | Shell + own login | Articles, schedules, publishing |
| Assets | 3004 | Shell auth only (SSO demo) | File management, processing jobs |
Auth patterns explained:
- Shell + own login: User logs into the shell portal to see the app (role-gated), then logs into the app for its own API token. Two independent auth layers sharing zero state.
- Shell auth only: App reads the shell's token via
window.__nexus_authand has no login screen. Demonstrates forward-compatible SSO without rebuilding the remote.
| Package | Description |
|---|---|
@repo/auth |
AuthProvider, useAuth hook, RBAC, mock users, window.__nexus_auth bridge |
@repo/shared-ui |
shadcn/ui CSS variables + 19 Radix UI primitives |
@repo/ui |
48 pre-built components: Card, DataTable, Badge, Button, Dialog, Tabs, etc. |
@repo/tailwind-config |
Signal & Flame design system — color scales, spacing tokens, typography |
@repo/typescript-config |
Shared TypeScript configs: base.json, vite.json |
Signal Blue (#1428A0) primary brand — Shell, Analytics
Flame Orange (#F4511E) urgency, assets, destructive actions
Purple (#546BE8) Workflow accent
Dark Navy (#0D1B70) Content accent, sidebar backgrounds
DM Sans → body text
Sora → headings, brand mark
DM Mono → metrics, code, telemetry
Tokens live in packages/tailwind-config/shared-styles.css and are imported by every app through src/index.css.
@module-federation/vite works over remoteEntry.js build artifacts — a remote in Vite dev mode serves a different module protocol that the shell can't consume. The pnpm dev script builds all remotes first, then runs them in vite preview, then starts the shell in vite dev (HMR on shell only).
Each route inside a plugin is a lazy-loaded chunk. The DevTools chunk-hash comparison tool hashes each .js output file (stripping Vite's 8-char content hash to prevent cascading hash changes), then diffs builds. Adding a route creates exactly one new hash — no existing hashes change. This is production-confidence tooling: it proves you can ship a new route to one app without invalidating any CDN cache entries elsewhere.
Remotes bundle their own copy of @repo/auth (no shared-singleton risk). The shell's AuthProvider writes window.__nexus_auth = { user, token } on every auth state change. Remote useAuth() falls back to reading this bridge when it detects no local AuthContext. Zero runtime coupling, zero version conflict.
Remote CSS is injected via vite-plugin-css-injected-by-js — it appends a <style> tag at runtime. No <link> tag in the shell's HTML. This means remotes are fully self-contained: deploy a new remote and its styles appear automatically.
# Install
pnpm install
# Start everything (recommended)
pnpm dev # builds remotes → preview remotes → shell dev
# DevTools (separate terminal)
pnpm devtools # API server (5001) + DevTools UI (5173)Default mock users:
| User | Role | Accessible apps |
|---|---|---|
| Nishant | admin | All 4 apps |
| Bob Ops | ops | Analytics, Workflow |
| Carol Editor | editor | Content, Assets |
| Dave Viewer | viewer | All apps (read-only) |
Password for all users: password123
Open the portal at http://localhost:3000 and DevTools at http://localhost:5173.
DevTools is a companion web app + Express REST API for development-time productivity:
- Apps panel — live status of all registered plugin apps
- Scaffold — generate a new plugin app from a form (ID, label, port, color, initial routes) — creates
apps/<id>/with all config files, runs install + build, registers in registry - New Route — add a new page to an existing app without touching the terminal
- Build & Compare — build any app, snapshot chunk hashes, diff two snapshots to prove code isolation
- Deploy — copy built artifacts to
deploys/<app>/for local static serving - Mock API — Express server serving realistic fixture data for all apps (metrics, files, articles, automation checks)
- Code Studio — AI-powered generation of login, form, detail, and CRUD pages
- Dev Login — switch between mock users instantly to test RBAC without re-logging in
pnpm dev # full dev environment (recommended)
pnpm dev:hmr # true HMR on all apps (disable Console Ninja first)
pnpm build:mfe # build all remote plugins
pnpm --filter analytics build # build one plugin
pnpm devtools # DevTools API + UI
pnpm snapshot # snapshot chunk hashes for current build
pnpm compare # diff chunks vs last snapshot
# E2E tests
pip install playwright && playwright install chromium
python tests/e2e/portal.test.py| Service | Port |
|---|---|
| Shell | 3000 |
| Analytics remote | 3001 |
| Workflow remote | 3002 |
| Content remote | 3003 |
| Assets remote | 3004 |
| DevTools API | 5001 |
| DevTools UI | 5173 |
| Document | Contents |
|---|---|
| docs/TAILWIND-ARCHITECTURE.md | Tailwind v4 pipeline, cascade layers, CSS injection, debugging guide |
| docs/RTK-MFE-GUIDE.md | Redux Toolkit + RTK Query across MFEs, dual auth tokens, store patterns |
| docs/FEDERATION-GUIDE.md | Module Federation v2 internals, build output, dev vs preview, error reference |
| docs/INTEGRATION-GUIDE.md | Production deployment guide, SSO, CDN config, cross-app communication |
| docs/MFE-ARCHITECTURE.md | Architecture reference with component diagrams |
- Bundler — Vite 6 +
@module-federation/vitev2 - Framework — React 19
- State — Redux Toolkit + RTK Query (per app)
- Styling — Tailwind CSS v4 + shadcn/ui (Radix primitives)
- Monorepo — pnpm workspaces + Turborepo
- Language — TypeScript (strict)
- Testing — Playwright (Python) for E2E, Vitest for unit
- DevTools — Express 4 + React 19 + Vite
Migrated from @originjs/vite-plugin-federation (v1) to @module-federation/vite (v2):
| Change | Detail |
|---|---|
| Plugin import | import { federation } (named export, not default) |
remoteEntry.js location |
Now at dist/remoteEntry.js (was dist/assets/) |
| Loading mechanism | Native import() in RemoteLoader (was script tag injection) |
| CSS | Requires vite-plugin-css-injected-by-js for CSS injection in remotes |
| Dev mode | pnpm dev:hmr — requires Console Ninja disabled |
| Runtime | Removed @module-federation/runtime from shell; federation runtime is implicit |