π GitHub Repository: https://github.com/Dimuthu7/Micro-frontend-with-React-Vite
A comprehensive guide to building production-ready micro frontends architecture using React, TypeScript, Vite, and Module Federation. Learn how to break down monolithic frontends into independent, scalable applications.
Table of Contents
- Introduction to Micro Frontends
- Why Micro Frontends?
- Benefits of Micro Frontends Architecture
- Project Overview
- Tech Stack
- Understanding Module Federation
- Architecture Deep Dive
- Implementation Details
- Local Development Setup
- Docker Setup and Deployment
- Running the Project
- Best Practices Implemented
- Conclusion
Introduction to Micro Frontends
Micro Frontends is an architectural approach that extends the microservices pattern to the frontend. Instead of building a monolithic frontend application, you break it down into smaller, independent applications that can be developed, tested, and deployed separately.
Think of it like a restaurant: instead of one chef preparing everything, you have specialized chefs (teams) working on different dishes (features) that come together to create a complete meal (user experience).
Why Micro Frontends?
The Problem with Monolithic Frontends
As applications grow, monolithic frontends face several challenges:
- Scaling Issues: Large codebases become difficult to navigate and maintain
- Deployment Bottlenecks: One bug can block the entire release
- Team Coordination: Multiple teams working on the same codebase creates conflicts
- Technology Lock-in: Difficult to adopt new technologies or upgrade dependencies
- Performance: Large bundles slow down initial page load
Benefits of Micro Frontends Architecture
1. Team Autonomy & Scalability
- Independent Development: Teams can work in parallel without stepping on each other
- Own Technology Stack: Each team can choose frameworks that best fit their needs
- Faster Feature Delivery: Smaller codebases mean faster development cycles
- Reduced Coordination: Less need for cross-team meetings and synchronization
2. Independent Deployment
- Isolated Releases: Deploy features independently without affecting other parts
- Faster Rollouts: Release features as soon as they're ready
- Safer Rollbacks: Rollback individual features without impacting the entire app
- A/B Testing: Easier to test new features with a subset of users
3. Technology Diversity
- Framework Flexibility: Use React for one feature, Vue for another, Angular for a third
- Gradual Migration: Migrate legacy code piece by piece
- Technology Experimentation: Try new technologies without rewriting everything
- Independent Upgrades: Upgrade dependencies per micro frontend
4. Code Isolation & Maintainability
- Smaller Codebases: Easier to understand, test, and maintain
- Clear Boundaries: Well-defined interfaces prevent tight coupling
- Easier Onboarding: New developers can focus on one micro frontend
- Better Organization: Clear separation of concerns
5. Performance Optimization
- Code Splitting: Load only what's needed, when it's needed
- Independent Caching: Each micro frontend can have its own caching strategy
- Smaller Bundles: Smaller applications mean smaller bundle sizes
- Lazy Loading: Load components on-demand for better performance
6. Fault Isolation
- Error Containment: Errors in one micro frontend don't crash the entire app
- Graceful Degradation: Error boundaries can handle failures elegantly
- Better Resilience: One failing feature doesn't break the user experience
Project Overview
This project demonstrates a production-ready micro frontends architecture using a Product Catalog application. The application consists of:
- Host Application: The container/orchestrator that displays the product catalog and manages application state
- Remote Application: A micro frontend that provides detailed product information components
Project Structure
βββ 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
Tech Stack
Core Technologies
- React 18: Modern UI library with hooks and Suspense for async operations
- TypeScript 5: Type-safe development with compile-time error checking
- Vite 5: Fast build tool and dev server with excellent HMR (Hot Module Replacement)
- Tailwind CSS 3: Utility-first CSS framework for rapid UI development
Micro Frontends Technology
- Module Federation: Runtime module sharing and dynamic loading
- @originjs/vite-plugin-federation: Vite plugin that enables Module Federation
DevOps & Infrastructure
- Docker & Docker Compose: Containerization for consistent environments
- Nginx: Production web server with optimized configuration
- Node.js 18+: JavaScript runtime environment
Understanding Module Federation
What is Module Federation?
Module Federation is a JavaScript architecture that allows a JavaScript application to dynamically load code from another application at runtime. It was introduced by Webpack 5 and has been adapted for Vite through plugins.
Key Concepts
1. Host Application (Container)
The host application is the main application that consumes remote modules. It:
- Loads and orchestrates remote components
- Manages application state
- Handles routing and navigation
- Provides the shell/framework for the application
2. Remote Application (Micro Frontend)
The remote application exposes modules that can be consumed by the host. It:
- Exposes components, utilities, or features
- Can run independently
- Shares dependencies with the host to avoid duplication
- Maintains its own build and deployment pipeline
3. Shared Dependencies
Both host and remote can share common dependencies (like React, React-DOM) to:
- Avoid loading duplicate code
- Ensure consistent versions
- Reduce bundle sizes
- Maintain singleton instances
How Module Federation Works
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 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 β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Architecture Deep Dive
Component Architecture
The host application follows a clean component-based architecture:
App.tsx (Orchestrator)
βββ Header (Cart display)
βββ ProductListView (Product catalog)
β βββ ProductList (Product grid)
βββ ProductDetailsView (Remote component container)
βββ RemoteProductCard (Loaded from remote app)
Data Flow
ββββββββββββββ Props βββββββββββββββ
β Host App βββββββββββββββββββββββββΆβ Remote App β
β β β β
β - State ββββββββββββββββββββββββββ - Component β
β - Handlers β Callbacks β - UI β
ββββββββββββββ βββββββββββββββ
β β
β Shared React Instance β
ββββββββββββββββββββββββββββββββββββββββ
Complete Request Flow
-
Initial Page Load
- Browser requests the host app
- Nginx serves
index.htmland host app bundle - React app initializes and renders
ProductListView - Product catalog displays
-
User Clicks Product
- Host app updates state with selected product
- Switches to
ProductDetailsView - Component lazy loads the remote component
- Module Federation makes HTTP request to remote app
-
Remote Component Loading
- Host requests
remoteEntry.jsfrom remote app - Remote app serves Module Federation manifest
- Host requests component bundle (
ProductCard.[hash].js) - Module Federation runtime loads and executes component
- Component renders in host context with shared React instance
- Host requests
-
User Interaction
- User clicks "Add to Cart" in remote component
- Callback function executes in host app
- Host app updates cart count state
- Header re-renders with new count
Implementation Details
Module Federation Configuration
Host Application (host/vite.config.ts)
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import federation from '@originjs/vite-plugin-federation';
export default defineConfig({
plugins: [
react(),
federation({
name: 'host_app',
remotes: {
remote_app: {
external: `http://${process.env.VITE_REMOTE_HOST || 'localhost'}:${process.env.VITE_REMOTE_PORT || '5174'}/assets/remoteEntry.js`,
shareScope: 'default',
},
},
shared: {
react: {
singleton: true,
requiredVersion: '^18.2.0',
},
'react-dom': {
singleton: true,
requiredVersion: '^18.2.0',
},
},
}),
],
build: {
modulePreload: false,
target: 'esnext',
minify: false,
cssCodeSplit: false,
},
server: {
port: Number(process.env.PORT) || 5173,
host: true,
},
});
Key Points:
-
remotes: Defines where to load remote modules from -
shared: Specifies dependencies to share (React, React-DOM) -
singleton: true: Ensures only one instance of React is loaded - Environment variables allow different URLs for different environments
Remote Application (remote/vite.config.ts)
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import federation from '@originjs/vite-plugin-federation';
export default defineConfig({
plugins: [
react(),
federation({
name: 'remote_app',
filename: 'remoteEntry.js',
exposes: {
'./ProductCard': './src/components/ProductCard',
},
shared: {
react: {
singleton: true,
requiredVersion: '^18.2.0',
},
'react-dom': {
singleton: true,
requiredVersion: '^18.2.0',
},
},
}),
],
build: {
modulePreload: false,
target: 'esnext',
minify: false,
cssCodeSplit: false,
},
server: {
port: Number(process.env.PORT) || 5174,
host: true,
cors: true,
},
});
Key Points:
-
exposes: Defines which modules/components to expose -
filename: Name of the Module Federation manifest file -
shared: Same shared dependencies as host to avoid duplication
Loading Remote Components
ProductDetailsView Component
import React, { Suspense, lazy } from 'react';
import { ErrorBoundary } from './ErrorBoundary';
// Lazy load the remote ProductCard component
const RemoteProductCard = lazy(() => import('remote_app/ProductCard'));
interface ProductDetailsViewProps {
product: Product;
onAddToCart: () => void;
onClose: () => void;
}
export const ProductDetailsView: React.FC<ProductDetailsViewProps> = ({
product,
onAddToCart,
onClose,
}) => {
return (
<ErrorBoundary
fallback={
<div>Failed to load remote component</div>
}
>
<Suspense
fallback={
<div>Loading product details...</div>
}
>
<RemoteProductCard
product={product}
onAddToCart={onAddToCart}
/>
</Suspense>
</ErrorBoundary>
);
};
Key Features:
-
Lazy Loading: Uses React's
lazy()to dynamically import the remote component - Suspense: Provides loading state while component is being fetched
- Error Boundary: Gracefully handles errors if remote component fails to load
- Type Safety: TypeScript ensures props match the expected interface
Error Handling
The project implements a robust error boundary:
import { Component, ErrorInfo, ReactNode } from 'react';
interface Props {
children: ReactNode;
fallback?: ReactNode;
}
interface State {
hasError: boolean;
error?: Error;
}
export class ErrorBoundary extends Component<Props, State> {
public state: State = {
hasError: false,
};
public static getDerivedStateFromError(error: Error): State {
return { hasError: true, error };
}
public componentDidCatch(error: Error, errorInfo: ErrorInfo) {
console.error('ErrorBoundary caught an error:', error, errorInfo);
}
public render() {
if (this.state.hasError) {
return this.props.fallback || <DefaultErrorUI />;
}
return this.props.children;
}
}
This ensures that if the remote component fails to load, the application doesn't crash and shows a user-friendly error message.
Type Safety Across Applications
Both applications share the same TypeScript interfaces:
// host/src/types/index.ts
// remote/src/types/index.ts
export interface Product {
id: string;
name: string;
description: string;
price: number;
image: string;
category: string;
inStock: boolean;
}
This ensures type safety when passing data between host and remote applications.
Local Development Setup
Prerequisites
- Node.js 18+ and npm/yarn/pnpm
- Code Editor (VS Code recommended)
- Git (for cloning the repository)
Step-by-Step Setup
1. Install Dependencies
Install Remote App Dependencies:
cd remote
npm install
Install Host App Dependencies:
cd ../host
npm install
Or use the Makefile:
make install
2. Understanding the Setup
The applications need to run simultaneously because:
- The host app needs to fetch the remote component from the remote app
- Both apps need to be running for Module Federation to work
- The remote app must be accessible via HTTP
3. Running the Applications
You need two terminal windows:
Terminal 1 - Remote App (must start first):
cd remote
npm run dev
The remote app will run on http://localhost:5174
Terminal 2 - Host App:
cd host
npm run dev
The host app will run on http://localhost:5173
4. Verify the Setup
- Open your browser and navigate to
http://localhost:5173 - You should see the product catalog
- Click on any product
- The product details should load from the remote micro frontend
- Check the browser console for any errors
Environment Variables
You can customize the setup using environment variables:
Host App (.env file):
PORT=5173
VITE_REMOTE_HOST=localhost
VITE_REMOTE_PORT=5174
Remote App (.env file):
PORT=5174
Troubleshooting Local Setup
Issue: Remote component not loading
- Ensure the remote app is running before starting the host app
- Check that both apps are running on the correct ports
- Verify the remote URL in
host/vite.config.ts - Check browser console for CORS errors
Issue: CORS errors
- The remote app's Vite dev server has CORS enabled
- Ensure both apps are running
- Check that ports match the configuration
Issue: Type errors
- Run
npm run buildto check for TypeScript errors - Ensure both apps have the same type definitions
- Check that shared types are synchronized
Docker Setup and Deployment
Why Docker?
Docker provides:
- Consistent Environments: Same environment in development and production
- Easy Deployment: Package everything needed to run the application
- Isolation: Each application runs in its own container
- Orchestration: Docker Compose manages multiple containers
Docker Architecture
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Docker Network (microfrontends-network) β
β β
β ββββββββββββββββ ββββββββββββββββ β
β β host-app β β remote-app β β
β β :5173 ββββββββββββββββΆβ :5174 β β
β β (Nginx) β HTTP Request β (Nginx) β β
β β βββββββββββββββββ β β
β β β remoteEntry β β β
β ββββββββββββββββ ββββββββββββββββ β
β β
β Both containers communicate via service names β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Dockerfile Structure
Both applications use multi-stage builds:
Stage 1: Builder
FROM node:18-alpine AS builder
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install dependencies
RUN npm ci
# Copy source code
COPY . .
# Build the application
RUN npm run build
Stage 2: Production
FROM nginx:alpine
# Install wget for health checks
RUN apk add --no-cache wget
# Copy built assets from builder stage
COPY --from=builder /app/dist /usr/share/nginx/html
# Copy nginx configuration
COPY nginx.conf /etc/nginx/conf.d/default.conf
# Expose port
EXPOSE 80
# Start nginx
CMD ["nginx", "-g", "daemon off;"]
Benefits of Multi-Stage Builds:
- Smaller final image (only production files)
- Faster builds (cached layers)
- Security (no build tools in production image)
Docker Compose Configuration
services:
remote-app:
build:
context: ./remote
dockerfile: Dockerfile
container_name: micro-frontend-remote
ports:
- "5174:80"
networks:
- micro-frontend-network
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost/assets/remoteEntry.js"]
interval: 10s
timeout: 5s
retries: 5
host-app:
build:
context: ./host
dockerfile: Dockerfile
args:
- VITE_REMOTE_HOST=remote-app
- VITE_REMOTE_PORT=80
container_name: micro-frontend-host
ports:
- "5173:80"
depends_on:
remote-app:
condition: service_healthy
networks:
- micro-frontend-network
networks:
micro-frontend-network:
driver: bridge
Key Features:
- Health Checks: Ensures remote app is ready before starting host app
-
Service Names: Containers communicate using service names (
remote-app) - Network Isolation: Both apps on the same Docker network
- Port Mapping: Exposes apps on host machine ports
Nginx Configuration
Why Nginx?
- Production Web Server: More performant than development servers
- CORS Headers: Required for Module Federation cross-origin requests
- SPA Routing: Handles client-side routing correctly
- Performance: Gzip compression, caching, security headers
Remote App Nginx Config
server {
listen 80;
root /usr/share/nginx/html;
# CORS headers for Module Federation
location = /assets/remoteEntry.js {
add_header Access-Control-Allow-Origin * always;
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS' always;
add_header Cache-Control "no-cache, no-store, must-revalidate";
}
# CORS for JavaScript files
location ~* ^/assets/.*\.js$ {
add_header Access-Control-Allow-Origin * always;
expires 1y;
add_header Cache-Control "public, immutable";
}
# SPA routing
location / {
try_files $uri $uri/ /index.html;
}
}
Key Points:
- CORS Headers: Essential for cross-origin Module Federation requests
- No Cache for remoteEntry.js: Ensures latest version is always loaded
- Cache for Assets: Optimizes performance with long-term caching
-
SPA Routing: Serves
index.htmlfor all routes
Running the Project
Option 1: Docker (Recommended for Production)
- Start Both Applications:
docker-compose up --build - Start in Detached Mode:
docker-compose up -d --build - View Logs:
docker-compose logs -f - Stop Applications:
docker-compose down
Using Makefile:
make docker-up # Start with logs
make docker-up-detached # Start in background
make docker-down # Stop containers
make docker-logs # View logs
Access Applications:
- Host App: http://localhost:5173
- Remote App: http://localhost:5174
Option 2: Local Development
Using Makefile:
make install # Install dependencies
make dev # Instructions for running both apps
Manual:
# Terminal 1
cd remote
npm run dev
# Terminal 2
cd host
npm run dev
Option 3: Production Build
Build Both Applications:
make build
Or manually:
# Build remote app first
cd remote
npm run build
# Build host app
cd ../host
npm run build
Preview Production Build:
# Terminal 1
cd remote
npm run preview
# Terminal 2
cd host
npm run preview---
Best Practices Implemented
1. Component Architecture
- Clean separation of concerns
- Focused, reusable components
- Clear component hierarchy
2. Error Handling
- Error boundaries for remote components
- Fallback UI for failed loads
- User-friendly error messages
3. Performance Optimization
- Lazy loading with React Suspense
- Code splitting per micro frontend
- Shared dependencies to reduce bundle size
- Nginx caching strategies
4. Type Safety
- Shared TypeScript types across applications
- Type-safe component props
- Compile-time error checking
5. Development Experience
- Hot Module Replacement (HMR) in development
- Clear project structure
- Environment variable configuration
- Makefile for common tasks
6. Production Readiness
- Multi-stage Docker builds
- Health checks for orchestration
- Security headers in Nginx
- Optimized build configurations
- CORS properly configured
7. Code Organization
- Shared types in both applications
- Consistent naming conventions
- Clear file structure
- Separation of concerns
Conclusion
This project demonstrates a production-ready micro frontends architecture using React, Vite, and Module Federation.
When to Use Micro Frontends
Good Fit:
- Large applications with multiple teams
- Need for independent deployments
- Different parts require different technologies
- Gradual migration from legacy systems
Not Ideal For:
- Small applications with a single team
- Tightly coupled features
- Limited DevOps resources
- Simple applications without scaling needs
Next Steps
- Add more micro frontends (e.g., user profile, checkout)
- Implement state management (Redux, Zustand)
- Add routing (React Router)
- Set up CI/CD pipelines
- Add monitoring and logging
- Implement authentication/authorization
Resources
- Module Federation Documentation
- Vite Plugin Federation
- React TypeScript Cheatsheet
- Tailwind CSS Documentation
Built with β€οΈ for learning Micro Frontends Architecture
This architecture provides a solid foundation for building scalable, maintainable frontend applications. By understanding these concepts and patterns, you can apply them to your own projects and teams.
Top comments (1)
Thanks for your article π
As of July 2026, originjs/vite-plugin-federation project appears largely unmaintained.
It has 200+ open issues, no commits since May 17, 2025, and an open deprecation proposal (#748) recommending migration to the official MFE Vite library.
Iβm going to adopt the official library to reduce the risk of production issues and avoid maintenance problems that could put my job at risk.