SPA-Architektur
Archiviert / nicht mehr im Monorepo
Die App apps/frontend-spa wurde entfernt. Öffentliche Center-Websites laufen über apps/center-website (Multi-Tenant Next.js) oder externe v0/Vercel-Projekte mit der öffentlichen API. Diese Seite bleibt als historische Referenz; bitte nicht für neue Projekte verwenden.
Entwickler-Guide: Multi-Tenant-SPA-Architektur von CockpitOS (Legacy).
Architektur-Übersicht
Das CockpitOS Frontend SPA ist eine moderne Single-Page-Application für mehrere Shopping Center.
Multi-Tenant-Konzept
Organisation HBB → Theme "hbb-standard" → Center 1, 2, 3...
Organisation SMG → Theme "smg-modern" → Center A, B, C...
URL-Routing-System
Domain-basiert: https://rathaus-galerie-wuppertal.de/shops
Path-basiert: https://preview.cockpit-os.de/rathaus-galerie-wuppertal/shops
Verzeichnis-Struktur
apps/frontend-spa/
├── src/
│ ├── components/
│ │ ├── layout/ # Layout-Komponenten
│ │ ├── blocks/ # Block-Komponenten
│ │ └── pages/ # Seiten-Komponenten
│ ├── lib/
│ │ ├── api/ # API-Layer
│ │ ├── theme/ # Theme-System
│ │ ├── blocks/ # Block-Registry
│ │ └── router/ # Multi-Tenant-Router
│ └── styles/
│ ├── base.css # Base-Styles
│ └── blocks.css # Block-Styles
Theme-System
Theme-Definition:
export const hbbStandardTheme: ThemeConfig = {
id: 'hbb-standard',
name: 'HBB Standard',
organization: 'hbb',
colors: {
primary: '#892831', // HBB-Rot
secondary: '#f5f5f5', // Hellgrau
accent: '#d4af37', // Gold
},
fonts: {
heading: 'Roboto',
body: 'Open Sans',
},
blocks: [
'hbb-hero-section',
'hbb-bento-grid',
'hbb-shop-grid',
],
};
Block-System
Block-Registry:
blockRegistry.register({
id: 'hbb-hero-section',
name: 'HBB Hero Section',
category: 'hero',
themes: ['hbb-standard', 'hbb-modern'],
component: HeroSection,
});
Dynamisches Block-Loading:
async loadBlockComponent(blockId: string) {
if (blockId.startsWith('hbb-')) {
const blockName = this.toPascalCase(blockId.replace('hbb-', ''));
const module = await import(`../../components/blocks/hbb/${blockName}`);
return module[blockName];
}
}
Multi-Tenant-Routing
URL-Resolver:
export function resolveRouteFromURL(): RouteInfo {
const { hostname, pathname } = window.location;
const isPreviewDomain = hostname.includes('preview.cockpit-os.de');
if (isPreviewDomain) {
return resolvePathBasedRoute(pathname);
} else {
return resolveDomainBasedRoute(hostname, pathname);
}
}
API-Integration
API-Client:
export async function apiRequest<T>(endpoint: string): Promise<T> {
const url = `${API_BASE_URL}${endpoint}`;
const response = await fetch(url);
if (!response.ok) {
throw new ApiError(`API Error: ${response.status}`, response.status);
}
return response.json();
}
Responsive Design
Mobile-First-Ansatz:
/* Mobile (default) */
.hbb-bento-grid {
grid-template-columns: 1fr;
}
/* Tablet */
@media (min-width: 768px) {
.hbb-bento-grid {
grid-template-columns: repeat(2, 1fr);
}
}
/* Desktop */
@media (min-width: 1024px) {
.hbb-bento-grid {
grid-template-columns: repeat(3, 1fr);
}
}
Performance-Optimierung
Code-Splitting:
const HeroSection = lazy(() => import('./blocks/hbb/HeroSection'));
Bundle-Optimierung:
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom', 'react-router-dom'],
blocks: ['./src/components/blocks'],
},
},
},
},
});
Development-Workflow
Lokale Entwicklung:
cd apps/frontend-spa
npm install
npm run dev
# → http://localhost:3001/rathaus-galerie-wuppertal
Deployment
Build-Prozess:
npm run build
# → dist/ Verzeichnis mit optimierten Assets
Render.com-Konfiguration:
services:
- type: web
name: frontend-spa
env: node
buildCommand: npm run build
startCommand: npm run preview
envVars:
- key: VITE_API_URL
value: https://dashboard.cockpit-os.de
Nutzungsstatistik: Seitenaufrufe werden anonymisiert erfasst. Im Umami-Dashboard nach diesem Pfad filtern: /en/entwickler-handbuch/frontend-spa/architektur