Skip to main content

TypeScript Modern.js MFE Shell

A sophisticated micro frontend shell application built with Modern.js framework, designed to host and orchestrate multiple micro frontend applications using Module Federation for seamless integration and dynamic loading.

Overview

This archetype generates a complete shell application using Modern.js with TypeScript, designed to dynamically load and integrate multiple micro frontend applications. It provides the foundational infrastructure for a distributed frontend architecture, handling routing, authentication, shared services, and cross-application communication.

Technology Stack

  • Modern.js: React-based framework with Rspack bundling
  • TypeScript 5+: Strong typing and modern JavaScript features
  • Module Federation: Webpack's micro frontend solution for dynamic loading
  • Rspack: Ultra-fast Rust-based bundler
  • React 18: Modern React with concurrent features
  • React Router: Client-side routing with micro frontend integration
  • Tailwind CSS: Utility-first CSS framework
  • Zustand: Lightweight state management
  • Docker: Containerization for deployment
  • Kubernetes: Container orchestration support

Key Features

Shell Architecture

  • Micro Frontend Orchestration: Dynamic loading and integration of MFE apps
  • Routing Management: Centralized routing with delegation to micro frontends
  • Authentication: Shared authentication across all micro frontends
  • Theme Management: Consistent theming and design system
  • Error Boundaries: Graceful handling of micro frontend failures

Module Federation Host

  • Dynamic Imports: Runtime loading of federated modules
  • Version Management: Handling multiple versions of micro frontends
  • Fallback Strategies: Graceful degradation when modules fail to load
  • Health Monitoring: Monitoring federated module availability
  • Performance Optimization: Lazy loading and code splitting

Production Features

  • Docker Optimization: Multi-stage builds for production
  • Kubernetes Ready: Helm charts and deployment manifests
  • CDN Integration: Optimized static asset delivery
  • Monitoring: Application performance and health monitoring
  • Security: CORS configuration and content security policies

Project Structure

