A production-ready Micro Frontends architecture demonstration using modern web technologies. This project showcases a real-world Product Catalog application where the host app dynamically loads product detail components from a remote micro frontend.
.
├── host/ # Container application (Host)
│ ├── src/
│ │ ├── components/
│ │ │ ├── Header.tsx # Application header with cart
│ │ │ ├── Footer.tsx # Application footer
│ │ │ ├── ProductList.tsx # Product grid component
│ │ │ ├── ProductListView.tsx # Product list view with header
│ │ │ ├── ProductDetailsView.tsx # Product details view container
│ │ │ ├── BackButton.tsx # Reusable back navigation button
│ │ │ └── ErrorBoundary.tsx # Error boundary for graceful error handling
│ │ ├── data/
│ │ │ └── products.ts # Product data
│ │ ├── types/
│ │ │ └── index.ts # TypeScript type definitions
│ │ └── App.tsx # Main application (orchestrator)
│ ├── Dockerfile
│ └── nginx.conf
├── remote/ # Micro frontend application (Remote)
│ ├── src/
│ │ ├── components/
│ │ │ └── ProductCard.tsx # Remote component (exposed via Module Federation)
│ │ └── types/
│ │ └── index.ts # TypeScript type definitions
│ ├── Dockerfile
│ └── nginx.conf
├── docker-compose.yml # Docker orchestration
└── README.md
The host application follows a component-based architecture with clear separation of concerns:
App.tsx: Main orchestrator managing state and routing between viewsProductListView: Displays the product catalog with header informationProductDetailsView: Container for remote product details component with error handlingProductList: Reusable product grid componentHeader: Application header with shopping cartFooter: Application footerBackButton: Reusable navigation componentErrorBoundary: Error handling wrapper for remote components
- ✅ TypeScript - Full type safety across host and remote apps
- ✅ Tailwind CSS - Modern, utility-first CSS framework
- ✅ Module Federation - Runtime code sharing and dynamic loading
- ✅ Docker Support - Containerized applications with Docker Compose
- ✅ Error Boundaries - Graceful error handling
- ✅ Lazy Loading - Optimized performance with React Suspense
- ✅ Production Ready - Nginx configuration, health checks, and best practices
- ✅ Real-world App - Product Catalog with shopping cart functionality
- React 18 - UI library with hooks and Suspense
- TypeScript 5 - Type-safe development
- Vite 5 - Fast build tool and dev server
- Tailwind CSS 3 - Utility-first CSS framework
- Module Federation - Runtime module sharing
- Docker & Docker Compose - Containerization
- Nginx - Production web server
The easiest way to run the entire application:
# Build and start both applications
docker-compose up --build
# Or run in detached mode
docker-compose up -d --build
# View logs
docker-compose logs -f
# Stop applications
docker-compose downAccess the applications:
- Host App: http://localhost:5173
- Remote App: http://localhost:5174
- Node.js 18+ and npm/yarn/pnpm
-
Install dependencies for the remote app:
cd remote npm install -
Install dependencies for the host app:
cd ../host npm install
You need to run both applications simultaneously. Open two terminal windows:
Terminal 1 - Remote App (must start first):
cd remote
npm run devThe remote app will run on http://localhost:5174
Terminal 2 - Host App:
cd host
npm run devThe host app will run on http://localhost:5173
Open your browser and navigate to http://localhost:5173. You should see:
- A product catalog with multiple products
- Click on any product to view detailed information loaded from the remote micro frontend
- Shopping cart functionality
Micro Frontends architecture is crucial for modern web applications because it addresses several key challenges:
-
Team Autonomy & Scalability
- Different teams can work independently on separate micro frontends
- Teams can choose their own tech stack and deployment cycles
- Reduces coordination overhead in large organizations
- Enables parallel development and faster feature delivery
-
Independent Deployment
- Deploy micro frontends independently without affecting other parts
- Rollback individual features without impacting the entire application
- Faster release cycles and reduced deployment risk
- A/B testing and gradual rollouts become easier
-
Technology Diversity
- Different micro frontends can use different frameworks (React, Vue, Angular)
- Teams can upgrade dependencies independently
- Experiment with new technologies without rewriting the entire app
- Gradual migration path for legacy applications
-
Code Isolation & Maintainability
- Smaller, focused codebases are easier to understand and maintain
- Clear boundaries prevent tight coupling
- Easier to onboard new developers
- Better code organization and separation of concerns
-
Performance Optimization
- Load only what's needed, when it's needed (code splitting)
- Independent caching strategies per micro frontend
- Smaller bundle sizes per application
- Better performance through lazy loading
-
Fault Isolation
- Errors in one micro frontend don't crash the entire application
- Error boundaries can gracefully handle failures
- Better resilience and user experience
The host application (host/) is the container/orchestrator that:
- Displays the product catalog using local components
- Manages application state (selected product, cart count)
- Dynamically loads the
ProductCardcomponent from the remote app at runtime - Handles routing and navigation between views
- Provides error boundaries for graceful failure handling
- Uses a clean component architecture for maintainability
Component Structure:
App.tsx (Orchestrator)
├── Header (Cart display)
├── ProductListView (Product catalog)
│ └── ProductList (Product grid)
└── ProductDetailsView (Remote component container)
└── RemoteProductCard (Loaded from remote app)
The remote application (remote/) is a standalone micro frontend that:
- Exposes the
ProductCardcomponent via Module Federation - Can run independently for development and testing
- Shares React and React-DOM with the host to avoid duplication
- Maintains its own build and deployment pipeline
- Provides detailed product information UI
┌─────────────────────────────────────────────────────────────┐
│ Browser (Client) │
│ │
│ 1. User visits Host App (localhost:5173) │
│ 2. Host App loads and initializes │
│ 3. User clicks on a product │
│ 4. Host App requests Remote Component │
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Host App │ │ Remote App │ │
│ │ (Port 5173) │──────────────▶ │ (Port 5174) │ │
│ │ │ HTTP Request │ │ │
│ │ │◀────────────── │ │ │
│ │ │ remoteEntry.js│ │ │
│ │ │ + ProductCard │ │ │
│ └──────────────┘ └──────────────┘ │
│ │
│ 5. Module Federation loads remote component dynamically │
│ 6. Component renders in Host App context │
└─────────────────────────────────────────────────────────────┘
-
Build Time:
- Remote app builds and generates
remoteEntry.js(Module Federation manifest) - Host app is configured to consume from remote URL
- Shared dependencies (React, React-DOM) are configured
- Remote app builds and generates
-
Runtime Connection:
- When host app needs the remote component, it makes an HTTP request to the remote app
- Remote app serves
remoteEntry.jswhich contains module metadata - Module Federation runtime loads the component code dynamically
- Component executes in the host app's context with shared dependencies
-
Communication:
- Components communicate via props (passed from host to remote)
- Callbacks allow remote components to communicate back to host
- Shared state can be managed through context or state management libraries
Nginx serves multiple critical purposes in this architecture:
- Serves static files (HTML, CSS, JS) efficiently
- Handles HTTP requests and routing
- More performant than development servers (Vite dev server)
- CORS Headers: Remote app must allow cross-origin requests from host app
add_header 'Access-Control-Allow-Origin' '*'; add_header 'Access-Control-Allow-Methods' 'GET, OPTIONS';
- Proper MIME Types: Ensures JavaScript modules are served with correct content-type
- Caching Strategy: Optimizes loading of
remoteEntry.jsand component chunks
- Handles client-side routing for React applications
- Serves
index.htmlfor all routes (fallback) - Prevents 404 errors on direct URL access or page refresh
- Gzip Compression: Reduces bundle sizes for faster loading
- Static File Caching: Caches assets with appropriate headers
- HTTP/2 Support: Enables multiplexing for better performance
- Security headers (X-Frame-Options, X-Content-Type-Options, etc.)
- Protection against common web vulnerabilities
- SSL/TLS termination in production
- Development: Vite dev server handles hot module replacement
- Production: Nginx serves optimized, minified bundles
- Docker containers use Nginx for production-ready deployment
┌─────────────────────────────────────────────────────────────────┐
│ User Interaction Flow │
└─────────────────────────────────────────────────────────────────┘
1. INITIAL PAGE LOAD
┌─────────────┐
│ Browser │
└──────┬──────┘
│ HTTP GET /
▼
┌─────────────────┐
│ Nginx (Host) │ Serves index.html + host app bundle
└──────┬──────────┘
│
▼
┌─────────────────┐
│ Host App │ React app initializes
│ (App.tsx) │ - Loads ProductListView
│ │ - Renders product catalog
└─────────────────┘
2. USER CLICKS PRODUCT
┌─────────────────┐
│ Host App │ setSelectedProduct(product)
│ (App.tsx) │ - Switches to ProductDetailsView
└──────┬──────────┘
│
▼
┌──────────────────┐
│ProductDetailsView│ Lazy loads remote component
│ (Suspense) │ - Triggers Module Federation
└──────┬───────────┘
│
│ HTTP Request: GET /remoteEntry.js
▼
┌─────────────────┐
│ Nginx (Remote) │ Serves Module Federation manifest
└──────┬──────────┘
│
│ HTTP Request: GET /assets/ProductCard.[hash].js
▼
┌─────────────────┐
│ Nginx (Remote) │ Serves component bundle
└──────┬──────────┘
│
▼
┌──────────────────┐
│ Module Federation│ Loads & executes component
│ Runtime │ - Shares React instance
│ │ - Renders in host context
└──────┬───────────┘
│
▼
┌─────────────────┐
│RemoteProductCard│ Component renders with props
│ (Remote App) │ - Displays product details
│ │ - onAddToCart callback available
└─────────────────┘
3. USER ADDS TO CART
┌─────────────────┐
│RemoteProductCard│ Calls onAddToCart()
└──────┬──────────┘
│
▼
┌─────────────────┐
│ Host App │ handleAddToCart()
│ (App.tsx) │ - Updates cartCount state
└──────┬──────────┘
│
▼
┌─────────────────┐
│ Header │ Re-renders with new count
└─────────────────┘
Host (host/vite.config.ts):
// Consumes remote app
remotes: {
remote_app: 'http://localhost:5174/assets/remoteEntry.js'
},
// Shares dependencies to avoid duplication
shared: {
react: { singleton: true },
'react-dom': { singleton: true }
}Remote (remote/vite.config.ts):
// Exposes component
exposes: {
'./ProductCard': './src/components/ProductCard'
},
// Shares dependencies
shared: {
react: { singleton: true },
'react-dom': { singleton: true }
}┌──────────────┐ Props ┌──────────────┐
│ Host App │───────────────────────▶│ Remote App │
│ │ │ │
│ - State │◀───────────────────────│ - Component │
│ - Handlers │ Callbacks │ - UI │
└──────────────┘ └──────────────┘
│ │
│ Shared React Instance │
└──────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ Docker Network (microfrontends-network) │
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ host-app │ │ remote-app │ │
│ │ :5173 │──────────────▶│ :5174 │ │
│ │ (Nginx) │ HTTP Request │ (Nginx) │ │
│ │ │◀──────────────│ │ │
│ │ │ remoteEntry │ │ │
│ └──────────────┘ └──────────────┘ │
│ │
│ Both containers can communicate via service names │
│ Host app uses: http://remote-app/assets/remoteEntry.js │
└─────────────────────────────────────────────────────────┘
# Build production images
docker-compose build
# Start production containers
docker-compose up -dBuild Remote App First:
cd remote
npm run buildBuild Host App:
cd host
npm run buildNote: In production, update the remote URL in host/vite.config.ts to point to your production remote app URL.
The docker-compose.yml file orchestrates both applications:
- remote-app: Runs on port 5174
- host-app: Runs on port 5173, depends on remote-app
- Health checks ensure proper startup order
- Shared network for inter-container communication
Both applications use multi-stage builds:
- Builder stage: Installs dependencies and builds the application
- Production stage: Serves the built application with Nginx
- Optimized for SPA routing
- Gzip compression enabled
- Security headers configured
- CORS headers for Module Federation (remote app)
- Proper caching strategies
Module Federation allows JavaScript applications to:
- Dynamically load code from other applications at runtime
- Share dependencies to avoid duplication
- Enable independent deployment of micro frontends
- Maintain team autonomy with separate codebases
TypeScript ensures type safety across applications:
- Shared type definitions in both apps
- Type-safe component props
- Compile-time error checking
- Component Architecture - Clean separation of concerns with focused, reusable components
- Error Boundaries - Graceful error handling for remote components with fallback UI
- Lazy Loading - Performance optimization with React Suspense for code splitting
- Shared Dependencies - React and React-DOM shared as singletons to avoid duplication
- Environment Variables - Configurable remote URLs for different environments
- Health Checks - Docker health checks for reliable orchestration and startup order
- Security Headers - Nginx security headers configured for production security
- Code Splitting - Optimized bundle sizes with dynamic imports
- Type Safety - Shared TypeScript types ensure consistency across apps
- Containerization - Docker ensures consistent environments across development and production
Create .env files based on .env.example:
Host App:
PORT=5173
VITE_REMOTE_HOST=localhost
VITE_REMOTE_PORT=5174Remote App:
PORT=5174For Docker, the remote host should be the service name:
VITE_REMOTE_HOST=remote-app
VITE_REMOTE_PORT=80# Host app
cd host
npm run lint
# Remote app
cd remote
npm run lintTypeScript type checking is integrated into the build process:
npm run build- Ensure the remote app is running before starting the host app
- Check browser console for CORS or network errors
- Verify the remote URL in
host/vite.config.ts - Check that
remoteEntry.jsis accessible
- Port conflicts: Ensure ports 5173 and 5174 are available
- Build failures: Check Docker logs with
docker-compose logs - Network issues: Verify both containers are on the same network
- The remote app includes CORS headers in its Nginx configuration
- For local development, ensure both apps are running
- For Docker, the apps communicate via the internal network
- Module Federation Documentation
- Vite Plugin Federation
- React TypeScript Cheatsheet
- Tailwind CSS Documentation
This is a learning project. Feel free to:
- Experiment with different architectures
- Add more micro frontends
- Implement additional features
- Share your improvements
MIT
Built with ❤️ for learning Micro Frontends Architecture