Zum Hauptinhalt springen

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: /entwickler-handbuch/frontend-spa/architektur