Luigi - The Enterprise Micro Frontend Framework¶
Executive Summary
Luigi is an open-source, enterprise-ready JavaScript framework for building modular web applications using micro frontend architecture. Created by SAP and governed by the NeoNephos Foundation, Luigi enables independent teams to develop, deploy, and maintain UI components using different technologies while providing a unified user experience through consistent navigation, authentication, and communication patterns.
Project Origin & Governance¶
Open Source Enterprise Framework
Repository: github.com/SAP/luigi Original Creator: SAP SE Current Governance: NeoNephos Foundation (The Linux Foundation Europe) License: Apache 2.0 Funding: European Union NextGenerationEU program
History & Evolution¶
Luigi was originally developed by SAP as an internal solution for building modular administrative interfaces across their product portfolio. Recognizing the broader industry need for enterprise-grade micro frontend solutions, SAP open-sourced Luigi under the Apache 2.0 license.
Key Milestones:
- Initial Release: SAP open-sources Luigi
- Community Growth: 903+ GitHub stars, 177+ forks, 64+ contributors
- Governance Transfer: Project moved to NeoNephos Foundation under Linux Foundation Europe
- Production Adoption: Used by SAP, emporix, and other enterprises
- Ecosystem Integration: Became foundation for OpenMFP and Platform Mesh
NeoNephos Foundation¶
Linux Foundation Europe Initiative
Luigi is governed by the NeoNephos Foundation, which operates under The Linux Foundation Europe and focuses on advancing open-source projects aligned with European digital sovereignty objectives under the IPCEI-CIS (Important Projects of Common European Interest - Cloud Infrastructure and Services) program.
Sister Projects under NeoNephos:
- OpenMFP - Configuration-driven micro frontend platform built on Luigi
- Platform Mesh - Kubernetes-native platform using OpenMFP/Luigi
- Gardener - Kubernetes cluster management
- IronCore - Cloud infrastructure
- Garden Linux - Container-optimized OS
Community & Contributing¶
Active Open Source Community
Luigi maintains an active, welcoming community with multiple channels for collaboration and support.
Communication Channels¶
GitHub:
- Repository: github.com/SAP/luigi
- Discussions: Q&A, ideas, and general discussion
- Issues: 36+ open issues, bug reports, feature requests
- Pull Requests: 11+ active PRs
Real-Time Communication:
- Slack Community: Join for updates, questions, and collaboration
- YouTube Channel: youtube.com/channel/UC5WsYsHapDlg2K3iXS4n4AQ
Documentation:
- Website: luigi-project.io
- Docs: docs.luigi-project.io
- Interactive Playground: fiddle.luigi-project.io
How to Contribute¶
Contribution Guidelines
Getting Started:
- Read
CONTRIBUTING.mdin the repository - Check open issues for good first issues
- Join Slack to discuss your contribution
- Fork the repository and create a branch
- Follow code standards (Prettier, ESLint)
- Write tests for new features
- Submit PR with clear description
Development Setup:
# Clone repository
git clone https://github.com/SAP/luigi.git
cd luigi
# Install dependencies (monorepo)
npm run bootstrap
# Run tests
npm test
# Start development server
npm run simpledev
Quality Standards:
- Code Formatting: Prettier with husky Git hooks
- Testing: Unit tests, E2E tests with Cypress, backward compatibility tests
- Security: HTTPS-only, CSP headers, CORS validation, origin allowlists
- Documentation: Update docs for new features
- Licensing: REUSE tool for third-party component tracking
Overview¶
What is Luigi?
Luigi is a JavaScript framework that enables you to create administrative user interfaces driven by local and distributed views (micro frontends). It breaks down big frontend monoliths into smaller, independent chunks that can be developed by separate teams using different technologies.
Core Problems Solved¶
Monolithic Frontend Challenges:
- Technology Lock-in: Entire app must use same framework
- Team Bottlenecks: Multiple teams competing for deployment slots
- Slow Development: Changes require full app rebuild and redeployment
- Scalability Issues: Large codebases become unmaintainable
- Version Conflicts: Dependency conflicts across features
Luigi's Solutions:
- Technology Agnostic: Mix Angular, React, Vue, UI5, Svelte, or plain JavaScript
- Independent Deployment: Teams deploy their micro frontends separately
- Modular Architecture: Applications decompose into clear functional modules
- Unified UX: Consistent navigation, auth, and communication despite different technologies
- Secure Communication: PostMessage API with origin validation and CSP
Key Benefits¶
Enterprise-Grade Features
For Development Teams:
- Framework Freedom: Use best tool for each job
- Team Autonomy: Independent development and deployment
- Faster Iterations: No coordination needed for releases
- Easier Testing: Test micro frontends in isolation
- Simpler Maintenance: Smaller codebases, clearer ownership
For Organizations:
- Reduced Risk: Gradual migration from monoliths
- Better Scalability: Scale teams and features independently
- Future-Proof: Easy to adopt new technologies
- Cost Efficiency: Reuse components across applications
- Talent Flexibility: Hire specialists in different frameworks
For Users:
- Consistent Experience: Unified navigation and authentication
- Better Performance: Lazy-load features on demand
- Rich Functionality: Best-of-breed components from different teams
- Fast Load Times: Optimized bundle sizes
Architecture¶
Luigi consists of two primary components that work together to create a micro frontend application:
┌────────────────────────────────────────────────────────────────────┐
│ Luigi Core │
│ (Shell Application) │
│ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ Configuration Layer │ │
│ │ - Navigation Structure (nodes tree) │ │
│ │ - Authentication Settings (auth providers) │ │
│ │ - Authorization Rules (permissions) │ │
│ │ - General Settings (theme, header, footer) │ │
│ │ - Routing Configuration (hash/path based) │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ Core Components │ │
│ │ │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ Top Nav │ │ Header │ │ User Menu │ │ │
│ │ │ (Level 1) │ │ (Logo/Title)│ │ (Profile) │ │ │
│ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │
│ │ │ │
│ │ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ Side Nav │ │ Tab Nav │ │ │
│ │ │ (Children) │ │ (Tabs) │ │ │
│ │ └──────────────┘ └──────────────┘ │ │
│ │ │ │
│ │ ┌──────────────────────────────────────────────────┐ │ │
│ │ │ Main Content Area (Iframe Container) │ │ │
│ │ │ │ │ │
│ │ │ ┌─────────────────────────────────────────┐ │ │ │
│ │ │ │ Micro Frontend A (iframe) │ │ │ │
│ │ │ │ - Isolated execution context │ │ │ │
│ │ │ │ - Secure postMessage communication │ │ │ │
│ │ │ │ - Luigi Client API integration │ │ │ │
│ │ │ └─────────────────────────────────────────┘ │ │ │
│ │ └──────────────────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ PostMessage Communication Layer │ │
│ │ - Origin validation │ │
│ │ - Message serialization/deserialization │ │
│ │ - Context injection │ │
│ │ - Event routing │ │
│ └────────────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────────────┘
↕ (PostMessage API)
┌────────────────────────────────────────────────────────────────────┐
│ Luigi Client │
│ (Embedded in each Micro Frontend) │
│ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ Client API │ │
│ │ │ │
│ │ - linkManager: Navigation control │ │
│ │ - uxManager: UI interactions (alerts, modals) │ │
│ │ - lifecycleManager: Initialization, context updates │ │
│ │ - storageManager: Cross-MFE persistent storage │ │
│ │ - getContext(): Access global & node context │ │
│ │ - sendCustomMessage(): Custom communication │ │
│ │ - getToken(): Access auth token │ │
│ └────────────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────────────┘
Luigi Core (Shell Application)¶
The Container Application
Technology: Svelte-based framework (but framework-agnostic from configuration perspective)
Responsibilities:
- Navigation Management: Render top nav, side nav, tabs based on configuration
- Routing: Handle URL changes, deep linking, browser history
- Authentication: Integrate with OAuth2, OIDC, or custom auth providers
- Authorization: Check permissions, hide/show navigation nodes
- Context Management: Inject global and node-specific context into micro frontends
- Lifecycle Management: Load/unload micro frontends, preserve views, handle modals/drawers
- Theming: Apply themes, pass CSS variables to micro frontends
- Localization: Translate navigation labels, manage locale switching
Installation:
Luigi Client (Micro Frontend Library)¶
The Communication Bridge
Technology: Framework-agnostic JavaScript library
Responsibilities:
- Initialization: Handshake with Luigi Core on load
- Context Access: Receive global context (user, permissions, entities)
- Navigation API: Programmatically navigate between micro frontends
- UX Integration: Show alerts, modals, loading indicators via Luigi shell
- Storage API: Cross-micro-frontend persistent storage
- Event Communication: Send/receive custom messages to other micro frontends
- Token Access: Retrieve authentication tokens for API calls
Installation:
Usage:
// ES6
import LuigiClient from '@luigi-project/client';
// CommonJS
const LuigiClient = require('@luigi-project/client');
// Global (no bundler)
window.LuigiClient
Communication Flow¶
User clicks navigation item
↓
Luigi Core routes to URL: /products/123
↓
Luigi Core identifies navigation node
↓
Luigi Core creates iframe with viewUrl
↓
Micro Frontend loads in iframe
↓
Luigi Client initializes in micro frontend
↓
Luigi Client sends init message via postMessage
↓
Luigi Core validates origin
↓
Luigi Core responds with context:
{
productId: "123",
user: { name: "Alice", roles: ["admin"] },
theme: "dark",
locale: "en"
}
↓
Luigi Client triggers init listeners
↓
Micro Frontend receives context and renders
↓
User clicks "Edit Product" button
↓
Micro Frontend calls LuigiClient.linkManager().navigate('/products/123/edit')
↓
Luigi Client sends navigate message via postMessage
↓
Luigi Core validates and routes to /products/123/edit
↓
New micro frontend loads with updated context
Core Concepts¶
1. Navigation Nodes¶
Tree-Based Navigation Structure
Navigation in Luigi is defined as a tree of nodes. Each node represents a route and can have child nodes.
Node Structure:
{
pathSegment: 'products', // URL segment
label: 'Products', // Display name
icon: 'product', // Icon identifier
viewUrl: '/products.html', // Micro frontend URL
children: [ // Child nodes
{
pathSegment: ':id', // Dynamic segment
label: 'Product Details',
viewUrl: '/product-details.html',
context: { // Node-specific context
currentProduct: ':id'
}
}
]
}
Navigation Types:
- Top Navigation: First-level nodes (horizontal menu bar)
- Side Navigation: Children of active top node (vertical sidebar)
- Tab Navigation: Tabs within a specific section
- Context Navigation: Nodes that appear based on entity context
Dynamic Path Segments:
2. View URLs¶
Micro Frontend Loading
Each navigation node has a viewUrl that points to the micro frontend to load.
Absolute URLs:
Relative URLs (resolved against configuration):
Dynamic URLs (with context variables):
3. Context¶
Shared State Across Micro Frontends
Context is the mechanism for passing data from Luigi Core to micro frontends.
Global Context: Available to all micro frontends
Luigi.setConfig({
globalContext: {
user: { name: 'Alice', email: 'alice@example.com' },
tenant: 'acme-corp',
theme: 'dark'
}
});
Node Context: Specific to a navigation node
Accessing Context in Micro Frontend:
LuigiClient.addInitListener((context) => {
console.log(context.user); // Global context
console.log(context.productId); // Node context
});
// Or get current context anytime
const context = LuigiClient.getContext();
4. Categories & Grouping¶
Organize Navigation Items
Categories group related navigation nodes.
Top Navigation Dropdown:
{
pathSegment: 'settings',
label: 'Settings',
category: 'admin', // Group in dropdown
icon: 'settings'
}
Side Navigation Collapsible Sections:
{
pathSegment: 'overview',
label: 'Overview',
category: {
label: 'Analytics',
icon: 'bar-chart',
collapsible: true
}
}
Subcategories (Luigi 2.23.0+):
5. View Groups & Preserved Views¶
Performance Optimization
View Groups allow multiple micro frontends to share the same iframe, reducing load times.
{
pathSegment: 'products',
viewGroup: 'productsGroup',
children: [
{
pathSegment: 'list',
viewGroup: 'productsGroup' // Reuses same iframe
},
{
pathSegment: ':id',
viewGroup: 'productsGroup' // Reuses same iframe
}
]
}
Preserved Views keep micro frontends in memory when navigating away:
{
pathSegment: 'product',
context: {
id: ':id'
},
preserveView: true // Don't destroy iframe when navigating away
}
Users can return to preserved views with:
6. Authentication & Authorization¶
Security Configuration
Authentication Providers: - OpenID Connect (OIDC) - OAuth2 Implicit Grant - Custom Provider
OIDC Example:
Luigi.setConfig({
auth: {
use: 'openIdConnect',
openIdConnect: {
authority: 'https://auth.example.com',
client_id: 'my-app',
scope: 'openid profile email',
automaticSilentRenew: true
}
}
});
Authorization Rules:
{
pathSegment: 'admin',
label: 'Admin Panel',
// Only visible to users with 'admin' permission
nodeAccessibilityResolver: (nodeConfig, context) => {
return context.user.roles.includes('admin');
}
}
Anonymous Access:
Luigi Core API¶
Navigation Configuration¶
Basic Configuration:
Luigi.setConfig({
navigation: {
nodes: [
{
pathSegment: 'home',
label: 'Home',
icon: 'home',
viewUrl: '/home.html'
},
{
pathSegment: 'products',
label: 'Products',
icon: 'product',
viewUrl: '/products.html',
children: [
{
pathSegment: ':id',
viewUrl: '/product-details.html',
context: {
productId: ':id'
}
}
]
}
]
},
routing: {
useHashRouting: true // Use # in URLs (default)
}
});
General Settings¶
Header Configuration:
Luigi.setConfig({
settings: {
header: {
logo: '/assets/logo.png',
title: 'My Application',
favicon: '/assets/favicon.ico'
},
hideNavigation: false, // Show/hide all navigation
responsiveNavigation: 'simple' // 'simple' | 'Fiori3' | 'semiCollapsible'
}
});
Theme Configuration:
Luigi.setConfig({
settings: {
theming: {
themes: [
{ id: 'light', name: 'Light Theme' },
{ id: 'dark', name: 'Dark Theme' }
],
defaultTheme: 'light'
}
}
});
Footer Configuration:
Luigi.setConfig({
settings: {
footer: {
text: '© 2026 My Company',
links: [
{ label: 'Privacy', url: '/privacy' },
{ label: 'Terms', url: '/terms' }
]
}
}
});
Advanced Features¶
Iframe Interceptor (modify iframe before creation):
Luigi.setConfig({
settings: {
iframeCreationInterceptor: (iframe, viewUrl, nodeParams) => {
iframe.sandbox = 'allow-scripts allow-same-origin';
iframe.title = 'Micro Frontend';
return iframe;
}
}
});
Custom Alert Handler:
Luigi.setConfig({
settings: {
customAlertHandler: (settings) => {
// Use custom UI library for alerts
myCustomAlert(settings.text, settings.type);
}
}
});
Luigi Client API¶
Lifecycle Management¶
Initialization:
import LuigiClient from '@luigi-project/client';
// Add init listener (called when Luigi is ready)
LuigiClient.addInitListener((context, origin) => {
console.log('Luigi initialized');
console.log('Context:', context);
console.log('Origin:', origin);
// Initialize your micro frontend with context
initApp(context);
});
// Check if Luigi is initialized
if (LuigiClient.isLuigiClientInitialized()) {
const context = LuigiClient.getContext();
}
Context Updates:
// Listen for context updates (e.g., when navigating within view group)
LuigiClient.addContextUpdateListener((context) => {
console.log('Context updated:', context);
updateApp(context);
});
Inactive State (when view is preserved but not visible):
LuigiClient.addInactiveListener(() => {
console.log('Micro frontend is now inactive');
pauseDataPolling();
});
Link Manager (Navigation)¶
Basic Navigation:
// Navigate to absolute path
LuigiClient.linkManager().navigate('/products/123');
// Navigate to relative path
LuigiClient.linkManager().navigate('details'); // Appends to current path
// Navigate with parameters
LuigiClient.linkManager()
.withParams({ view: 'edit', mode: 'advanced' })
.navigate('/products/123');
Contextual Navigation:
// Navigate from parent context
LuigiClient.linkManager()
.fromContext('product')
.navigate('/analytics');
// Navigate from closest context
LuigiClient.linkManager()
.fromClosestContext()
.navigate('settings');
Go Back:
// Navigate to previous view
LuigiClient.linkManager().goBack();
// Go back with parameters
LuigiClient.linkManager().goBack({ refreshed: true });
Modal Navigation:
// Open in modal
LuigiClient.linkManager()
.withParams({ mode: 'edit' })
.openAsModal('/products/123/edit', { title: 'Edit Product', size: 'l' });
// Close modal
LuigiClient.linkManager().modal().close();
Split View:
// Open split view
LuigiClient.linkManager()
.openAsSplitView('/products/123/preview', { size: 40, collapsed: false });
// Collapse/expand split view
LuigiClient.linkManager().splitView().collapse();
LuigiClient.linkManager().splitView().expand();
// Close split view
LuigiClient.linkManager().splitView().close();
Drawer:
// Open drawer
LuigiClient.linkManager()
.openAsDrawer('/notifications', { size: 's', backdrop: true });
// Close drawer
LuigiClient.linkManager().drawer().close();
Intent-Based Navigation (semantic navigation):
UX Manager (UI Interactions)¶
Loading Indicators:
// Show loading indicator
LuigiClient.uxManager().showLoadingIndicator();
// Hide loading indicator
LuigiClient.uxManager().hideLoadingIndicator();
Alerts:
// Show success alert
LuigiClient.uxManager().showAlert({
text: 'Product saved successfully',
type: 'success', // 'info' | 'success' | 'warning' | 'error'
closeAfter: 3000 // Auto-close after 3 seconds
});
// Alert with HTML
LuigiClient.uxManager().showAlert({
text: 'Click <a href="/help">here</a> for help',
type: 'info'
});
Confirmation Modals:
LuigiClient.uxManager()
.showConfirmationModal({
header: 'Delete Product',
body: 'Are you sure you want to delete this product?',
buttonConfirm: 'Delete',
buttonDismiss: 'Cancel'
})
.then(() => {
// User clicked "Delete"
deleteProduct();
})
.catch(() => {
// User clicked "Cancel"
});
Document Title:
Locale & Theme:
// Get current locale
const locale = LuigiClient.uxManager().getCurrentLocale(); // 'en', 'de', etc.
// Get current theme
const theme = LuigiClient.uxManager().getCurrentTheme(); // 'light', 'dark', etc.
Backdrop:
// Show backdrop (overlay)
LuigiClient.uxManager().showBackdrop();
// Hide backdrop
LuigiClient.uxManager().hideBackdrop();
Dirty Status (unsaved changes):
// Mark form as dirty (warns user before navigating away)
LuigiClient.uxManager().setDirtyStatus(true);
// Clear dirty status
LuigiClient.uxManager().setDirtyStatus(false);
Storage Manager (Cross-MFE Storage)¶
Set & Get Items:
// Store value (returns Promise)
await LuigiClient.storageManager().setItem('user-preference', 'dark-theme');
// Retrieve value (returns Promise)
const preference = await LuigiClient.storageManager().getItem('user-preference');
Remove Items:
// Remove single item
await LuigiClient.storageManager().removeItem('user-preference');
// Clear all storage
await LuigiClient.storageManager().clear();
Check Existence:
// Check if key exists
const exists = await LuigiClient.storageManager().has('user-preference');
// Get all keys
const allKeys = await LuigiClient.storageManager().getAllKeys();
Custom Messages¶
Send Messages:
// Send custom message to Luigi Core
LuigiClient.sendCustomMessage({
id: 'my-event',
data: { productId: '123', action: 'refresh' }
});
Receive Messages:
// Listen for custom messages from Luigi Core
LuigiClient.addCustomMessageListener('refresh-data', (data) => {
console.log('Received refresh command:', data);
reloadData();
});
Authentication¶
Get Token:
// Retrieve current auth token
const token = LuigiClient.getToken();
// Use token in API calls
fetch('/api/products', {
headers: {
'Authorization': `Bearer ${token}`
}
});
Path & Node Parameters¶
Get Node Parameters:
Get Path Parameters:
// Get path parameters (from URL, e.g., :id)
const pathParams = LuigiClient.getPathParams();
console.log(pathParams.productId); // '123' from /products/123
Get Search Parameters (query string):
// Get search/query parameters
const searchParams = LuigiClient.getSearchParams();
console.log(searchParams.view); // 'edit' from ?view=edit
Creating a Luigi Application¶
1. Core Application Setup¶
Install Dependencies:
HTML Setup (index.html):
<!DOCTYPE html>
<html>
<head>
<title>My Luigi App</title>
<link rel="stylesheet" href="/luigi-core/luigi.css">
</head>
<body>
<script src="/luigi-core/luigi.js"></script>
<script src="/luigi-config.js"></script>
</body>
</html>
Configuration (luigi-config.js):
Luigi.setConfig({
navigation: {
nodes: [
{
pathSegment: 'home',
label: 'Home',
icon: 'home',
viewUrl: 'https://my-microfrontend.com/home.html',
children: [
{
pathSegment: 'overview',
label: 'Overview',
viewUrl: 'https://my-microfrontend.com/overview.html'
}
]
}
]
},
routing: {
useHashRouting: true
},
settings: {
header: {
logo: '/logo.png',
title: 'My Application'
},
responsiveNavigation: 'simple'
}
});
2. Micro Frontend Setup¶
Install Luigi Client:
Initialize in Your App:
import LuigiClient from '@luigi-project/client';
// Wait for Luigi to initialize
LuigiClient.addInitListener((context) => {
console.log('Initialized with context:', context);
// Access user info
const user = context.user;
// Access custom context
const productId = context.productId;
// Initialize your application
renderApp(context);
});
Navigation Example:
// In your UI component
function navigateToProduct(productId) {
LuigiClient.linkManager().navigate(`/products/${productId}`);
}
function showEditModal(productId) {
LuigiClient.linkManager()
.openAsModal(`/products/${productId}/edit`, {
title: 'Edit Product',
size: 'l'
});
}
3. Example: Angular Micro Frontend¶
import { Component, OnInit } from '@angular/core';
import LuigiClient from '@luigi-project/client';
@Component({
selector: 'app-product-list',
template: `
<div *ngIf="user">
<h1>Products for {{ user.name }}</h1>
<button (click)="createProduct()">Create Product</button>
</div>
`
})
export class ProductListComponent implements OnInit {
user: any;
ngOnInit() {
LuigiClient.addInitListener((context) => {
this.user = context.user;
});
}
createProduct() {
LuigiClient.linkManager()
.openAsModal('/products/new', {
title: 'Create Product',
size: 'm'
});
}
}
4. Example: React Micro Frontend¶
import React, { useEffect, useState } from 'react';
import LuigiClient from '@luigi-project/client';
function ProductDetails() {
const [product, setProduct] = useState(null);
const [productId, setProductId] = useState(null);
useEffect(() => {
LuigiClient.addInitListener((context) => {
setProductId(context.productId);
fetchProduct(context.productId);
});
}, []);
const fetchProduct = async (id) => {
const token = LuigiClient.getToken();
const response = await fetch(`/api/products/${id}`, {
headers: { 'Authorization': `Bearer ${token}` }
});
setProduct(await response.json());
};
const handleEdit = () => {
LuigiClient.linkManager().navigate('edit');
};
return (
<div>
{product && (
<>
<h1>{product.name}</h1>
<button onClick={handleEdit}>Edit</button>
</>
)}
</div>
);
}
export default ProductDetails;
Authentication Integration¶
OpenID Connect (OIDC)¶
Luigi.setConfig({
auth: {
use: 'openIdConnect',
openIdConnect: {
authority: 'https://auth.example.com',
client_id: 'my-app-client-id',
scope: 'openid profile email',
redirect_uri: 'https://my-app.com/callback',
post_logout_redirect_uri: 'https://my-app.com',
automaticSilentRenew: true,
accessTokenExpiringNotificationTime: 60,
// Remove sensitive data before storage (GDPR compliance)
profileStorageInterceptorFn: (profile) => {
delete profile.email;
return profile;
}
},
disableAutoLogin: false,
storage: 'localStorage' // 'localStorage' | 'sessionStorage' | 'none'
}
});
OAuth2 Implicit Grant¶
Luigi.setConfig({
auth: {
use: 'oAuth2ImplicitGrant',
oAuth2ImplicitGrant: {
authorizeUrl: 'https://auth.example.com/authorize',
logoutUrl: 'https://auth.example.com/logout',
oAuthData: {
client_id: 'my-app',
redirect_uri: 'https://my-app.com/callback',
scope: 'read write'
}
}
}
});
Custom Authentication Provider¶
class MyCustomAuthProvider {
login() {
// Custom login logic
return new Promise((resolve, reject) => {
// Perform authentication
const token = authenticateUser();
resolve(token);
});
}
logout() {
// Custom logout logic
clearUserSession();
}
setTokenExpirationAction() {
// Handle token expiration
}
setLogoutAction() {
// Handle logout
}
userInfo() {
// Return user information
return getUserInfo();
}
generateNonce() {
// Generate security nonce
return crypto.randomUUID();
}
}
Luigi.setConfig({
auth: {
use: 'myCustomAuth',
myCustomAuth: new MyCustomAuthProvider()
}
});
Best Practices¶
Production-Ready Patterns
1. Security Best Practices¶
Always Use HTTPS:
// Micro frontends must be served over HTTPS in production
viewUrl: 'https://secure-microfrontend.com/app'
Implement Content Security Policy:
<meta http-equiv="Content-Security-Policy"
content="default-src 'self';
frame-src https://trusted-domain.com;
script-src 'self' 'unsafe-inline';">
Validate Origins:
// In your micro frontend
LuigiClient.addInitListener((context, origin) => {
// Verify Luigi Core origin
const allowedOrigins = ['https://my-app.com', 'https://staging.my-app.com'];
if (!allowedOrigins.includes(origin)) {
console.error('Untrusted origin:', origin);
return;
}
initApp(context);
});
Restrict Iframe Permissions:
{
pathSegment: 'external',
viewUrl: 'https://external.com',
iframe: {
sandbox: 'allow-scripts allow-same-origin' // Minimal permissions
}
}
2. Performance Optimization¶
Use View Groups:
// Reuse iframes for related views
{
pathSegment: 'products',
viewGroup: 'productManagement',
children: [
{ pathSegment: 'list', viewGroup: 'productManagement' },
{ pathSegment: ':id', viewGroup: 'productManagement' }
]
}
Lazy Load Navigation Nodes:
{
pathSegment: 'reports',
label: 'Reports',
// Load children dynamically when accessed
children: () => {
return fetch('/api/report-configs')
.then(res => res.json())
.then(configs => configs.map(c => ({
pathSegment: c.id,
label: c.name,
viewUrl: c.url
})));
}
}
Preserve Views Strategically:
// Only preserve expensive views
{
pathSegment: 'dashboard',
viewUrl: '/dashboard',
preserveView: true // Keep in memory for fast return
}
3. Context Design¶
Keep Global Context Lean:
// Good: Only essential global data
globalContext: {
user: { id: '123', name: 'Alice' },
tenant: 'acme',
locale: 'en'
}
// Bad: Too much data
globalContext: {
user: { ...everythingAboutUser },
allProducts: [...],
allOrders: [...]
}
Use Node Context for Specifics:
4. Navigation Design¶
Keep Navigation Shallow:
Use Categories for Organization:
// Group related items in dropdowns/sections
{
pathSegment: 'users',
category: 'admin',
label: 'User Management'
},
{
pathSegment: 'roles',
category: 'admin',
label: 'Role Management'
}
5. Error Handling¶
Handle Missing Context:
LuigiClient.addInitListener((context) => {
if (!context.productId) {
LuigiClient.uxManager().showAlert({
text: 'Product ID is required',
type: 'error'
});
LuigiClient.linkManager().navigate('/products');
return;
}
loadProduct(context.productId);
});
Handle Failed Navigation:
LuigiClient.linkManager()
.navigate('/products/123')
.catch((error) => {
console.error('Navigation failed:', error);
LuigiClient.uxManager().showAlert({
text: 'Failed to navigate',
type: 'error'
});
});
6. Testing¶
Unit Test Navigation Logic:
// Mock Luigi Client
jest.mock('@luigi-project/client');
test('navigates to product details', () => {
const navigate = jest.fn();
LuigiClient.linkManager.mockReturnValue({ navigate });
viewProductDetails('123');
expect(navigate).toHaveBeenCalledWith('/products/123');
});
E2E Test with Cypress:
describe('Product Navigation', () => {
it('should navigate to product details', () => {
cy.visit('/');
cy.get('[data-testid="product-link"]').click();
cy.url().should('include', '/products/123');
cy.get('h1').should('contain', 'Product Details');
});
});
Common Pitfalls¶
Pitfall 1: Direct URL Manipulation¶
Don't Bypass Luigi Navigation
Problem: Directly manipulating window.location breaks Luigi's navigation state
// ❌ Bad
window.location.href = '/products/123';
// ✅ Good
LuigiClient.linkManager().navigate('/products/123');
Pitfall 2: Not Validating Origins¶
Always Verify Message Origins
Problem: Accepting messages from untrusted origins creates security vulnerabilities
// ❌ Bad
LuigiClient.addInitListener((context) => {
initApp(context); // No origin check
});
// ✅ Good
LuigiClient.addInitListener((context, origin) => {
if (origin !== 'https://my-app.com') {
console.error('Untrusted origin');
return;
}
initApp(context);
});
Pitfall 3: Hardcoding Base URLs¶
Use Relative Paths
Problem: Hardcoded URLs break in different environments
// ❌ Bad
viewUrl: 'https://production.com/products'
// ✅ Good
viewUrl: '/products' // Resolved against base URL
Pitfall 4: Synchronous Assumptions¶
Luigi Client is Asynchronous
Problem: Assuming Luigi Client methods are synchronous
// ❌ Bad
const context = LuigiClient.getContext();
console.log(context.user); // May be undefined
// ✅ Good
LuigiClient.addInitListener((context) => {
console.log(context.user); // Guaranteed to be available
});
Pitfall 5: Missing Loading States¶
Show Loading Indicators
Problem: Users see blank screens during data fetching
// ✅ Good
LuigiClient.addInitListener(async (context) => {
LuigiClient.uxManager().showLoadingIndicator();
try {
const data = await fetchData(context.productId);
renderApp(data);
} finally {
LuigiClient.uxManager().hideLoadingIndicator();
}
});
Pitfall 6: Over-Preserving Views¶
Memory Leaks
Problem: Preserving too many views consumes memory
// ❌ Bad: Every view preserved
{
pathSegment: 'products',
preserveView: true,
children: [
{ pathSegment: 'list', preserveView: true },
{ pathSegment: ':id', preserveView: true },
{ pathSegment: 'analytics', preserveView: true }
]
}
// ✅ Good: Only preserve expensive views
{
pathSegment: 'dashboard',
preserveView: true // Only dashboard is complex/expensive
}
Luigi Plugins¶
Luigi supports plugins to extend functionality:
OAuth2 Plugin¶
Luigi.setConfig({
auth: {
use: 'oAuth2ImplicitGrant',
oAuth2ImplicitGrant: {
// ... OAuth2 config
}
}
});
OIDC Plugin¶
Built into Luigi Core, no separate installation needed.
Authorization Helpers Plugin¶
For advanced authorization rules and permission management.
Integration with OpenMFP and Platform Mesh¶
Luigi as Foundation
OpenMFP builds on top of Luigi by providing:
- Configuration-driven setup (content-configuration.json)
- Higher-level abstractions for common patterns
- Kubernetes-native integration
- Simplified developer experience
Platform Mesh uses OpenMFP (which uses Luigi) to create:
- Kubernetes-native portal with Extension Manager
- Entity-based navigation (Organizations → Accounts → Projects)
- Dynamic extension discovery from CRDs
- Integration with Security Operator for fine-grained permissions
Relationship:
Luigi (Core Framework)
↓
OpenMFP (Configuration Abstraction)
↓
Platform Mesh (Kubernetes-Native Portal)
When to Use Each:
- Luigi directly: Maximum flexibility, custom requirements, non-OpenMFP environments
- OpenMFP: Simplified configuration, common patterns, community standards
- Platform Mesh: Kubernetes environment, need Extension Manager, entity model required
Technical Stack¶
Technologies Used
Luigi Core:
- Framework: Svelte
- Languages: JavaScript (56.8%), TypeScript (16.4%), Svelte (12.8%), HTML (9.6%), SCSS (2.5%)
- Build: Rollup
- Testing: Mocha, Chai, Cypress
Luigi Client:
- Language: JavaScript/TypeScript
- Module Formats: ES6, CommonJS, UMD
- Size: ~20KB minified
Repository:
- Monorepo: Managed with custom scripts
- Bootstrap:
npm run bootstrap(symbolic linking) - Testing: Unit tests, E2E tests, backward compatibility tests
- CI/CD: GitHub Actions
Troubleshooting¶
Common Issues¶
| Issue | Symptoms | Resolution |
|---|---|---|
| Micro frontend not loading | Blank iframe | Check CORS headers, ensure HTTPS, verify viewUrl is accessible |
| Navigation not working | Clicks don't navigate | Verify pathSegments are unique, check for route conflicts |
| Context is empty | getContext() returns {} |
Use addInitListener, verify Luigi Core is configured |
| Authentication loop | Keeps redirecting to login | Check auth provider config, verify redirect URIs match |
| Postmessage errors | Console errors about origins | Validate origin in micro frontend, check allowlists |
Debug Mode¶
// Enable Luigi debug logs
localStorage.setItem('luigi.debug', 'true');
// Check Luigi version
console.log(LuigiClient.VERSION);
Browser Console¶
// Access Luigi globally (in shell app)
window.Luigi
// Access Luigi Client globally (in micro frontend)
window.LuigiClient
// Check current navigation state
Luigi.navigation().getCurrentNode()
Related Topics¶
- OpenMFP Overview - Configuration-driven platform built on Luigi
- Platform Mesh Portal - How Platform Mesh uses OpenMFP/Luigi
- Extension Manager - Kubernetes operator for micro frontends
Further Resources¶
Official Documentation¶
- Website: luigi-project.io
- Documentation: docs.luigi-project.io
- GitHub: github.com/SAP/luigi
- Playground: fiddle.luigi-project.io
Community¶
- Slack: Luigi Community Slack
- GitHub Discussions: Q&A and feature discussions
- YouTube: Luigi Project Channel
Learning Resources¶
- Getting Started Guide: docs.luigi-project.io/docs/getting-started
- Example Applications: Angular, React, Vue, UI5, Svelte examples in repository
- Tutorials: Shopping app tutorial for beginners
Governance¶
- NeoNephos Foundation: neonephos.org
- Linux Foundation Europe: Parent organization
- Contributing:
CONTRIBUTING.mdin repository - License: Apache 2.0
Sources: