Skip to content

Repository files navigation

Nexus — Micro-Frontend Platform

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.


What makes this interesting to a recruiter or senior engineer

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

Architecture

┌─────────────────────────────────────────────────────────────┐
│  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     │
└─────────────────────────┘

Plugin Apps

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_auth and has no login screen. Demonstrates forward-compatible SSO without rebuilding the remote.

Packages

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

Design System — Signal & Flame

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.


Key Technical Decisions

Why remotes must be built before the shell starts

@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).

Route-level chunk isolation

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.

Window bridge for SSO

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.

CSS injection

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.


Quick Start

# 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 Capabilities

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

Commands Reference

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

Port Reference

Service Port
Shell 3000
Analytics remote 3001
Workflow remote 3002
Content remote 3003
Assets remote 3004
DevTools API 5001
DevTools UI 5173

Documentation

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

Tech Stack

  • Bundler — Vite 6 + @module-federation/vite v2
  • 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

MF v2 Migration Notes

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

About

Micro-frontend architecture with module federation and shared design system.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages