- Introduction
- Architecture Overview
- Getting Started
- Creating Your First App
- Integration Types
- Module Federation Setup
- Using the FrontFuse SDK
- User Session Management
- Menu Integration
- Deployment Guide
- Best Practices
- Troubleshooting
FrontFuse is a microfrontend hosting platform that enables you to build, deploy, and manage federated applications seamlessly. This guide will walk you through creating and deploying your own pluggable applications.
FrontFuse uses a hub-and-spoke architecture where:
- Platform Core: The main portal that hosts and manages all applications
- Federated Apps: Independent applications that integrate with the platform
- SDK: Shared utilities and context for seamless integration
- API: Backend services for authentication, app management, and communication
- Module Federation: For dynamic loading of React applications
- JWT Authentication: Secure user sessions across all apps
- WebSocket Communication: Real-time updates and notifications
- Shared Context: User data, menu items, and platform state
- Node.js 24+ (Active LTS) and npm
- React 19+ knowledge
- Basic understanding of Module Federation
- Git for version control
-
Clone the FrontFuse repository:
git clone https://github.com/your-org/frontfuse.git cd frontfuse -
Install dependencies:
npm install
-
Start the development environment:
# Terminal 1 - Backend npm run dev:backend # Terminal 2 - Frontend npm run dev:frontend # Terminal 3 - Example app (optional) cd clock-app && npm run dev
-
Access the platform:
- Platform: http://localhost:5173
- API Docs: http://localhost:3001/api-docs
- Clock App Example: http://localhost:3003
Create a new React application with Vite:
npm create vite@latest my-frontfuse-app -- --template react-ts
cd my-frontfuse-app
npm installnpm install @frontfuse/sdkInstall the Module Federation plugin:
npm install @originjs/vite-plugin-federation --save-devUpdate your 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: 'myApp',
filename: 'remoteEntry.js',
exposes: {
'./App': './src/App.tsx',
},
shared: ['react', 'react-dom'],
}),
],
build: {
modulePreload: false,
target: 'esnext',
minify: false,
cssCodeSplit: false,
},
})Update your src/App.tsx:
import React, { useEffect } from 'react'
import { PlatformProvider, useCurrentUser, useGlobalMenu } from '@frontfuse/sdk'
function AppContent() {
const { user, isAuthenticated } = useCurrentUser()
const { addAppMenuItems, removeAppMenuItems } = useGlobalMenu()
useEffect(() => {
// Register menu items when app loads
if (isAuthenticated) {
addAppMenuItems('myApp', [
{
id: 'my-dashboard',
label: 'My Dashboard',
path: '/my-dashboard',
icon: '📊',
category: 'app',
appId: 'myApp',
order: 1
},
{
id: 'my-settings',
label: 'Settings',
path: '/my-settings',
icon: '⚙️',
category: 'app',
appId: 'myApp',
order: 2
}
])
}
// Cleanup on unmount
return () => {
removeAppMenuItems('myApp')
}
}, [isAuthenticated, addAppMenuItems, removeAppMenuItems])
if (!isAuthenticated) {
return <div>Please log in to access this application.</div>
}
return (
<div className="my-app">
<h1>Welcome to My App, {user?.firstName}!</h1>
<p>Your email: {user?.email}</p>
<p>Your roles: {user?.roles?.join(', ')}</p>
{/* Your app content here */}
</div>
)
}
function App() {
return (
<PlatformProvider>
<AppContent />
</PlatformProvider>
)
}
export default AppAdd these scripts to your package.json:
{
"scripts": {
"dev": "vite --port 3003",
"build": "tsc && vite build",
"preview": "vite preview --port 3003",
"serve": "vite preview --port 3003"
}
}FrontFuse supports three integration types:
- Best for: React applications
- Pros: Shared dependencies, seamless integration, optimal performance
- Cons: Requires build configuration
- Best for: Legacy applications, non-React apps
- Pros: Technology agnostic, easy integration
- Cons: Limited communication, styling challenges
- Best for: Framework-agnostic components
- Pros: Standard-based, reusable
- Cons: Limited React integration
For production-ready Module Federation:
// 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: 'myApp',
filename: 'remoteEntry.js',
exposes: {
'./App': './src/App.tsx',
'./routes': './src/routes.tsx', // Optional: expose routing
},
shared: {
react: {
singleton: true,
requiredVersion: '^19.0.0',
},
'react-dom': {
singleton: true,
requiredVersion: '^19.0.0',
},
'@frontfuse/sdk': {
singleton: true,
},
},
}),
],
build: {
modulePreload: false,
target: 'esnext',
minify: false,
cssCodeSplit: false,
rollupOptions: {
external: ['react', 'react-dom'],
},
},
server: {
port: 3003,
cors: true,
},
})Implement error boundaries for robust federation:
// src/ErrorBoundary.tsx
import React, { Component, ErrorInfo, ReactNode } from 'react'
interface Props {
children: ReactNode
}
interface State {
hasError: boolean
error?: Error
}
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('Microfrontend error:', error, errorInfo)
}
public render() {
if (this.state.hasError) {
return (
<div className="error-boundary">
<h2>Something went wrong in this application.</h2>
<details>
<summary>Error details</summary>
<pre>{this.state.error?.stack}</pre>
</details>
</div>
)
}
return this.props.children
}
}
export default ErrorBoundaryimport { useCurrentUser } from '@frontfuse/sdk'
function MyComponent() {
const { user, isAuthenticated, isLoading } = useCurrentUser()
if (isLoading) return <div>Loading...</div>
if (!isAuthenticated) return <div>Please log in</div>
return <div>Hello, {user.firstName}!</div>
}import { useGlobalMenu } from '@frontfuse/sdk'
function MyComponent() {
const {
addAppMenuItems,
removeAppMenuItems,
portalMenuItems,
appMenuItems
} = useGlobalMenu()
// Add menu items
const handleAddMenu = () => {
addAppMenuItems('myApp', [
{
id: 'new-item',
label: 'New Feature',
path: '/new-feature',
icon: '✨',
category: 'app',
appId: 'myApp'
}
])
}
return <button onClick={handleAddMenu}>Add Menu Item</button>
}import { useAppContext } from '@frontfuse/sdk'
function MyComponent() {
const { state, dispatch } = useAppContext()
// Access platform state
const { user, apps, menuItems } = state
return <div>Platform has {apps.length} apps</div>
}import { useCurrentUser } from '@frontfuse/sdk'
function UserProfile() {
const { user, isAuthenticated } = useCurrentUser()
if (!isAuthenticated) {
return <div>Please log in</div>
}
return (
<div className="user-profile">
<h2>{user.firstName} {user.lastName}</h2>
<p>Email: {user.email}</p>
<p>Roles: {user.roles.join(', ')}</p>
<p>User ID: {user.id}</p>
</div>
)
}import { useCurrentUser } from '@frontfuse/sdk'
function useUserStorage(key: string) {
const { user } = useCurrentUser()
const userKey = user ? `${key}_${user.id}` : key
const setItem = (value: any) => {
localStorage.setItem(userKey, JSON.stringify(value))
}
const getItem = () => {
const item = localStorage.getItem(userKey)
return item ? JSON.parse(item) : null
}
const removeItem = () => {
localStorage.removeItem(userKey)
}
return { setItem, getItem, removeItem }
}
// Usage
function MyComponent() {
const { setItem, getItem } = useUserStorage('myAppData')
const saveData = () => {
setItem({ preferences: { theme: 'dark' } })
}
const loadData = () => {
const data = getItem()
console.log('User data:', data)
}
return (
<div>
<button onClick={saveData}>Save Data</button>
<button onClick={loadData}>Load Data</button>
</div>
)
}interface MenuItem {
id: string
label: string
path: string
icon?: string
category: 'portal' | 'app' | 'system'
appId?: string
order?: number
roles?: string[]
isActive?: boolean
}import { useGlobalMenu, useCurrentUser } from '@frontfuse/sdk'
import { useEffect } from 'react'
function MenuManager() {
const { addAppMenuItems, removeAppMenuItems } = useGlobalMenu()
const { user, isAuthenticated } = useCurrentUser()
useEffect(() => {
if (!isAuthenticated) return
const menuItems = [
{
id: 'dashboard',
label: 'Dashboard',
path: '/dashboard',
icon: '📊',
category: 'app' as const,
appId: 'myApp',
order: 1,
},
]
// Add admin-only items
if (user?.roles?.includes('admin')) {
menuItems.push({
id: 'admin-panel',
label: 'Admin Panel',
path: '/admin',
icon: '⚙️',
category: 'app' as const,
appId: 'myApp',
order: 10,
})
}
addAppMenuItems('myApp', menuItems)
// Cleanup
return () => removeAppMenuItems('myApp')
}, [isAuthenticated, user?.roles, addAppMenuItems, removeAppMenuItems])
return null
}Two kinds of deployment. This section covers deploying your microfrontend app (typically static hosting, then registering it with the platform at runtime). Deploying the FuzeFront platform itself is now Kubernetes-based: a Helm chart into a local kind cluster (
kind-fuzeinfra) for development, and Argo CD + Contabo k3s for production. Seedeploy/helm/fuzefront/README.mdanddocs/PRODUCTION_DEPLOYMENT.md. The old docker-compose platform deployment is legacy.
# Bring up FuzeInfra (ingress-nginx + Postgres + Redis) in kind
cd FuzeInfra && make kind-up && cd ..
# Build + load the FuzeFront images into the cluster
docker build -t fuzefront/backend:local ./backend
docker build -t fuzefront/frontend:local --build-arg VITE_API_URL=http://fuzefront.dev.local ./frontend
kind load docker-image fuzefront/backend:local fuzefront/frontend:local --name fuzeinfra
# Deploy with Helm
helm upgrade --install fuzefront deploy/helm/fuzefront \
-n fuzefront --create-namespace \
-f deploy/helm/fuzefront/values-local.yaml
# then add `127.0.0.1 fuzefront.dev.local` to your hosts filenpm run build# Install Netlify CLI
npm install -g netlify-cli
# Deploy
netlify deploy --prod --dir=dist# Install Vercel CLI
npm install -g vercel
# Deploy
vercel --prod# Build and sync to S3
npm run build
aws s3 sync dist/ s3://your-bucket-name --delete
aws cloudfront create-invalidation --distribution-id YOUR_DISTRIBUTION_ID --paths "/*"Once deployed, register your app with FrontFuse:
curl -X POST http://localhost:3001/api/apps \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-d '{
"name": "My App",
"url": "https://my-app.netlify.app",
"iconUrl": "https://my-app.netlify.app/icon.svg",
"integrationType": "module-federation",
"remoteUrl": "https://my-app.netlify.app/assets/remoteEntry.js",
"scope": "myApp",
"module": "./App",
"description": "My awesome microfrontend application"
}'Create environment-specific configurations:
// src/config/index.ts
const config = {
development: {
apiUrl: 'http://localhost:3001',
appUrl: 'http://localhost:3003',
},
production: {
apiUrl: 'https://api.frontfuse.dev',
appUrl: 'https://my-app.netlify.app',
},
}
export default config[process.env.NODE_ENV || 'development']- Lazy Loading: Load components only when needed
- Code Splitting: Split your app into smaller chunks
- Shared Dependencies: Use Module Federation shared dependencies
- Caching: Implement proper caching strategies
// Lazy loading example
import { lazy, Suspense } from 'react'
const LazyComponent = lazy(() => import('./LazyComponent'))
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</Suspense>
)
}- Always wrap your app in error boundaries
- Implement graceful fallbacks
- Log errors for debugging
- Validate user permissions before rendering sensitive content
- Sanitize user inputs
- Use HTTPS in production
// Permission checking
function AdminPanel() {
const { user } = useCurrentUser()
if (!user?.roles?.includes('admin')) {
return <div>Access denied</div>
}
return <div>Admin content</div>
}// src/__tests__/App.test.tsx
import { render, screen } from '@testing-library/react'
import { PlatformProvider } from '@frontfuse/sdk'
import App from '../App'
// Mock the SDK
jest.mock('@frontfuse/sdk', () => ({
PlatformProvider: ({ children }: any) => children,
useCurrentUser: () => ({
user: { firstName: 'Test', email: 'test@example.com', roles: ['user'] },
isAuthenticated: true
}),
useGlobalMenu: () => ({
addAppMenuItems: jest.fn(),
removeAppMenuItems: jest.fn()
})
}))
test('renders app content', () => {
render(<App />)
expect(screen.getByText(/Welcome to My App, Test!/)).toBeInTheDocument()
})Error: Loading chunk failed
Solution: Check that your remoteUrl is correct and accessible.
Error: Shared module is not available for eager consumption
Solution: Ensure shared dependencies are properly configured in both host and remote.
User is undefined
Solution: Ensure your app is wrapped in PlatformProvider and the user is logged in.
Solution: Check that you're calling addAppMenuItems after authentication and with the correct format.
Enable debug mode in development:
// src/App.tsx
if (process.env.NODE_ENV === 'development') {
window.__FRONTFUSE_DEBUG__ = true
}Implement a health check for your app:
// src/health.ts
export const healthCheck = () => ({
status: 'ok',
timestamp: new Date().toISOString(),
version: process.env.npm_package_version || '1.0.0',
})
// Expose via HTTP endpoint if needed- Documentation: https://docs.frontfuse.dev
- GitHub Issues: https://github.com/your-org/frontfuse/issues
- Discord Community: https://discord.gg/frontfuse
- Email Support: support@frontfuse.dev
We welcome contributions! Please see our Contributing Guide for details.
FrontFuse is licensed under the MIT License. See LICENSE for details.