modernjs-mfe-shell/
├── src/
│ ├── pages/ # Modern.js file-based routing
│ │ ├── layout.tsx # Root layout
│ │ ├── page.tsx # Home page
│ │ ├── dashboard/ # Dashboard route
│ │ │ └── page.tsx
│ │ ├── customers/ # Customer MFE route
│ │ │ └── page.tsx # Customer MFE container
│ │ ├── orders/ # Orders MFE route
│ │ │ └── page.tsx # Orders MFE container
│ │ └── settings/ # Settings route
│ │ └── page.tsx
│ ├── components/ # Shell components
│ │ ├── layout/ # Layout components
│ │ │ ├── Header.tsx # Main navigation header
│ │ │ ├── Sidebar.tsx # Application sidebar
│ │ │ ├── Footer.tsx # Application footer
│ │ │ └── MainLayout.tsx # Main layout wrapper
│ │ ├── mfe/ # MFE-specific components
│ │ │ ├── MFEContainer.tsx # Generic MFE container
│ │ │ ├── MFELoader.tsx # MFE loading component
│ │ │ ├── MFEErrorBoundary.tsx # MFE error boundary
│ │ │ └── MFERegistry.tsx # MFE registry management
│ │ ├── auth/ # Authentication components
│ │ │ ├── LoginForm.tsx
│ │ │ ├── AuthProvider.tsx
│ │ │ └── ProtectedRoute.tsx
│ │ ├── ui/ # Shared UI components
│ │ │ ├── Button.tsx
│ │ │ ├── Modal.tsx
│ │ │ ├── Spinner.tsx
│ │ │ └── Toast.tsx
│ │ └── navigation/ # Navigation components
│ │ ├── MainNavigation.tsx
│ │ ├── Breadcrumbs.tsx
│ │ └── UserMenu.tsx
│ ├── routing/ # Routing configuration
│ │ ├── router.tsx # Main router setup
│ │ ├── routes.ts # Route definitions
│ │ ├── guards.ts # Route guards
│ │ └── mfeRoutes.ts # MFE route mappings
│ ├── services/ # Shared services
│ │ ├── api/ # API clients
│ │ │ ├── baseApi.ts # Base API configuration
│ │ │ ├── authApi.ts # Authentication API
│ │ │ └── userApi.ts # User management API
│ │ ├── mfe/ # MFE management services
│ │ │ ├── mfeRegistry.ts # MFE registration service
│ │ │ ├── mfeLoader.ts # MFE loading service
│ │ │ └── mfeHealthCheck.ts # MFE health monitoring
│ │ ├── auth/ # Authentication services
│ │ │ ├── authService.ts # Auth service
│ │ │ └── tokenService.ts # Token management
│ │ └── theme/ # Theme services
│ │ ├── themeService.ts # Theme management
│ │ └── cssVariables.ts # CSS variable management
│ ├── store/ # Global state management
│ │ ├── authStore.ts # Authentication state
│ │ ├── mfeStore.ts # MFE management state
│ │ ├── themeStore.ts # Theme state
│ │ ├── userStore.ts # User state
│ │ └── index.ts # Store configuration
│ ├── hooks/ # Custom React hooks
│ │ ├── useMFE.ts # MFE management hook
│ │ ├── useAuth.ts # Authentication hook
│ │ ├── useTheme.ts # Theme management hook
│ │ └── useNavigation.ts # Navigation hook
│ ├── utils/ # Utility functions
│ │ ├── mfe/ # MFE utilities
│ │ │ ├── moduleLoader.ts # Module loading utilities
│ │ │ ├── errorHandler.ts # Error handling utilities
│ │ │ └── urlUtils.ts # URL manipulation utilities
│ │ ├── auth/ # Auth utilities
│ │ │ ├── permissions.ts # Permission checking
│ │ │ └── validation.ts # Auth validation
│ │ └── common/ # Common utilities
│ │ ├── logger.ts # Logging utilities
│ │ ├── constants.ts # Application constants
│ │ └── helpers.ts # Helper functions
│ ├── types/ # TypeScript type definitions
│ │ ├── mfe.ts # MFE-related types
│ │ ├── auth.ts # Authentication types
│ │ ├── api.ts # API response types
│ │ ├── navigation.ts # Navigation types
│ │ └── global.ts # Global types
│ ├── styles/ # Global styles
│ │ ├── globals.css # Global CSS
│ │ ├── components.css # Component styles
│ │ ├── themes/ # Theme definitions
│ │ │ ├── light.css
│ │ │ ├── dark.css
│ │ │ └── variables.css
│ │ └── mfe.css # MFE-specific styles
│ ├── config/ # Configuration files
│ │ ├── mfe.config.ts # MFE configuration
│ │ ├── api.config.ts # API configuration
│ │ └── auth.config.ts # Auth configuration
│ └── App.tsx # Root application component
├── public/ # Static assets
│ ├── favicon.ico
│ ├── manifest.json
│ └── mf-manifest.json # Module Federation manifest
├── tests/ # Test files
│ ├── unit/ # Unit tests
│ ├── integration/ # Integration tests
│ └── e2e/ # End-to-end tests
├── docker/ # Docker configurations
│ ├── Dockerfile # Production Dockerfile
│ ├── Dockerfile.dev # Development Dockerfile
│ └── nginx.conf # Nginx configuration
├── k8s/ # Kubernetes manifests
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── ingress.yaml
│ └── configmap.yaml
├── scripts/ # Build and deployment scripts
│ ├── build.sh
│ ├── deploy.sh
│ └── start-dev.sh
├── modern.config.ts # Modern.js configuration
├── module-federation.config.ts # Module Federation configuration
├── package.json
├── tsconfig.json
├── tailwind.config.js
├── biome.json # Code quality configuration
└── README.md

Module Federation Configuration

Shell Federation Configuration

// module-federation.config.ts
import { ModuleFederationPlugin } from '@module-federation/enhanced'

export default {
name: 'shell',
remotes: {
// Micro frontend applications
customerApp: 'customer_app@http://localhost:3001/mf-manifest.json',
orderApp: 'order_app@http://localhost:3002/mf-manifest.json',
inventoryApp: 'inventory_app@http://localhost:3003/mf-manifest.json',
analyticsApp: 'analytics_app@http://localhost:3004/mf-manifest.json'
},
shared: {
react: {
singleton: true,
requiredVersion: '^18.0.0',
eager: true
},
'react-dom': {
singleton: true,
requiredVersion: '^18.0.0',
eager: true
},
'@modern-js/runtime': {
singleton: true,
eager: true
},
'react-router-dom': {
singleton: true,
eager: true
}
},
runtimePlugins: ['./src/federation-runtime']
}

MFE Registry Service

// src/services/mfe/mfeRegistry.ts
import type { MFEConfig, MFEInstance, MFEStatus } from '@/types/mfe'

export class MFERegistry {
private mfeConfigs: Map<string, MFEConfig> = new Map()
private mfeInstances: Map<string, MFEInstance> = new Map()
private healthCheckIntervals: Map<string, NodeJS.Timeout> = new Map()

constructor() {
this.initializeDefaultMFEs()
}

private initializeDefaultMFEs() {
const defaultMFEs: MFEConfig[] = [
{
name: 'customerApp',
displayName: 'Customer Management',
remoteEntry: 'http://localhost:3001/mf-manifest.json',
exposedModule: './CustomerModule',
basePath: '/customers',
icon: 'Users',
permissions: ['customer.read', 'customer.write'],
healthCheckUrl: 'http://localhost:3001/health',
version: '1.0.0'
},
{
name: 'orderApp',
displayName: 'Order Management',
remoteEntry: 'http://localhost:3002/mf-manifest.json',
exposedModule: './OrderModule',
basePath: '/orders',
icon: 'ShoppingCart',
permissions: ['order.read', 'order.write'],
healthCheckUrl: 'http://localhost:3002/health',
version: '1.0.0'
},
{
name: 'inventoryApp',
displayName: 'Inventory Management',
remoteEntry: 'http://localhost:3003/mf-manifest.json',
exposedModule: './InventoryModule',
basePath: '/inventory',
icon: 'Package',
permissions: ['inventory.read', 'inventory.write'],
healthCheckUrl: 'http://localhost:3003/health',
version: '1.0.0'
}
]

defaultMFEs.forEach(config => {
this.registerMFE(config)
})
}

registerMFE(config: MFEConfig): void {
this.mfeConfigs.set(config.name, config)

const instance: MFEInstance = {
config,
status: 'unknown',
lastHealthCheck: null,
loadError: null,
module: null
}

this.mfeInstances.set(config.name, instance)
this.startHealthCheck(config.name)
}

getMFE(name: string): MFEInstance | undefined {
return this.mfeInstances.get(name)
}

getAllMFEs(): MFEInstance[] {
return Array.from(this.mfeInstances.values())
}

getAvailableMFEs(): MFEInstance[] {
return this.getAllMFEs().filter(mfe => mfe.status === 'healthy')
}

async loadMFE(name: string): Promise<any> {
const instance = this.mfeInstances.get(name)
if (!instance) {
throw new Error(`MFE ${name} not registered`)
}

if (instance.module) {
return instance.module
}

try {
instance.status = 'loading'

// Dynamic import of federated module
const module = await import(/* webpackIgnore: true */ instance.config.remoteEntry)
.then(() => (window as any)[name])
.then(container => container.get(instance.config.exposedModule))
.then(factory => factory())

instance.module = module
instance.status = 'loaded'
instance.loadError = null

return module
} catch (error) {
instance.status = 'error'
instance.loadError = error instanceof Error ? error.message : 'Unknown error'
throw error
}
}

private async startHealthCheck(name: string): Promise<void> {
const instance = this.mfeInstances.get(name)
if (!instance || !instance.config.healthCheckUrl) {
return
}

const performHealthCheck = async () => {
try {
const response = await fetch(instance.config.healthCheckUrl!, {
method: 'GET',
mode: 'cors',
credentials: 'omit'
})

if (response.ok) {
instance.status = 'healthy'
instance.lastHealthCheck = new Date()
} else {
instance.status = 'unhealthy'
}
} catch (error) {
instance.status = 'unreachable'
console.warn(`Health check failed for MFE ${name}:`, error)
}
}

// Initial health check
await performHealthCheck()

// Set up periodic health checks
const interval = setInterval(performHealthCheck, 30000) // 30 seconds
this.healthCheckIntervals.set(name, interval)
}

unregisterMFE(name: string): void {
const interval = this.healthCheckIntervals.get(name)
if (interval) {
clearInterval(interval)
this.healthCheckIntervals.delete(name)
}

this.mfeConfigs.delete(name)
this.mfeInstances.delete(name)
}

dispose(): void {
this.healthCheckIntervals.forEach(interval => clearInterval(interval))
this.healthCheckIntervals.clear()
this.mfeConfigs.clear()
this.mfeInstances.clear()
}
}

