cms.c2sgmbh/docs/anleitungen/FRONTEND.md
Martin Porwoll 9f7a9ad558 docs: update infrastructure, project status, and frontend docs
Add Multi-Server Orchestration (Phase 1-8) to all docs:
- INFRASTRUCTURE.md: Hetzner 1/2 production servers, SSH infrastructure,
  payload-contracts, deployment workflows, port-forwarding
- PROJECT_STATUS.md: Orchestration changelog, production URLs, SSH commands
- FRONTEND.md: payload-contracts usage, CI/CD pipelines, staging/production
  deploy, work order system, ESLint config, updated tenant IDs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 22:49:38 +00:00

464 lines
12 KiB
Markdown

# Frontend-Entwicklung - Payload CMS Multi-Tenant
> **Server:** sv-frontend (LXC 704) - 10.10.181.104
> **Backend API:** https://cms.c2sgmbh.de/api (Production)
> **Shared Types:** `@c2s/payload-contracts` (Git-Dependency)
## Übersicht
Jedes Frontend ist ein separates Next.js-Projekt und nutzt Payload CMS als Headless CMS über die REST-API. Alle Frontends teilen sich TypeScript-Typen und den API-Client über das `payload-contracts` Package.
**Architektur:**
```
CMS (payload-cms) payload-contracts Frontends (Next.js)
━━━━━━━━━━━━━━━━ ━━━━━━━━━━━━━━━━━ ━━━━━━━━━━━━━━━━━━
Collections + Blocks → Shared Types → frontend.porwoll.de
payload-types.ts → API Client → frontend.blogwoman.de
Block Registry → (7 weitere)
```
---
## payload-contracts (Shared Package)
Alle Frontends verwenden `@c2s/payload-contracts` als Git-Dependency für TypeScript-Typen, den API-Client und die Block-Registry.
### Installation
```json
// package.json
{
"dependencies": {
"@c2s/payload-contracts": "github:complexcaresolutions/payload-contracts#main"
}
}
```
### Imports
```typescript
// Types
import type { Page, Post, Media } from '@c2s/payload-contracts/types'
import type { Block, BlockByType } from '@c2s/payload-contracts/types'
// API Client
import { createPayloadClient } from '@c2s/payload-contracts/api-client'
// Block Renderer
import { createBlockRenderer } from '@c2s/payload-contracts/blocks'
// Constants
import { TENANTS } from '@c2s/payload-contracts/constants'
```
### API Client erstellen
```typescript
// src/lib/api.ts
import { createPayloadClient } from '@c2s/payload-contracts/api-client'
export const api = createPayloadClient({
baseUrl: process.env.NEXT_PUBLIC_PAYLOAD_URL!,
tenantId: Number(process.env.NEXT_PUBLIC_TENANT_ID),
})
// Verwendung
const page = await api.pages.getBySlug('home')
const posts = await api.posts.getAll({ limit: 10 })
const nav = await api.navigation.get()
const settings = await api.settings.get()
```
### Block Renderer
```typescript
// src/components/blocks/index.tsx
import { createBlockRenderer } from '@c2s/payload-contracts/blocks'
import { HeroBlock } from './HeroBlock'
import { TextBlock } from './TextBlock'
// ... weitere Block-Imports
export const BlockRenderer = createBlockRenderer({
'hero-block': HeroBlock,
'text-block': TextBlock,
// ... nur die Blocks registrieren, die das Frontend braucht
})
```
### Block-Komponente implementieren
```typescript
// src/components/blocks/HeroBlock.tsx
import type { BlockByType } from '@c2s/payload-contracts/types'
type HeroBlockData = BlockByType<'hero-block'>
interface HeroBlockProps {
block: HeroBlockData
}
export function HeroBlock({ block }: HeroBlockProps) {
return (
<section>
<h1>{block.headline}</h1>
{block.subline && <p>{block.subline}</p>}
</section>
)
}
```
### Types aktualisieren
Wenn sich CMS-Collections oder Blocks ändern:
```bash
# Auf sv-payload:
cd ~/payload-cms && pnpm payload generate:types
cd ~/payload-contracts && pnpm extract
git add -A && git commit -m "types: update from CMS" && git push
# Auf sv-frontend (pro Projekt):
cd ~/frontend.porwoll.de
pnpm update @c2s/payload-contracts
```
---
## Umgebungskonfiguration
### Environment Variables (.env.local)
```env
# API-Endpunkte (PRODUKTION)
NEXT_PUBLIC_PAYLOAD_URL=https://cms.c2sgmbh.de
NEXT_PUBLIC_API_URL=https://cms.c2sgmbh.de/api
# Analytics (optional)
NEXT_PUBLIC_UMAMI_HOST=https://analytics.c2sgmbh.de
NEXT_PUBLIC_UMAMI_WEBSITE_ID=<website-id>
# Tenant-Konfiguration (je nach Projekt)
NEXT_PUBLIC_TENANT_ID=4
NEXT_PUBLIC_TENANT_SLUG=c2s
```
### Tenant-IDs
| ID | Name | Slug | Domain | Status |
|----|------|------|--------|--------|
| 1 | porwoll.de | porwoll | porwoll.de | ✅ Live |
| 4 | Complex Care Solutions GmbH | c2s | complexcaresolutions.de | Geplant |
| 5 | Gunshin | gunshin | gunshin.de | Geplant |
| 9 | BlogWoman | blogwoman | blogwoman.de | ✅ Live |
### Warum Production-Daten?
Die Frontend-Entwicklung verwendet die Produktions-API, um mit echten Inhalten zu arbeiten. SEO-Einstellungen und Cookie-Consent-Konfigurationen werden ebenfalls aus der Produktionsumgebung geladen.
---
## CI/CD Pipeline
### CI (Lint + Build)
Jedes Frontend hat eine GitHub Actions CI-Pipeline, die bei Push auf `develop` und `main` läuft.
```yaml
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [develop, main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with: { node-version: 22, cache: pnpm }
- run: pnpm install --frozen-lockfile
- run: pnpm lint
- run: pnpm build
```
### Staging-Deploy (sv-frontend)
Push auf `develop` → GitHub Actions → SSH via UDM Pro SE Port-Forward → Build auf sv-frontend.
```yaml
# .github/workflows/deploy-staging.yml
# SSH-Credentials als Repository-Secrets: SSH_HOST, SSH_PORT, SSH_USER, SSH_PRIVATE_KEY
```
### Production-Deploy (Plesk)
Push auf `main` → GitHub Webhook → Plesk Git Pull → `pnpm install && pnpm build` → Passenger Restart.
Production-Deployment wird NICHT über GitHub Actions gesteuert, sondern über Plesk Git-Integration mit Webhooks.
### Deployment-Flow
```
develop (sv-frontend) → CI ✅ → Staging-Test (*-dev.porwoll.tech)
main (merge) → CI ✅ → Webhook → Plesk → Production ✅
```
---
## Work-Order-System
Wenn ein neuer Block oder eine neue Collection im CMS erstellt wird, koordiniert das Work-Order-System die Frontend-Implementierung.
### Ablauf
1. **sv-payload:** CMS ändern → Types extrahieren → Work Order schreiben
2. **sv-frontend:** Work Order lesen → `pnpm update @c2s/payload-contracts` → Block implementieren
### Referenz
Vollständige Dokumentation im `payload-contracts` Repo:
- `work-orders/_template.md` — Vorlage
- `scripts/create-work-order.sh` — Work Order erstellen (auf sv-payload)
- `scripts/execute-work-order.sh` — Work Order ausführen (auf sv-frontend)
---
## API-Dokumentation
| Ressource | URL |
|-----------|-----|
| **Swagger UI** | https://cms.c2sgmbh.de/api/docs |
| **OpenAPI JSON** | https://cms.c2sgmbh.de/api/openapi.json |
| **REST API Base** | https://cms.c2sgmbh.de/api |
---
## Frontend-Status
### Live
| Frontend | Production | Blocks | Contracts |
|----------|-----------|--------|-----------|
| porwoll.de | ✅ Hetzner 2 | 9 implementiert | Direkte Types |
| blogwoman.de | ✅ Hetzner 1 | Alle implementiert | Bridge-Pattern (lokale types.ts) |
### Geplant
| Frontend | Server | Priorität |
|----------|--------|-----------|
| complexcaresolutions.de | Hetzner 1 | Hoch |
| caroline-porwoll.com | Hetzner 2 | Mittel |
| caroline-porwoll.de | Hetzner 2 | Mittel |
| gunshin.de | - | Niedrig |
| zweitmeinu.ng | Hetzner 1 | Niedrig |
### Offene Tasks
- [ ] porwoll.de: Fehlende Blocks implementieren (~6 via Work Orders)
- [ ] blogwoman.de: Bridge-Pattern durch direkte Contracts-Imports ersetzen
- [ ] Cookie-Banner implementieren (DSGVO)
- [ ] Newsletter-Formular-Komponente
---
## API-Endpoints
### Collections
| Collection | Endpoint | Beschreibung |
|------------|----------|--------------|
| Pages | `GET /api/pages` | Seiten mit Blocks |
| Posts | `GET /api/posts` | Blog, News, Presse |
| Categories | `GET /api/categories` | Post-Kategorien |
| Testimonials | `GET /api/testimonials` | Kundenbewertungen |
| Team | `GET /api/team` | Team-Mitglieder |
| Services | `GET /api/services` | Leistungen |
| FAQs | `GET /api/faqs` | FAQ-Einträge |
| Portfolios | `GET /api/portfolios` | Portfolio-Projekte |
| Media | `GET /api/media` | Medien/Bilder |
| Videos | `GET /api/videos` | Video-Bibliothek |
| Timelines | `GET /api/timelines` | Chronologische Events |
| Workflows | `GET /api/workflows` | Prozess-Darstellungen |
| Favorites | `GET /api/favorites` | Affiliate-Produkte (BlogWoman) |
| Series | `GET /api/series` | YouTube-Serien (BlogWoman) |
### Site Settings & Navigation (Tenant-isoliert)
| Collection | Endpoint | Beschreibung |
|------------|----------|--------------|
| Site Settings | `GET /api/site-settings?where[tenant][equals]=4` | Logo, Name, Kontakt, Adresse |
| Navigation | `GET /api/navigations?where[tenant][equals]=4` | Menü-Struktur |
| Privacy Policy | `GET /api/privacy-policy-settings?where[tenant][equals]=4` | Datenschutz |
### Spezielle Endpoints
| Endpoint | Methode | Beschreibung |
|----------|---------|--------------|
| `/api/search` | GET | Volltextsuche |
| `/api/search/suggestions` | GET | Auto-Complete |
| `/api/newsletter/subscribe` | POST | Newsletter-Anmeldung |
---
## Tenant-Filterung
Alle Collection-Anfragen sollten nach Tenant gefiltert werden:
```typescript
// Mit payload-contracts API Client (empfohlen):
const posts = await api.posts.getAll({ limit: 10 })
// → Tenant-Filter wird automatisch angewendet
// Manuell:
fetch('https://cms.c2sgmbh.de/api/posts?where[tenant][equals]=4&locale=de')
```
---
## Bild-Optimierung
Media-Objekte enthalten mehrere Größen:
```typescript
interface Media {
url: string // Original
sizes: {
thumbnail: { url, width, height } // 150x150
small: { url, width, height } // 300x300
medium: { url, width, height } // 600x600
large: { url, width, height } // 1200x1200
xlarge: { url, width, height } // 1920x1920
'2k': { url, width, height } // 2560x2560
og: { url, width, height } // 1200x630 (Social)
// + AVIF-Varianten
thumbnail_avif: { url, width, height }
small_avif: { url, width, height }
// ...
}
focalX?: number // Fokuspunkt X (0-100)
focalY?: number // Fokuspunkt Y (0-100)
}
```
---
## Lokalisierung
Unterstützte Locales: `de` (default), `en`
```typescript
// Deutsch (default)
fetch('https://cms.c2sgmbh.de/api/posts?locale=de')
// Englisch
fetch('https://cms.c2sgmbh.de/api/posts?locale=en')
// Fallback: Wenn EN nicht vorhanden, wird DE zurückgegeben
```
---
## Newsletter-Integration
```typescript
const response = await fetch('https://cms.c2sgmbh.de/api/newsletter/subscribe', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: 'user@example.com',
firstName: 'Max', // optional
tenantId: 4, // Pflicht
source: 'footer' // optional: Herkunft
})
})
```
**Flow:** Subscribe → Double Opt-In E-Mail → Bestätigung → Willkommens-E-Mail
---
## Rate-Limits
| Endpoint | Limit |
|----------|-------|
| Öffentliche API | 60/min |
| Suche | 30/min |
| Newsletter | 5/10min |
| Formulare | 5/10min |
---
## Development Server (sv-frontend)
### SSH-Zugang
```bash
ssh frontend@10.10.181.104
```
### Projekt starten
```bash
cd ~/frontend.porwoll.de
pnpm dev
# Läuft auf Port 3000 → https://porwoll-dev.porwoll.tech
```
### AI-Tools
```bash
claude # Claude Code CLI (v2.1.37)
codex # Codex CLI
gemini # Gemini CLI
```
---
## ESLint-Konfiguration
Alle Frontends verwenden die gleiche ESLint-Konfiguration:
```javascript
// eslint.config.mjs
import { defineConfig, globalIgnores } from "eslint/config";
import nextVitals from "eslint-config-next/core-web-vitals";
import nextTs from "eslint-config-next/typescript";
const eslintConfig = defineConfig([
...nextVitals,
...nextTs,
{
rules: {
"@typescript-eslint/no-unused-vars": ["warn", {
argsIgnorePattern: "^_",
varsIgnorePattern: "^_",
destructuredArrayIgnorePattern: "^_",
}],
},
},
globalIgnores([".next/**", "out/**", "build/**", "next-env.d.ts", "server.js"]),
]);
export default eslintConfig;
```
`server.js` wird ignoriert, da es CJS für Phusion Passenger verwendet.
---
## Ressourcen
| Ressource | URL/Pfad |
|-----------|----------|
| Payload CMS Docs | https://payloadcms.com/docs |
| API-Dokumentation | https://cms.c2sgmbh.de/api/docs |
| payload-contracts | https://github.com/complexcaresolutions/payload-contracts |
| Infrastruktur-Docs | `docs/INFRASTRUCTURE.md` |
| Analytics | https://analytics.c2sgmbh.de |
---
*Letzte Aktualisierung: 15.02.2026*