# 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 (

{block.headline}

{block.subline &&

{block.subline}

}
) } ``` ### 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= # 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*