export const mfeRegistry = new MFERegistry()

MFE Container Component

// src/components/mfe/MFEContainer.tsx
import React, { Suspense, useEffect, useState } from 'react'
import { MFELoader } from './MFELoader'
import { MFEErrorBoundary } from './MFEErrorBoundary'
import { useMFE } from '@/hooks/useMFE'
import { useAuth } from '@/hooks/useAuth'
import { useTheme } from '@/hooks/useTheme'
import type { MFEContainerProps } from '@/types/mfe'

export const MFEContainer: React.FC<MFEContainerProps> = ({
mfeName,
fallback,
onError,
className,
...props
}) => {
const { loadMFE, getMFEStatus } = useMFE()
const { user, token } = useAuth()
const { theme } = useTheme()
const [MFEComponent, setMFEComponent] = useState<React.ComponentType | null>(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)

useEffect(() => {
let mounted = true

const loadComponent = async () => {
try {
setLoading(true)
setError(null)

// Check if MFE is available
const status = getMFEStatus(mfeName)
if (status === 'unreachable' || status === 'unhealthy') {
throw new Error(`MFE ${mfeName} is not available`)
}

// Load the MFE module
const module = await loadMFE(mfeName)

if (mounted) {
setMFEComponent(() => module.default || module)
}
} catch (err) {
if (mounted) {
const errorMessage = err instanceof Error ? err.message : 'Failed to load micro frontend'
setError(errorMessage)
onError?.(errorMessage)
}
} finally {
if (mounted) {
setLoading(false)
}
}
}

loadComponent()

return () => {
mounted = false
}
}, [mfeName, loadMFE, getMFEStatus, onError])

if (loading) {
return <MFELoader mfeName={mfeName} />
}

if (error || !MFEComponent) {
return (
<div className="mfe-error-container p-8 text-center">
<div className="max-w-md mx-auto">
<div className="text-red-500 mb-4">
<svg className="w-16 h-16 mx-auto" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.958-.833-2.728 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z" />
</svg>
</div>
<h3 className="text-lg font-semibold text-gray-900 mb-2">
Failed to Load {mfeName}
</h3>
<p className="text-gray-600 mb-4">
{error || 'The micro frontend could not be loaded.'}
</p>
{fallback && (
<div className="mt-4">
{fallback}
</div>
)}
</div>
</div>
)
}

return (
<MFEErrorBoundary mfeName={mfeName} fallback={fallback}>
<Suspense fallback={<MFELoader mfeName={mfeName} />}>
<div className={`mfe-container ${className || ''}`} data-mfe={mfeName}>
<MFEComponent
// Pass shared context to MFE
user={user}
token={token}
theme={theme}
{...props}
/>
</div>
</Suspense>
</MFEErrorBoundary>
)
}

