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

12 KiB

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

// package.json
{
  "dependencies": {
    "@c2s/payload-contracts": "github:complexcaresolutions/payload-contracts#main"
  }
}

Imports

// 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

// 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

// 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

// 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:

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

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

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

# .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:

// 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:

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

// 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

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

ssh frontend@10.10.181.104

Projekt starten

cd ~/frontend.porwoll.de
pnpm dev
# Läuft auf Port 3000 → https://porwoll-dev.porwoll.tech

AI-Tools

claude      # Claude Code CLI (v2.1.37)
codex       # Codex CLI
gemini      # Gemini CLI

ESLint-Konfiguration

Alle Frontends verwenden die gleiche ESLint-Konfiguration:

// 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