Shell Layout

// src/components/layout/MainLayout.tsx
import React from 'react'
import { Outlet } from 'react-router-dom'
import { Header } from './Header'
import { Sidebar } from './Sidebar'
import { Footer } from './Footer'
import { useAuth } from '@/hooks/useAuth'
import { useMFE } from '@/hooks/useMFE'

export const MainLayout: React.FC = () => {
const { isAuthenticated } = useAuth()
const { availableMFEs } = useMFE()

if (!isAuthenticated) {
return <Outlet /> // Login page or other public routes
}

return (
<div className="min-h-screen bg-gray-50 flex flex-col">
<Header />

<div className="flex flex-1">
<Sidebar mfeApps={availableMFEs} />

<main className="flex-1 overflow-hidden">
<div className="h-full overflow-auto">
<Outlet />
</div>
</main>
</div>

<Footer />
</div>
)
}
// src/components/navigation/MainNavigation.tsx
import React from 'react'
import { Link, useLocation } from 'react-router-dom'
import { useMFE } from '@/hooks/useMFE'
import { useAuth } from '@/hooks/useAuth'
import type { MFEInstance } from '@/types/mfe'

export const MainNavigation: React.FC = () => {
const location = useLocation()
const { availableMFEs } = useMFE()
const { user, hasPermission } = useAuth()

const getNavigationItems = () => {
const items = [
{
name: 'Dashboard',
path: '/dashboard',
icon: 'Home',
permissions: []
}
]

// Add MFE navigation items
availableMFEs.forEach((mfe: MFEInstance) => {
if (mfe.status === 'healthy' && hasPermission(mfe.config.permissions)) {
items.push({
name: mfe.config.displayName,
path: mfe.config.basePath,
icon: mfe.config.icon,
permissions: mfe.config.permissions
})
}
})

return items
}

const navigationItems = getNavigationItems()

return (
<nav className="space-y-1">
{navigationItems.map((item) => {
const isActive = location.pathname.startsWith(item.path)

return (
<Link
key={item.path}
to={item.path}
className={`
group flex items-center px-2 py-2 text-sm font-medium rounded-md
${isActive
? 'bg-blue-100 text-blue-900'
: 'text-gray-600 hover:bg-gray-50 hover:text-gray-900'
}
`}
>
<span className="mr-3 h-6 w-6" aria-hidden="true">
{/* Icon component based on item.icon */}
</span>
{item.name}
</Link>
)
})}
</nav>
)
}

Routing Configuration

Main Router Setup

// src/routing/router.tsx
import React from 'react'
import { createBrowserRouter, RouterProvider } from 'react-router-dom'
import { MainLayout } from '@/components/layout/MainLayout'
import { ProtectedRoute } from '@/components/auth/ProtectedRoute'
import { MFEContainer } from '@/components/mfe/MFEContainer'
import { HomePage } from '@/pages/HomePage'
import { DashboardPage } from '@/pages/DashboardPage'
import { LoginPage } from '@/pages/auth/LoginPage'
import { NotFoundPage } from '@/pages/NotFoundPage'

const router = createBrowserRouter([
{
path: '/',
element: <MainLayout />,
children: [
// Public routes
{
path: '/login',
element: <LoginPage />
},

// Protected routes
{
path: '/',
element: <ProtectedRoute />,
children: [
{
index: true,
element: <HomePage />
},
{
path: 'dashboard',
element: <DashboardPage />
},

// MFE routes
{
path: 'customers/*',
element: (
<MFEContainer
mfeName="customerApp"
fallback={<div>Customer app is currently unavailable</div>}
/>
)
},
{
path: 'orders/*',
element: (
<MFEContainer
mfeName="orderApp"
fallback={<div>Order app is currently unavailable</div>}
/>
)
},
{
path: 'inventory/*',
element: (
<MFEContainer
mfeName="inventoryApp"
fallback={<div>Inventory app is currently unavailable</div>}
/>
)
}
]
},

// 404 route
{
path: '*',
element: <NotFoundPage />
}
]
}
])

export const AppRouter: React.FC = () => {
return <RouterProvider router={router} />
}

State Management

MFE Store

// src/store/mfeStore.ts
import { create } from 'zustand'
import { subscribeWithSelector } from 'zustand/middleware'
import { mfeRegistry } from '@/services/mfe/mfeRegistry'
import type { MFEInstance, MFEStatus } from '@/types/mfe'

interface MFEStoreState {
mfeInstances: MFEInstance[]
loadingMFEs: Set<string>
errorMessages: Map<string, string>

// Actions
refreshMFEs: () => void
loadMFE: (name: string) => Promise<void>
getMFEStatus: (name: string) => MFEStatus
clearMFEError: (name: string) => void
}

export const useMFEStore = create<MFEStoreState>()(
subscribeWithSelector((set, get) => ({
mfeInstances: [],
loadingMFEs: new Set(),
errorMessages: new Map(),

refreshMFEs: () => {
const instances = mfeRegistry.getAllMFEs()
set({ mfeInstances: instances })
},

loadMFE: async (name: string) => {
const { loadingMFEs, errorMessages } = get()

if (loadingMFEs.has(name)) {
return // Already loading
}

try {
set({
loadingMFEs: new Set([...loadingMFEs, name])
})

await mfeRegistry.loadMFE(name)

// Clear any previous errors
errorMessages.delete(name)
set({
errorMessages: new Map(errorMessages)
})
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to load MFE'
errorMessages.set(name, message)
set({
errorMessages: new Map(errorMessages)
})
throw error
} finally {
loadingMFEs.delete(name)
set({
loadingMFEs: new Set(loadingMFEs)
})

// Refresh instances to get updated status
get().refreshMFEs()
}
},

getMFEStatus: (name: string) => {
const { mfeInstances } = get()
const instance = mfeInstances.find(mfe => mfe.config.name === name)
return instance?.status || 'unknown'
},

clearMFEError: (name: string) => {
const { errorMessages } = get()
errorMessages.delete(name)
set({
errorMessages: new Map(errorMessages)
})
}
}))
)

// Initialize MFE monitoring
export const initializeMFEStore = () => {
const store = useMFEStore.getState()

// Initial load
store.refreshMFEs()

// Set up periodic refresh
setInterval(() => {
store.refreshMFEs()
}, 10000) // Every 10 seconds
}

Testing Strategy

MFE Integration Testing

// tests/integration/mfe.test.tsx
import { render, screen, waitFor } from '@testing-library/react'
import { MFEContainer } from '@/components/mfe/MFEContainer'
import { mfeRegistry } from '@/services/mfe/mfeRegistry'

// Mock the MFE registry
jest.mock('@/services/mfe/mfeRegistry')

const mockMFERegistry = mfeRegistry as jest.Mocked<typeof mfeRegistry>

describe('MFE Integration', () => {
beforeEach(() => {
jest.clearAllMocks()
})

it('should load and render MFE successfully', async () => {
const MockMFEComponent = () => <div>Mock Customer App</div>

mockMFERegistry.getMFE.mockReturnValue({
config: {
name: 'customerApp',
displayName: 'Customer Management',
remoteEntry: 'http://localhost:3001/mf-manifest.json',
exposedModule: './CustomerModule',
basePath: '/customers',
icon: 'Users',
permissions: [],
version: '1.0.0'
},
status: 'healthy',
lastHealthCheck: new Date(),
loadError: null,
module: null
})

mockMFERegistry.loadMFE.mockResolvedValue({
default: MockMFEComponent
})

render(<MFEContainer mfeName="customerApp" />)

expect(screen.getByText('Loading Customer App...')).toBeInTheDocument()

await waitFor(() => {
expect(screen.getByText('Mock Customer App')).toBeInTheDocument()
})

expect(mockMFERegistry.loadMFE).toHaveBeenCalledWith('customerApp')
})

it('should handle MFE loading errors gracefully', async () => {
mockMFERegistry.getMFE.mockReturnValue({
config: {
name: 'customerApp',
displayName: 'Customer Management',
remoteEntry: 'http://localhost:3001/mf-manifest.json',
exposedModule: './CustomerModule',
basePath: '/customers',
icon: 'Users',
permissions: [],
version: '1.0.0'
},
status: 'error',
lastHealthCheck: new Date(),
loadError: 'Failed to load module',
module: null
})

mockMFERegistry.loadMFE.mockRejectedValue(new Error('Network error'))

render(
<MFEContainer
mfeName="customerApp"
fallback={<div>Fallback content</div>}
/>
)

await waitFor(() => {
expect(screen.getByText('Failed to Load customerApp')).toBeInTheDocument()
})

expect(screen.getByText('Fallback content')).toBeInTheDocument()
})
})

E2E Shell Testing

// tests/e2e/shell-navigation.spec.ts
import { test, expect } from '@playwright/test'

test.describe('Shell Navigation', () => {
test.beforeEach(async ({ page }) => {
// Mock the MFE endpoints
await page.route('**/mf-manifest.json', route => {
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
name: 'customer_app',
remotes: {},
shared: {}
})
})
})

await page.goto('/')
})

test('should navigate between shell and MFE routes', async ({ page }) => {
// Should start on dashboard
await expect(page.locator('h1')).toContainText('Dashboard')

// Navigate to customers (MFE)
await page.click('text=Customer Management')
await expect(page.url()).toContain('/customers')

// Should load customer MFE
await expect(page.locator('[data-mfe="customerApp"]')).toBeVisible()

// Navigate back to dashboard
await page.click('text=Dashboard')
await expect(page.url()).toContain('/dashboard')
await expect(page.locator('h1')).toContainText('Dashboard')
})

test('should handle MFE loading errors', async ({ page }) => {
// Mock failed MFE load
await page.route('**/customerApp/**', route => {
route.abort('failed')
})

await page.click('text=Customer Management')

await expect(page.locator('text=Failed to Load customerApp')).toBeVisible()
await expect(page.locator('text=Fallback content')).toBeVisible()
})

test('should maintain authentication state across MFEs', async ({ page }) => {
// Login
await page.fill('[data-testid="username"]', 'admin')
await page.fill('[data-testid="password"]', 'password')
await page.click('[data-testid="login-button"]')

// Navigate to MFE
await page.click('text=Customer Management')

// Should still be authenticated
await expect(page.locator('[data-testid="user-menu"]')).toBeVisible()
await expect(page.locator('text=Admin User')).toBeVisible()
})
})

Quick Start

  1. Generate the shell application:

    archetect render git@github.com:p6m-archetypes/typescript-modernjs-mfe-shell.archetype.git
  2. Install dependencies:

    npm install
  3. Configure MFE endpoints:

    # Update src/config/mfe.config.ts with your MFE URLs
  4. Start development server:

    npm run dev
  5. Access the shell:

    http://localhost:8080
  6. Start MFE applications:

    # Start customer app on port 3001
    # Start order app on port 3002
    # etc.
  7. Run tests:

    npm run test
    npm run test:e2e

Best Practices

Shell Architecture

  • Keep the shell lightweight and focused on orchestration
  • Implement proper error boundaries for MFE failures
  • Use consistent authentication and authorization patterns
  • Maintain minimal shared dependencies

MFE Integration

  • Design clear contracts between shell and MFEs
  • Implement health monitoring for all MFEs
  • Use semantic versioning for MFE deployments
  • Provide fallback experiences for MFE failures

Performance

  • Implement lazy loading for MFE modules
  • Use proper caching strategies for MFE assets
  • Monitor bundle sizes and loading performance
  • Optimize shared dependencies

Development

  • Maintain independent development workflows
  • Use proper environment configuration
  • Implement comprehensive monitoring and logging
  • Document MFE integration patterns clearly