Add documentation and BlogWoman frontend development prompt

- Add API documentation (API_ANLEITUNG.md)
- Add architecture docs (UNIVERSAL_FEATURES.md, Analytics.md)
- Add guides (FRONTEND.md, SEO_ERWEITERUNG.md, styleguide.md)
- Add Planungs-KI prompt template (ANLEITUNG-PLANUNGS-KI-FRONTEND.md)
- Add BlogWoman frontend development prompt with:
  - Tenant-ID 9 configuration
  - Design system based on styleguide
  - BlogWoman-specific blocks (Favorites, Series, VideoEmbed)
  - API patterns with tenant isolation
  - SEO and Analytics integration

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
CCS Admin 2026-01-20 21:24:13 +00:00
parent 4311a1960d
commit dcfc48f5ce
10 changed files with 21510 additions and 0 deletions

1289
docs/api/API_ANLEITUNG.md Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,981 @@
# ANALYTICS-LÖSUNG: Implementierungsübersicht für Payload CMS
*Letzte Aktualisierung: 18. Dezember 2025*
## Kontext
Du entwickelst das Multi-Tenant Payload CMS Backend und Next.js Frontend für 4 Websites. Diese Dokumentation beschreibt die Analytics-Lösung, die in das Frontend integriert werden muss.
**Wichtig:** Frontends verwenden die **Production-API** (cms.c2sgmbh.de) und **Production-Analytics** (analytics.c2sgmbh.de).
---
## Architektur-Übersicht
```
┌─────────────────────────────────────────────────────────────────────────────────────┐
│ ANALYTICS ARCHITEKTUR │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────────┐ │
│ │ OHNE CONSENT (immer aktiv) │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │
│ │ │ UMAMI ANALYTICS │ │ │
│ │ │ │ │ │
│ │ │ Server: sv-analytics (10.10.181.103:3000) │ │ │
│ │ │ Dashboard: http://10.10.181.103:3000 │ │ │
│ │ │ Script: /custom.js (Anti-Adblock) │ │ │
│ │ │ Endpoint: /api/send │ │ │
│ │ │ │ │ │
│ │ │ Features: │ │ │
│ │ │ • Cookieless Tracking (DSGVO-konform ohne Einwilligung) │ │ │
│ │ │ • Pageviews, Sessions, Referrer, UTM-Parameter │ │ │
│ │ │ • Custom Events (Newsletter, Formulare, CTAs, Downloads) │ │ │
│ │ │ • 100% Erfassung aller Besucher │ │ │
│ │ └─────────────────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────────┐ │
│ │ MIT CONSENT (Kategorie: "marketing") │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │
│ │ │ GOOGLE ADS CONVERSION │ │ │
│ │ │ │ │ │
│ │ │ Client-Side (bei Consent): │ │ │
│ │ │ • Google Ads Tag (gtag.js) │ │ │
│ │ │ • Conversion Tracking │ │ │
│ │ │ • Remarketing Audiences │ │ │
│ │ │ │ │ │
│ │ │ Server-Side (immer, anonymisiert): │ │ │
│ │ │ • Google Ads Conversion API │ │ │
│ │ │ • Enhanced Conversions (gehashte E-Mail) │ │ │
│ │ │ • GCLID-basierte Attribution │ │ │
│ │ └─────────────────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │
│ │ │ GOOGLE CONSENT MODE v2 │ │ │
│ │ │ │ │ │
│ │ │ Integration mit bestehendem Orestbida Consent-Banner │ │ │
│ │ │ Kategorie "marketing" steuert: │ │ │
│ │ │ • ad_storage │ │ │
│ │ │ • ad_user_data │ │ │
│ │ │ • ad_personalization │ │ │
│ │ └─────────────────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────────┘
```
---
## Infrastruktur
### Umami Server
| Umgebung | Server | URL | Dashboard |
|----------|--------|-----|-----------|
| **Production** | Hetzner 3 | https://analytics.c2sgmbh.de | https://analytics.c2sgmbh.de |
| **Development** | sv-analytics (LXC 703) | https://umami.porwoll.tech | https://umami.porwoll.tech |
| Eigenschaft | Production | Development |
|-------------|------------|-------------|
| **Tracking Script** | https://analytics.c2sgmbh.de/script.js | https://umami.porwoll.tech/script.js |
| **API Endpoint** | https://analytics.c2sgmbh.de/api/send | https://umami.porwoll.tech/api/send |
| **Datenbank** | umami_db auf Hetzner 3 | umami_db auf sv-postgres |
### Website-IDs (Multi-Tenant)
Die Website-IDs werden in Umami für jeden Tenant erstellt:
```typescript
// src/config/analytics.ts
export const UMAMI_WEBSITE_IDS: Record<string, string> = {
'porwoll.de': 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
'complexcaresolutions.de': 'yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy',
'gunshin.de': 'zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz',
'zweitmeinu.ng': 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa',
}
// Production URL für Frontends
export const UMAMI_HOST = process.env.NEXT_PUBLIC_UMAMI_HOST || 'https://analytics.c2sgmbh.de'
```
---
## Frontend-Integration
### 1. Umami Script Komponente
```typescript
// src/components/analytics/UmamiScript.tsx
'use client'
import Script from 'next/script'
interface UmamiScriptProps {
websiteId: string
host?: string
}
export function UmamiScript({
websiteId,
host = 'https://analytics.c2sgmbh.de' // Production default
}: UmamiScriptProps) {
if (!websiteId) return null
return (
<Script
defer
src={`${host}/script.js`}
data-website-id={websiteId}
data-host-url={host}
strategy="afterInteractive"
/>
)
}
```
### 2. Layout Integration (Multi-Tenant)
```typescript
// src/app/layout.tsx
import { UmamiScript } from '@/components/analytics/UmamiScript'
import { UMAMI_WEBSITE_IDS, UMAMI_HOST } from '@/config/analytics'
import { getCurrentTenant } from '@/lib/tenant'
export default async function RootLayout({
children
}: {
children: React.ReactNode
}) {
const tenant = await getCurrentTenant()
const umamiWebsiteId = UMAMI_WEBSITE_IDS[tenant.domain]
return (
<html lang="de">
<body>
{children}
{/* Umami Analytics - Läuft OHNE Consent (cookieless) */}
{umamiWebsiteId && (
<UmamiScript
websiteId={umamiWebsiteId}
host={UMAMI_HOST}
/>
)}
</body>
</html>
)
}
```
### 3. Analytics Hook für Custom Events
```typescript
// src/hooks/useAnalytics.ts
'use client'
import { useCallback } from 'react'
declare global {
interface Window {
umami?: {
track: (eventName: string, eventData?: Record<string, unknown>) => void
}
gtag?: (...args: unknown[]) => void
CookieConsent?: {
acceptedCategory: (category: string) => boolean
}
}
}
export function useAnalytics() {
/**
* Generisches Event-Tracking
*/
const trackEvent = useCallback((
eventName: string,
eventData?: Record<string, unknown>
) => {
if (typeof window !== 'undefined' && window.umami) {
window.umami.track(eventName, eventData)
}
}, [])
/**
* Newsletter Anmeldung
*/
const trackNewsletterSubscribe = useCallback((source: string = 'unknown') => {
trackEvent('newsletter_subscribe', { source })
}, [trackEvent])
/**
* Newsletter Bestätigung (Double Opt-In)
*/
const trackNewsletterConfirm = useCallback(() => {
trackEvent('newsletter_confirm')
}, [trackEvent])
/**
* Kontaktformular abgesendet
*/
const trackContactFormSubmit = useCallback((formType: string = 'contact') => {
trackEvent('contact_form_submit', { form_type: formType })
}, [trackEvent])
/**
* CTA Klick
*/
const trackCtaClick = useCallback((
ctaName: string,
ctaLocation: string = 'unknown'
) => {
trackEvent('cta_click', { cta_name: ctaName, location: ctaLocation })
}, [trackEvent])
/**
* Download
*/
const trackDownload = useCallback((
fileName: string,
fileType: string = 'unknown'
) => {
trackEvent('download', { file_name: fileName, file_type: fileType })
}, [trackEvent])
/**
* Funnel-Step (für Conversion-Funnels)
*/
const trackFunnelStep = useCallback((
funnelName: string,
stepNumber: number,
stepName: string
) => {
trackEvent('funnel_step', {
funnel: funnelName,
step: stepNumber,
step_name: stepName
})
}, [trackEvent])
/**
* Scroll-Tiefe
*/
const trackScrollDepth = useCallback((depth: number) => {
trackEvent('scroll_depth', { depth_percent: depth })
}, [trackEvent])
/**
* Externer Link Klick
*/
const trackExternalLink = useCallback((url: string) => {
trackEvent('external_link', { url })
}, [trackEvent])
return {
trackEvent,
trackNewsletterSubscribe,
trackNewsletterConfirm,
trackContactFormSubmit,
trackCtaClick,
trackDownload,
trackFunnelStep,
trackScrollDepth,
trackExternalLink,
}
}
```
### 4. Server-Side Event Tracking
```typescript
// src/lib/analytics.server.ts
const UMAMI_HOST = process.env.UMAMI_HOST || 'https://analytics.c2sgmbh.de'
const UMAMI_WEBSITE_ID = process.env.UMAMI_WEBSITE_ID
interface ServerEventParams {
event: string
url: string
websiteId?: string
referrer?: string
data?: Record<string, unknown>
}
/**
* Server-Side Event an Umami senden
* Für Events die im Backend passieren (z.B. Newsletter-Bestätigung)
*/
export async function trackServerEvent(params: ServerEventParams) {
const websiteId = params.websiteId || UMAMI_WEBSITE_ID
if (!websiteId) {
console.warn('[Analytics] No websiteId configured for server-side tracking')
return
}
try {
const response = await fetch(`${UMAMI_HOST}/api/send`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'User-Agent': 'Payload-CMS-Server/1.0',
},
body: JSON.stringify({
type: 'event',
payload: {
website: websiteId,
url: params.url,
referrer: params.referrer || '',
name: params.event,
data: params.data,
},
}),
})
if (!response.ok) {
console.error('[Analytics] Server event failed:', response.status)
}
} catch (error) {
console.error('[Analytics] Server event error:', error)
}
}
```
---
## Komponenten-Beispiele
### Newsletter-Formular mit Tracking
```typescript
// src/components/forms/NewsletterForm.tsx
'use client'
import { useState } from 'react'
import { useAnalytics } from '@/hooks/useAnalytics'
interface NewsletterFormProps {
source?: string // z.B. 'footer', 'hero', 'popup'
}
export function NewsletterForm({ source = 'unknown' }: NewsletterFormProps) {
const [email, setEmail] = useState('')
const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle')
const { trackNewsletterSubscribe, trackFunnelStep } = useAnalytics()
async function handleSubmit(e: React.FormEvent) {
e.preventDefault()
setStatus('loading')
// Funnel-Step tracken
trackFunnelStep('newsletter', 1, 'form_submitted')
try {
const response = await fetch('/api/newsletter/subscribe', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, source }),
})
if (response.ok) {
setStatus('success')
// Erfolg tracken
trackNewsletterSubscribe(source)
trackFunnelStep('newsletter', 2, 'subscription_pending')
} else {
setStatus('error')
}
} catch {
setStatus('error')
}
}
if (status === 'success') {
return (
<div className="p-4 bg-green-50 text-green-800 rounded">
Bitte bestätige deine E-Mail-Adresse.
</div>
)
}
return (
<form onSubmit={handleSubmit} className="flex gap-2">
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="E-Mail-Adresse"
required
className="flex-1 px-4 py-2 border rounded"
/>
<button
type="submit"
disabled={status === 'loading'}
className="px-6 py-2 bg-primary text-white rounded"
>
{status === 'loading' ? 'Lädt...' : 'Anmelden'}
</button>
</form>
)
}
```
### CTA Button mit Tracking
```typescript
// src/components/ui/TrackedButton.tsx
'use client'
import { useAnalytics } from '@/hooks/useAnalytics'
import Link from 'next/link'
interface TrackedButtonProps {
href: string
ctaName: string
location?: string
children: React.ReactNode
className?: string
external?: boolean
}
export function TrackedButton({
href,
ctaName,
location = 'unknown',
children,
className,
external = false,
}: TrackedButtonProps) {
const { trackCtaClick, trackExternalLink } = useAnalytics()
function handleClick() {
trackCtaClick(ctaName, location)
if (external) {
trackExternalLink(href)
}
}
if (external) {
return (
<a
href={href}
target="_blank"
rel="noopener noreferrer"
onClick={handleClick}
className={className}
>
{children}
</a>
)
}
return (
<Link href={href} onClick={handleClick} className={className}>
{children}
</Link>
)
}
```
### Download-Link mit Tracking
```typescript
// src/components/ui/TrackedDownload.tsx
'use client'
import { useAnalytics } from '@/hooks/useAnalytics'
interface TrackedDownloadProps {
href: string
fileName: string
fileType?: string
children: React.ReactNode
className?: string
}
export function TrackedDownload({
href,
fileName,
fileType = 'document',
children,
className,
}: TrackedDownloadProps) {
const { trackDownload } = useAnalytics()
function handleClick() {
trackDownload(fileName, fileType)
}
return (
<a
href={href}
download
onClick={handleClick}
className={className}
>
{children}
</a>
)
}
```
---
## Google Ads Integration
### Consent Mode v2 Komponente
```typescript
// src/components/analytics/GoogleConsentMode.tsx
'use client'
import Script from 'next/script'
import { useEffect } from 'react'
interface GoogleConsentModeProps {
googleAdsId: string
}
export function GoogleConsentMode({ googleAdsId }: GoogleConsentModeProps) {
useEffect(() => {
// Consent-Änderungen von Orestbida abonnieren
window.addEventListener('cc:onConsent', handleConsentChange)
window.addEventListener('cc:onChange', handleConsentChange)
// Initial setzen
updateGoogleConsent()
return () => {
window.removeEventListener('cc:onConsent', handleConsentChange)
window.removeEventListener('cc:onChange', handleConsentChange)
}
}, [])
function handleConsentChange() {
updateGoogleConsent()
}
function updateGoogleConsent() {
if (typeof window.gtag !== 'function') return
const cc = window.CookieConsent
if (!cc) return
const hasMarketing = cc.acceptedCategory('marketing')
window.gtag('consent', 'update', {
'ad_storage': hasMarketing ? 'granted' : 'denied',
'ad_user_data': hasMarketing ? 'granted' : 'denied',
'ad_personalization': hasMarketing ? 'granted' : 'denied',
'analytics_storage': 'denied', // Wir nutzen Umami
})
}
return (
<>
{/* Consent Default (vor gtag.js) */}
<Script
id="google-consent-default"
strategy="beforeInteractive"
dangerouslySetInnerHTML={{
__html: `
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('consent', 'default', {
'ad_storage': 'denied',
'ad_user_data': 'denied',
'ad_personalization': 'denied',
'analytics_storage': 'denied',
'wait_for_update': 500
});
`,
}}
/>
{/* Google Ads Tag */}
<Script
src={`https://www.googletagmanager.com/gtag/js?id=${googleAdsId}`}
strategy="afterInteractive"
/>
<Script
id="google-ads-config"
strategy="afterInteractive"
dangerouslySetInnerHTML={{
__html: `
gtag('js', new Date());
gtag('config', '${googleAdsId}', {
'allow_enhanced_conversions': true
});
`,
}}
/>
</>
)
}
```
### GCLID Hook (für Conversion Attribution)
```typescript
// src/hooks/useGclid.ts
'use client'
import { useEffect } from 'react'
import { useSearchParams } from 'next/navigation'
const GCLID_STORAGE_KEY = 'gclid'
const GCLID_EXPIRY_DAYS = 90
/**
* GCLID aus URL erfassen und speichern
*/
export function useGclid() {
const searchParams = useSearchParams()
useEffect(() => {
const gclid = searchParams.get('gclid')
if (gclid) {
const data = {
value: gclid,
expires: Date.now() + (GCLID_EXPIRY_DAYS * 24 * 60 * 60 * 1000)
}
localStorage.setItem(GCLID_STORAGE_KEY, JSON.stringify(data))
}
}, [searchParams])
}
/**
* Gespeicherte GCLID abrufen
*/
export function getStoredGclid(): string | null {
if (typeof window === 'undefined') return null
try {
const stored = localStorage.getItem(GCLID_STORAGE_KEY)
if (!stored) return null
const data = JSON.parse(stored)
if (Date.now() > data.expires) {
localStorage.removeItem(GCLID_STORAGE_KEY)
return null
}
return data.value
} catch {
return null
}
}
```
### Client-Side Conversion Tracking
```typescript
// src/lib/google-ads.ts
declare global {
interface Window {
gtag: (...args: unknown[]) => void
CookieConsent: {
acceptedCategory: (category: string) => boolean
}
}
}
interface ConversionParams {
conversionId: string
value?: number
currency?: string
transactionId?: string
}
/**
* Client-Side Conversion (nur bei Marketing-Consent)
*/
export function trackConversion(params: ConversionParams) {
if (typeof window === 'undefined') return
if (typeof window.gtag !== 'function') return
const hasConsent = window.CookieConsent?.acceptedCategory('marketing')
if (!hasConsent) return
window.gtag('event', 'conversion', {
'send_to': params.conversionId,
'value': params.value || 1.0,
'currency': params.currency || 'EUR',
'transaction_id': params.transactionId,
})
}
/**
* Enhanced Conversion Data setzen
*/
export function setEnhancedConversionData(data: {
email?: string
phone?: string
firstName?: string
lastName?: string
}) {
if (typeof window === 'undefined') return
if (typeof window.gtag !== 'function') return
const hasConsent = window.CookieConsent?.acceptedCategory('marketing')
if (!hasConsent) return
window.gtag('set', 'user_data', {
'email': data.email,
'phone_number': data.phone,
'address': {
'first_name': data.firstName,
'last_name': data.lastName,
}
})
}
```
### Server-Side Conversion API
```typescript
// src/lib/google-ads.server.ts
import crypto from 'crypto'
const GOOGLE_ADS_CUSTOMER_ID = process.env.GOOGLE_ADS_CUSTOMER_ID
const GOOGLE_ADS_CONVERSION_ACTION_ID = process.env.GOOGLE_ADS_CONVERSION_ACTION_ID
const GOOGLE_ADS_API_TOKEN = process.env.GOOGLE_ADS_API_TOKEN
const GOOGLE_ADS_DEVELOPER_TOKEN = process.env.GOOGLE_ADS_DEVELOPER_TOKEN
interface ServerConversionParams {
conversionAction: string
email?: string
phone?: string
value?: number
currency?: string
gclid?: string
}
/**
* Server-Side Conversion Upload
* DSGVO-konform: Nur gehashte Daten
*/
export async function trackServerConversion(params: ServerConversionParams) {
if (!GOOGLE_ADS_CUSTOMER_ID || !GOOGLE_ADS_API_TOKEN) {
console.log('[Google Ads] Server-Side nicht konfiguriert')
return
}
const conversion: Record<string, unknown> = {
conversionAction: `customers/${GOOGLE_ADS_CUSTOMER_ID}/conversionActions/${GOOGLE_ADS_CONVERSION_ACTION_ID}`,
conversionDateTime: new Date().toISOString().replace('Z', '+00:00'),
conversionValue: params.value || 1.0,
currencyCode: params.currency || 'EUR',
}
if (params.gclid) {
conversion.gclid = params.gclid
}
if (params.email || params.phone) {
conversion.userIdentifiers = []
if (params.email) {
(conversion.userIdentifiers as Array<unknown>).push({
hashedEmail: hashForGoogle(params.email.toLowerCase().trim())
})
}
if (params.phone) {
(conversion.userIdentifiers as Array<unknown>).push({
hashedPhoneNumber: hashForGoogle(params.phone.replace(/[^0-9+]/g, ''))
})
}
}
try {
const response = await fetch(
`https://googleads.googleapis.com/v15/customers/${GOOGLE_ADS_CUSTOMER_ID}:uploadConversionAdjustments`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${GOOGLE_ADS_API_TOKEN}`,
'Content-Type': 'application/json',
'developer-token': GOOGLE_ADS_DEVELOPER_TOKEN!,
},
body: JSON.stringify({
conversions: [conversion],
partialFailure: true,
}),
}
)
if (!response.ok) {
console.error('[Google Ads] Upload failed:', await response.text())
}
} catch (error) {
console.error('[Google Ads] Error:', error)
}
}
function hashForGoogle(value: string): string {
return crypto.createHash('sha256').update(value).digest('hex')
}
```
---
## CookieInventory Erweiterung
Füge diese Cookies zur `cookie-inventory` Collection hinzu:
```typescript
// Über Payload Admin oder Seed-Script
const googleAdsCookies = [
{
name: '_gcl_au',
provider: 'Google Ads',
category: 'marketing',
purpose: 'Conversion-Tracking und Anzeigenmessung',
duration: '90 Tage',
type: 'first-party',
},
{
name: '_gcl_aw',
provider: 'Google Ads',
category: 'marketing',
purpose: 'Speichert GCLID für Conversion-Attribution',
duration: '90 Tage',
type: 'first-party',
},
{
name: 'IDE',
provider: 'Google (doubleclick.net)',
category: 'marketing',
purpose: 'Remarketing und Anzeigenpersonalisierung',
duration: '1 Jahr',
type: 'third-party',
},
]
```
---
## Environment Variables
### Frontend (.env.local)
```env
# Payload CMS API (Production)
NEXT_PUBLIC_PAYLOAD_URL=https://cms.c2sgmbh.de
NEXT_PUBLIC_API_URL=https://cms.c2sgmbh.de/api
# Umami Analytics (Production)
NEXT_PUBLIC_UMAMI_HOST=https://analytics.c2sgmbh.de
# Tenant-Konfiguration (je nach Projekt)
NEXT_PUBLIC_TENANT_ID=4
NEXT_PUBLIC_TENANT_SLUG=c2s
# Google Ads (pro Tenant unterschiedlich)
NEXT_PUBLIC_GOOGLE_ADS_ID=AW-XXXXXXXXX
```
### Backend (.env)
```env
# Umami Server-Side Tracking
UMAMI_HOST=https://analytics.c2sgmbh.de
UMAMI_WEBSITE_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
# Google Ads Server-Side API
GOOGLE_ADS_CUSTOMER_ID=1234567890
GOOGLE_ADS_CONVERSION_ACTION_ID=987654321
GOOGLE_ADS_API_TOKEN=ya29.xxx
GOOGLE_ADS_DEVELOPER_TOKEN=xxx
```
---
## Event-Naming-Konvention
| Event | Name | Data |
|-------|------|------|
| Newsletter Anmeldung | `newsletter_subscribe` | `{ source: string }` |
| Newsletter Bestätigung | `newsletter_confirm` | - |
| Kontaktformular | `contact_form_submit` | `{ form_type: string }` |
| CTA Klick | `cta_click` | `{ cta_name: string, location: string }` |
| Download | `download` | `{ file_name: string, file_type: string }` |
| Funnel-Step | `funnel_step` | `{ funnel: string, step: number, step_name: string }` |
| Scroll-Tiefe | `scroll_depth` | `{ depth_percent: number }` |
| Externer Link | `external_link` | `{ url: string }` |
---
## Zusammenfassung
```
┌─────────────────────────────────────────────────────────────────────────────────────┐
│ WAS IMPLEMENTIERT WERDEN MUSS │
│ │
│ 1. UMAMI (ohne Consent) │
│ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │
│ • UmamiScript Komponente in Layout einbinden │
│ • useAnalytics Hook in Formulare/CTAs integrieren │
│ • Server-Side Events für Backend-Actions │
│ │
│ 2. GOOGLE ADS (mit Consent) │
│ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │
│ • GoogleConsentMode Komponente (integriert mit Orestbida) │
│ • useGclid Hook für Attribution │
│ • Client-Side Conversions bei Consent │
│ • Server-Side Conversions immer (gehashte Daten) │
│ │
│ 3. COOKIE INVENTORY │
│ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │
│ • Google Ads Cookies dokumentieren │
│ │
└─────────────────────────────────────────────────────────────────────────────────────┘
```
---
## Dateien zu erstellen
```
src/
├── components/
│ ├── analytics/
│ │ ├── UmamiScript.tsx # Umami Tracking Script
│ │ └── GoogleConsentMode.tsx # Google Consent Mode v2
│ ├── forms/
│ │ └── NewsletterForm.tsx # Mit Analytics-Integration
│ └── ui/
│ ├── TrackedButton.tsx # CTA mit Tracking
│ └── TrackedDownload.tsx # Download mit Tracking
├── config/
│ └── analytics.ts # Website-IDs, Config
├── hooks/
│ ├── useAnalytics.ts # Client-Side Event Tracking
│ └── useGclid.ts # GCLID Erfassung
└── lib/
├── analytics.server.ts # Umami Server-Side
├── google-ads.ts # Google Ads Client
└── google-ads.server.ts # Google Ads Server API
```
---
*Letzte Aktualisierung: 18. Dezember 2025*

View file

@ -0,0 +1,758 @@
# Universal Features - Dokumentation
*Letzte Aktualisierung: 08. Januar 2026*
## Übersicht
Die Universal Features erweitern das Payload CMS um wiederverwendbare Collections und Blocks für Blog, News, Testimonials, Newsletter, FAQ, Team und Prozess-Darstellungen. Alle Features sind Multi-Tenant-fähig.
**Wichtig:** Frontends verwenden die **Production-API** (cms.c2sgmbh.de).
---
## API-Endpoints für Frontend
### Collections
| Collection | Endpoint | Beschreibung |
|------------|----------|--------------|
| Posts | `GET /api/posts` | Blog, News, Presse |
| Testimonials | `GET /api/testimonials` | Kundenbewertungen |
| FAQs | `GET /api/faqs` | FAQ-Einträge |
| Team | `GET /api/team` | Team-Mitglieder |
| Services | `GET /api/services` | Leistungen |
| Timelines | `GET /api/timelines` | Chronologische Events |
| Workflows | `GET /api/workflows` | Prozess-Darstellungen |
### Beispiel: Posts laden
```typescript
// Featured Blog-Posts für Tenant C2S laden
const posts = await fetch(
'https://cms.c2sgmbh.de/api/posts?where[tenant][equals]=4&where[type][equals]=blog&where[isFeatured][equals]=true&locale=de&limit=6'
).then(r => r.json())
```
### Newsletter API
| Endpoint | Methode | Beschreibung |
|----------|---------|--------------|
| `/api/newsletter/subscribe` | POST | Newsletter-Anmeldung |
| `/api/newsletter/confirm` | GET/POST | Double Opt-In Bestätigung |
| `/api/newsletter/unsubscribe` | GET/POST | Abmeldung |
```typescript
// Newsletter-Anmeldung
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',
tenantId: 4,
source: 'footer'
})
})
```
---
## Collections
### 1. Posts Collection
**Pfad:** `src/collections/Posts.ts`
**Slug:** `posts`
**Admin-Gruppe:** Content
Die Posts Collection dient zur Verwaltung von Blog-Artikeln, News, Pressemitteilungen und Ankündigungen.
#### Felder
| Feld | Typ | Beschreibung | Pflicht |
|------|-----|--------------|---------|
| `title` | Text | Titel des Beitrags | Ja |
| `slug` | Text | URL-Pfad (unique) | Ja |
| `type` | Select | Art des Beitrags | Ja |
| `isFeatured` | Checkbox | Hervorgehobener Beitrag | Nein |
| `excerpt` | Textarea | Kurzfassung (max. 300 Zeichen) | Nein |
| `featuredImage` | Upload | Beitragsbild | Nein |
| `content` | RichText | Hauptinhalt | Ja |
| `categories` | Relationship | Kategorien (hasMany) | Nein |
| `author` | Text | Autorname | Nein |
| `status` | Select | draft/published/archived | Nein |
| `publishedAt` | Date | Veröffentlichungsdatum | Nein |
| `meta` | Group | SEO-Einstellungen | Nein |
#### Post-Typen
- `blog` - Blog-Artikel (Standard)
- `news` - News/Aktuelles
- `press` - Pressemitteilung
- `announcement` - Ankündigung
---
### 2. Testimonials Collection
**Pfad:** `src/collections/Testimonials.ts`
**Slug:** `testimonials`
**Admin-Gruppe:** Content
Kundenstimmen und Bewertungen für Referenz-Seiten.
#### Felder
| Feld | Typ | Beschreibung | Pflicht |
|------|-----|--------------|---------|
| `quote` | Textarea | Zitat/Bewertungstext | Ja |
| `author` | Text | Name des Kunden | Ja |
| `role` | Text | Position/Rolle | Nein |
| `company` | Text | Unternehmen | Nein |
| `image` | Upload | Portrait-Foto | Nein |
| `rating` | Number | Bewertung 1-5 Sterne | Nein |
| `source` | Text | Quelle (z.B. "Google Reviews") | Nein |
| `sourceUrl` | Text | Link zur Original-Bewertung | Nein |
| `date` | Date | Datum der Bewertung | Nein |
| `isActive` | Checkbox | Sichtbarkeit | Nein |
| `order` | Number | Sortierung | Nein |
---
### 3. Newsletter Subscribers Collection
**Pfad:** `src/collections/NewsletterSubscribers.ts`
**Slug:** `newsletter-subscribers`
**Admin-Gruppe:** Marketing
DSGVO-konforme Speicherung von Newsletter-Anmeldungen mit Double Opt-In.
#### Felder
| Feld | Typ | Beschreibung | Pflicht |
|------|-----|--------------|---------|
| `email` | Email | E-Mail-Adresse | Ja |
| `firstName` | Text | Vorname | Nein |
| `lastName` | Text | Nachname | Nein |
| `status` | Select | Anmeldestatus | Ja |
| `interests` | Select (hasMany) | Interessengebiete | Nein |
| `source` | Text | Anmeldequelle | Nein |
| `subscribedAt` | Date | Anmeldedatum (auto) | Nein |
| `confirmedAt` | Date | Bestätigungsdatum (auto) | Nein |
| `unsubscribedAt` | Date | Abmeldedatum (auto) | Nein |
| `confirmationToken` | Text | Token für Double Opt-In (auto) | Nein |
| `ipAddress` | Text | IP-Adresse (DSGVO) | Nein |
| `userAgent` | Text | Browser-Info | Nein |
#### Double Opt-In Flow
1. User meldet sich an → Status: `pending`, Token generiert
2. E-Mail mit Bestätigungs-Link wird automatisch gesendet
3. User klickt Link → Status: `confirmed`
4. Willkommens-E-Mail wird gesendet
5. Abmeldung jederzeit über Link möglich
#### Status-Werte
- `pending` - Ausstehend (Double Opt-In)
- `confirmed` - Bestätigt
- `unsubscribed` - Abgemeldet
- `bounced` - E-Mail nicht zustellbar
---
### 4. FAQs Collection
**Pfad:** `src/collections/FAQs.ts`
**Slug:** `faqs`
**Admin-Gruppe:** Content
Häufig gestellte Fragen mit Kategorisierung.
#### Felder
| Feld | Typ | Beschreibung | Pflicht |
|------|-----|--------------|---------|
| `question` | Text | Frage | Ja |
| `answer` | RichText | Antwort | Ja |
| `category` | Text | Kategorie | Nein |
| `order` | Number | Sortierung | Nein |
| `isActive` | Checkbox | Sichtbarkeit | Nein |
---
### 5. Team Collection
**Pfad:** `src/collections/Team.ts`
**Slug:** `team`
**Admin-Gruppe:** Content
Team-Mitglieder und Mitarbeiter.
#### Felder
| Feld | Typ | Beschreibung | Pflicht |
|------|-----|--------------|---------|
| `name` | Text | Name | Ja |
| `role` | Text | Position/Rolle | Nein |
| `image` | Upload | Portrait-Foto | Nein |
| `bio` | Textarea | Kurzbiografie | Nein |
| `email` | Email | E-Mail-Adresse | Nein |
| `phone` | Text | Telefon | Nein |
| `socialLinks` | Array | Social Media Links | Nein |
| `order` | Number | Sortierung | Nein |
| `isActive` | Checkbox | Sichtbarkeit | Nein |
---
### 6. Services Collection
**Pfad:** `src/collections/Services.ts`
**Slug:** `services`
**Admin-Gruppe:** Content
Leistungen und Dienstleistungen.
#### Felder
| Feld | Typ | Beschreibung | Pflicht |
|------|-----|--------------|---------|
| `title` | Text | Leistungsname | Ja |
| `slug` | Text | URL-Pfad | Ja |
| `excerpt` | Textarea | Kurzbeschreibung | Nein |
| `content` | RichText | Detailbeschreibung | Nein |
| `icon` | Text | Icon-Name | Nein |
| `image` | Upload | Bild | Nein |
| `category` | Relationship | Service-Kategorie | Nein |
| `order` | Number | Sortierung | Nein |
---
### 7. Timelines Collection
**Pfad:** `src/collections/Timelines.ts`
**Slug:** `timelines`
**Admin-Gruppe:** Content
Chronologische Events für Geschichte, Meilensteine, Releases.
#### API
```typescript
// Timeline laden
fetch('https://cms.c2sgmbh.de/api/timelines?tenant=4&slug=company-history&locale=de')
```
#### Timeline-Typen
- `history` - Unternehmensgeschichte
- `milestones` - Projektmeilensteine
- `releases` - Produkt-Releases
- `career` - Karriere/Lebenslauf
- `events` - Ereignisse
- `process` - Prozess/Ablauf
---
### 8. Workflows Collection
**Pfad:** `src/collections/Workflows.ts`
**Slug:** `workflows`
**Admin-Gruppe:** Content
Komplexe Prozesse mit Phasen, Abhängigkeiten und Status-Tracking.
#### API
```typescript
// Workflow laden
fetch('https://cms.c2sgmbh.de/api/workflows?tenant=4&type=project&locale=de')
```
#### Workflow-Typen
- `project` - Projektabläufe
- `business` - Geschäftsprozesse
- `approval` - Genehmigungs-Workflows
- `onboarding` - Mitarbeiter-/Kundeneinführung
- `support` - Support/Service-Prozesse
- `development` - Entwicklungsprozesse
---
### 9. Favorites Collection (BlogWoman)
**Pfad:** `src/collections/Favorites.ts`
**Slug:** `favorites`
**Admin-Gruppe:** Content
Affiliate-Produkte für Blogger/Influencer mit Kategorien und Badges.
#### API
```typescript
// Alle Favorites eines Tenants
fetch('https://cms.c2sgmbh.de/api/favorites?where[tenant][equals]=4&locale=de')
// Nach Kategorie filtern
fetch('https://cms.c2sgmbh.de/api/favorites?where[tenant][equals]=4&where[category][equals]=fashion')
```
#### Felder
| Feld | Typ | Beschreibung | Pflicht |
|------|-----|--------------|---------|
| `title` | Text | Produktname | Ja |
| `slug` | Text | URL-Pfad (auto) | Ja |
| `description` | RichText | Beschreibung (lokalisiert) | Nein |
| `image` | Upload | Produktbild | Nein |
| `affiliateUrl` | Text | Affiliate-Link | Ja |
| `price` | Text | Preis (Freitext) | Nein |
| `category` | Select | fashion, beauty, travel, tech, home | Nein |
| `badge` | Select | investment-piece, daily-driver, grfi-approved, new, bestseller | Nein |
| `priceRange` | Select | budget, mid, premium, luxury | Nein |
| `isActive` | Checkbox | Sichtbarkeit | Nein |
| `order` | Number | Sortierung | Nein |
---
### 10. Series Collection (BlogWoman)
**Pfad:** `src/collections/Series.ts`
**Slug:** `series`
**Admin-Gruppe:** Content
YouTube-Serien mit eigenem Branding für Content-Reihen.
#### API
```typescript
// Alle Serien eines Tenants
fetch('https://cms.c2sgmbh.de/api/series?where[tenant][equals]=4&locale=de')
// Einzelne Serie nach Slug
fetch('https://cms.c2sgmbh.de/api/series?where[tenant][equals]=4&where[slug][equals]=grfi')
```
#### Felder
| Feld | Typ | Beschreibung | Pflicht |
|------|-----|--------------|---------|
| `title` | Text | Serienname (lokalisiert) | Ja |
| `slug` | Text | URL-Pfad (auto) | Ja |
| `description` | RichText | Beschreibung (lokalisiert) | Nein |
| `logo` | Upload | Serien-Logo | Nein |
| `coverImage` | Upload | Cover-Bild für Hero | Nein |
| `brandColor` | Text | Hex-Farbcode (#RRGGBB) | Nein |
| `youtubePlaylistId` | Text | YouTube Playlist ID | Nein |
| `isActive` | Checkbox | Sichtbarkeit | Nein |
---
## Blocks
Alle Blocks können in der Pages Collection verwendet werden.
### Block-Übersicht
#### Content Blocks
| Block | Slug | Beschreibung |
|-------|------|--------------|
| Posts List | `posts-list-block` | Blog/News-Liste |
| Testimonials | `testimonials-block` | Kundenstimmen |
| Newsletter | `newsletter-block` | Anmeldeformular |
| Process Steps | `process-steps-block` | Prozess-Schritte |
| Timeline | `timeline-block` | Chronologie |
| FAQ | `faq-block` | FAQ-Akkordeon |
| Team | `team-block` | Team-Mitglieder |
| Services | `services-block` | Leistungen |
#### BlogWoman Blocks (NEU)
| Block | Slug | Beschreibung |
|-------|------|--------------|
| Favorites | `favorites-block` | Affiliate-Produkte Grid/Liste/Karussell |
| Series | `series-block` | YouTube-Serien Übersicht |
| Series Detail | `series-detail-block` | Serien-Einzelseite mit Hero |
| Video Embed | `video-embed-block` | YouTube/Vimeo Embed mit Privacy Mode |
| Featured Content | `featured-content-block` | Kuratierte Mixed-Content Sammlung |
### 1. Posts List Block
**Slug:** `posts-list-block`
Zeigt eine Liste von Blog-Artikeln, News oder anderen Post-Typen an.
#### Konfigurationsoptionen
| Option | Typ | Standard | Beschreibung |
|--------|-----|----------|--------------|
| `title` | Text | - | Überschrift |
| `subtitle` | Text | - | Untertitel |
| `postType` | Select | blog | Beitragstyp-Filter |
| `layout` | Select | grid | Darstellung |
| `columns` | Select | 3 | Spaltenanzahl (bei Grid) |
| `limit` | Number | 6 | Anzahl Beiträge |
| `showFeaturedOnly` | Checkbox | false | Nur hervorgehobene |
| `filterByCategory` | Relationship | - | Kategorie-Filter |
| `showExcerpt` | Checkbox | true | Kurzfassung anzeigen |
| `showDate` | Checkbox | true | Datum anzeigen |
| `showAuthor` | Checkbox | false | Autor anzeigen |
| `showCategory` | Checkbox | true | Kategorie anzeigen |
| `showPagination` | Checkbox | false | Pagination |
| `showReadMore` | Checkbox | true | "Alle anzeigen" Link |
| `backgroundColor` | Select | white | Hintergrundfarbe |
#### Layout-Optionen
- `grid` - Karten im Grid
- `list` - Listenansicht
- `featured` - Featured + Grid
- `compact` - Kompakt (für Sidebar)
- `masonry` - Masonry-Layout
---
### 2. Testimonials Block
**Slug:** `testimonials-block`
Zeigt Kundenstimmen aus der Testimonials Collection.
#### Layout-Optionen
- `slider` - Karussell
- `grid` - Karten im Grid
- `single` - Einzeln (Featured)
- `masonry` - Masonry-Layout
- `list` - Listenansicht
---
### 3. Newsletter Block
**Slug:** `newsletter-block`
Anmeldeformular für Newsletter mit DSGVO-Hinweis.
#### Layout-Optionen
- `inline` - Eingabe + Button nebeneinander
- `stacked` - Untereinander
- `with-image` - Mit Bild (50/50)
- `minimal` - Nur Input
- `card` - Karten-Design
---
### 4. FAQ Block
**Slug:** `faq-block`
FAQ-Akkordeon mit Schema.org Markup.
#### Konfigurationsoptionen
| Option | Typ | Beschreibung |
|--------|-----|--------------|
| `title` | Text | Überschrift |
| `subtitle` | Text | Untertitel |
| `displayMode` | Select | all / selected / byCategory |
| `selectedFaqs` | Relationship | Handverlesene FAQs |
| `filterCategory` | Text | Kategorie-Filter |
| `layout` | Select | accordion / list / grid |
| `expandFirst` | Checkbox | Erste FAQ offen |
| `showSchema` | Checkbox | JSON-LD generieren |
---
### 5. Team Block
**Slug:** `team-block`
Team-Mitglieder aus der Team Collection.
#### Layout-Optionen
- `grid` - Karten im Grid
- `list` - Listenansicht
- `carousel` - Karussell
- `compact` - Kompakt
---
### 6. Services Block
**Slug:** `services-block`
Leistungen aus der Services Collection.
#### Layout-Optionen
- `grid` - Karten im Grid
- `list` - Listenansicht mit Details
- `icons` - Icon-Grid
- `tabs` - Tab-Navigation
---
### 7. Favorites Block (BlogWoman)
**Slug:** `favorites-block`
Zeigt Affiliate-Produkte aus der Favorites Collection.
#### Konfigurationsoptionen
| Option | Typ | Beschreibung |
|--------|-----|--------------|
| `title` | Text | Überschrift |
| `subtitle` | Text | Untertitel |
| `displayMode` | Select | all / selected / byCategory |
| `selectedFavorites` | Relationship | Handverlesene Favorites |
| `filterCategory` | Select | Kategorie-Filter |
| `layout` | Select | grid / list / carousel |
| `columns` | Select | 2 / 3 / 4 Spalten |
| `limit` | Number | Anzahl Produkte |
| `showPrice` | Checkbox | Preis anzeigen |
| `showBadge` | Checkbox | Badge anzeigen |
---
### 8. Series Block (BlogWoman)
**Slug:** `series-block`
Übersicht aller YouTube-Serien mit Filteroptionen.
#### Konfigurationsoptionen
| Option | Typ | Beschreibung |
|--------|-----|--------------|
| `title` | Text | Überschrift |
| `subtitle` | Text | Untertitel |
| `displayMode` | Select | all / selected |
| `selectedSeries` | Relationship | Handverlesene Serien |
| `layout` | Select | grid / list / featured |
| `showDescription` | Checkbox | Beschreibung anzeigen |
---
### 9. Series Detail Block (BlogWoman)
**Slug:** `series-detail-block`
Hero-Bereich für eine einzelne Serie mit Branding.
#### Konfigurationsoptionen
| Option | Typ | Beschreibung |
|--------|-----|--------------|
| `series` | Relationship | Serien-Referenz |
| `layout` | Select | hero / compact / sidebar |
| `showLogo` | Checkbox | Logo anzeigen |
| `showPlaylistLink` | Checkbox | YouTube-Link anzeigen |
| `useBrandColor` | Checkbox | Markenfarbe verwenden |
---
### 10. Video Embed Block (BlogWoman)
**Slug:** `video-embed-block`
Datenschutzfreundliches Video-Embedding für YouTube/Vimeo.
#### Konfigurationsoptionen
| Option | Typ | Beschreibung |
|--------|-----|--------------|
| `videoUrl` | Text | YouTube/Vimeo URL |
| `title` | Text | Video-Titel |
| `aspectRatio` | Select | 16:9 / 4:3 / 1:1 / 9:16 |
| `privacyMode` | Checkbox | youtube-nocookie.com verwenden |
| `autoplay` | Checkbox | Automatisch abspielen |
| `showControls` | Checkbox | Player-Controls anzeigen |
| `thumbnailImage` | Upload | Custom Thumbnail |
---
### 11. Featured Content Block (BlogWoman)
**Slug:** `featured-content-block`
Kuratierte Sammlung aus verschiedenen Content-Typen.
#### Konfigurationsoptionen
| Option | Typ | Beschreibung |
|--------|-----|--------------|
| `title` | Text | Überschrift |
| `subtitle` | Text | Untertitel |
| `items` | Array | Gemischte Content-Items |
| `layout` | Select | grid / masonry / featured |
**Item-Typen:**
- `post` - Blog-Artikel
- `video` - Video
- `favorite` - Affiliate-Produkt
- `series` - YouTube-Serie
- `external` - Externer Link
---
## Multi-Tenant Konfiguration
Alle Collections sind für Multi-Tenant konfiguriert:
```typescript
// payload.config.ts
multiTenantPlugin({
tenantsSlug: 'tenants',
collections: {
posts: {},
testimonials: {},
'newsletter-subscribers': {},
faqs: {},
team: {},
services: {},
timelines: {},
workflows: {},
favorites: {}, // BlogWoman
series: {}, // BlogWoman
// ... weitere Collections
},
})
```
### Tenant-Zuordnung
Jedes Dokument enthält ein `tenant`-Feld. Die Access-Control sorgt für Isolation:
- **Authentifizierte Admins:** Sehen alle Dokumente
- **Anonyme Requests:** Nur Dokumente des passenden Tenants
### Tenant-IDs
| ID | Name | Slug |
|----|------|------|
| 1 | porwoll.de | porwoll |
| 4 | Complex Care Solutions GmbH | c2s |
| 5 | Gunshin | gunshin |
---
## Dateien-Übersicht
```
src/
├── collections/
│ ├── Posts.ts
│ ├── Categories.ts
│ ├── Testimonials.ts
│ ├── NewsletterSubscribers.ts
│ ├── FAQs.ts
│ ├── Team.ts
│ ├── Services.ts
│ ├── ServiceCategories.ts
│ ├── Timelines.ts
│ ├── Workflows.ts
│ ├── Favorites.ts # BlogWoman
│ └── Series.ts # BlogWoman
├── blocks/
│ ├── PostsListBlock.ts
│ ├── TestimonialsBlock.ts
│ ├── NewsletterBlock.ts
│ ├── ProcessStepsBlock.ts
│ ├── TimelineBlock.ts
│ ├── FAQBlock.ts
│ ├── TeamBlock.ts
│ ├── ServicesBlock.ts
│ ├── FavoritesBlock.ts # BlogWoman
│ ├── SeriesBlock.ts # BlogWoman
│ ├── SeriesDetailBlock.ts # BlogWoman
│ ├── VideoEmbedBlock.ts # BlogWoman
│ ├── FeaturedContentBlock.ts # BlogWoman
│ └── index.ts
├── lib/
│ ├── tenantAccess.ts
│ └── email/
│ ├── newsletter-service.ts
│ └── newsletter-templates.ts
├── hooks/
│ └── sendNewsletterConfirmation.ts
└── app/(payload)/api/newsletter/
├── subscribe/route.ts
├── confirm/route.ts
└── unsubscribe/route.ts
```
---
## URLs
### Production (für Frontends)
| Resource | URL |
|----------|-----|
| Posts API | https://cms.c2sgmbh.de/api/posts |
| Testimonials API | https://cms.c2sgmbh.de/api/testimonials |
| FAQs API | https://cms.c2sgmbh.de/api/faqs |
| Team API | https://cms.c2sgmbh.de/api/team |
| Services API | https://cms.c2sgmbh.de/api/services |
| Timelines API | https://cms.c2sgmbh.de/api/timelines |
| Workflows API | https://cms.c2sgmbh.de/api/workflows |
| Newsletter Subscribe | https://cms.c2sgmbh.de/api/newsletter/subscribe |
### Development
| Resource | URL |
|----------|-----|
| API Base | https://pl.porwoll.tech/api |
| Admin Panel | https://pl.porwoll.tech/admin |
---
## Changelog
### Version 1.3 (08.01.2026)
- **BlogWoman Collections hinzugefügt:**
- Favorites Collection (Affiliate-Produkte)
- Series Collection (YouTube-Serien)
- **BlogWoman Blocks hinzugefügt:**
- FavoritesBlock (Grid/Liste/Karussell)
- SeriesBlock (Serien-Übersicht)
- SeriesDetailBlock (Serien-Hero)
- VideoEmbedBlock (Privacy-Mode)
- FeaturedContentBlock (Mixed-Content)
- Dateien-Übersicht aktualisiert
- Multi-Tenant Konfiguration erweitert
### Version 1.2 (18.12.2025)
- Dokumentation auf Production-URLs aktualisiert
- API-Endpoints für Frontend hinzugefügt
- Newsletter Double Opt-In Flow dokumentiert
- Timelines und Workflows Collections hinzugefügt
- FAQ, Team, Services Blocks dokumentiert
### Version 1.1 (14.12.2025)
- Timelines Collection für chronologische Darstellungen
- Workflows Collection für Prozesse
- FAQs Collection mit Kategorisierung
- Team Collection mit Social Links
- Services Collection mit Kategorien
### Version 1.0 (30.11.2025)
- Posts Collection um `type`, `isFeatured`, `excerpt` erweitert
- Testimonials Collection erstellt
- NewsletterSubscribers Collection erstellt (DSGVO-konform)
- 5 neue Blocks für Pages implementiert
- Multi-Tenant Integration für alle Collections
---
*Letzte Aktualisierung: 08. Januar 2026*

File diff suppressed because it is too large Load diff

526
docs/guides/FRONTEND.md Normal file
View file

@ -0,0 +1,526 @@
# Frontend-Entwicklung - Payload CMS Multi-Tenant
> **Server:** sv-frontend (LXC 704) - 10.10.181.104
> **Backend API:** https://cms.c2sgmbh.de/api (Production)
## Übersicht
Das Frontend wird als separates Next.js-Projekt entwickelt und nutzt Payload CMS als Headless CMS über die REST-API.
**Wichtig:** Die Frontend-Entwicklung verwendet die **Produktions-API und -Datenbank**, um mit echten Inhalten zu arbeiten. SEO-Einstellungen und Cookie-Consent-Konfigurationen werden ebenfalls aus der Produktionsumgebung geladen.
---
## 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
```
### Warum Production-Daten?
| Aspekt | Grund |
|--------|-------|
| **Content** | Echte Inhalte für realistische Entwicklung |
| **SEO** | Produktions-Meta-Tags und Structured Data |
| **Cookie-Consent** | Live Cookie-Konfigurationen (DSGVO-relevant) |
| **Media** | Produktions-Bilder mit allen Größen |
| **Consistency** | Keine Sync-Probleme zwischen Dev/Prod |
---
## 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 |
---
## Offene Frontend-Tasks
### Hohe Priorität
- [ ] **Block-Komponenten entwickeln**
- [ ] Hero Block
- [ ] Hero Slider Block
- [ ] Text Block
- [ ] Image Text Block
- [ ] Card Grid Block
- [ ] Quote Block
- [ ] CTA Block
- [ ] Contact Form Block
- [ ] Video Block
- [ ] Divider Block
- [ ] Timeline Block
- [ ] Posts List Block
- [ ] Testimonials Block
- [ ] Newsletter Block
- [ ] Process Steps Block
- [ ] FAQ Block
- [ ] Team Block
- [ ] Services Block
- [ ] **Newsletter-Anmelde-Formular**
- API: `POST /api/newsletter/subscribe`
- Double Opt-In Flow bereits im Backend implementiert
- Felder: email, firstName (optional), tenantId, source
- [ ] **Cookie-Banner implementieren**
- Cookie Configurations aus Production-API laden
- Consent-Logs an Backend senden
- DSGVO-konform mit Opt-In
### Mittlere Priorität
- [ ] **Multi-Tenant Routing**
- Domain-basierte Tenant-Erkennung
- Locale-Routing (`/[locale]/...`)
- Unterstützte Locales: `de` (default), `en`
- [ ] **SEO-Integration**
- Meta-Tags aus Pages/Posts (Production)
- Structured Data (JSON-LD)
- Sitemap: https://cms.c2sgmbh.de/sitemap.xml
- [ ] **Suche implementieren**
- API: `GET /api/search?q=...&locale=de`
- Auto-Complete: `GET /api/search/suggestions?q=...`
- Rate-Limit: 30 Requests/Minute
### Tenant-spezifische Features
#### porwoll.de
- [ ] Portfolio-Galerie (Fotografie)
- [ ] Buchungsformular
- [ ] Before/After Bildvergleich
#### complexcaresolutions.de (C2S)
- [ ] Team-Übersicht
- [ ] Leistungs-Seiten
- [ ] Zertifizierungen
- [ ] Karriere-Seite mit Stellenangeboten
#### gunshin.de (Game Development)
- [ ] Projekt-Galerie
- API: `GET /api/projects?where[tenant][equals]=5`
- [ ] Portfolio-Seiten
- [ ] Referenzen-Slider
#### zweitmein.ng
- [ ] FAQ-Sektion
- [ ] Preistabellen
- [ ] Kontaktformular
---
## 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) |
### Community Management (YouTube/Meta)
| Collection | Endpoint | Beschreibung |
|------------|----------|--------------|
| YouTube Channels | `GET /api/youtube-channels` | Multi-Kanal-Verwaltung |
| YouTube Content | `GET /api/youtube-content` | Videos + Shorts mit Kommentaren |
| YT Series | `GET /api/yt-series` | Serien mit Branding (Logo, Farben) |
| YT Notifications | `GET /api/yt-notifications` | Handlungsbedarf-System |
| Social Platforms | `GET /api/social-platforms` | Plattform-Konfiguration |
| Social Accounts | `GET /api/social-accounts` | OAuth-Verbindungen |
| Community Interactions | `GET /api/community-interactions` | Kommentare/Nachrichten |
### Site Settings & Navigation (Tenant-isolierte Collections)
> **Hinweis:** SiteSettings und Navigations wurden zu tenant-spezifischen Collections umgewandelt.
| 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 |
### Globals (Systemweit)
| Global | Endpoint | Beschreibung |
|--------|----------|--------------|
| SEO Settings | `GET /api/globals/seo-settings` | Default SEO (Production) |
### Spezielle Endpoints
| Endpoint | Methode | Beschreibung |
|----------|---------|--------------|
| `/api/search` | GET | Volltextsuche |
| `/api/search/suggestions` | GET | Auto-Complete |
| `/api/newsletter/subscribe` | POST | Newsletter-Anmeldung |
| `/api/timelines` | GET | Timeline-Daten |
| `/api/workflows` | GET | Workflow-Daten |
---
## Tenant-Filterung
Alle Collection-Anfragen sollten nach Tenant gefiltert werden:
```typescript
// Beispiel: Posts für Tenant "c2s" (ID: 4)
fetch('https://cms.c2sgmbh.de/api/posts?where[tenant][equals]=4&locale=de')
// Beispiel: Pages für Tenant "gunshin" (ID: 5)
fetch('https://cms.c2sgmbh.de/api/pages?where[tenant][equals]=5&locale=de')
// Beispiel: SEO-Settings (Global, kein Tenant-Filter)
fetch('https://cms.c2sgmbh.de/api/globals/seo-settings')
```
### Tenant-IDs
| ID | Name | Slug | Domain |
|----|------|------|--------|
| 1 | porwoll.de | porwoll | porwoll.de |
| 4 | Complex Care Solutions GmbH | c2s | complexcaresolutions.de |
| 5 | Gunshin | gunshin | gunshin.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
### Anmeldung
```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
})
})
// Response: { success: true, message: '...' }
```
### Flow
1. User gibt E-Mail ein → `POST /api/newsletter/subscribe`
2. Backend sendet Double Opt-In E-Mail
3. User klickt Bestätigungs-Link
4. Backend sendet Willkommens-E-Mail
5. User kann sich über Link in E-Mails abmelden
---
## Cookie-Consent (Production-Daten)
### Konfiguration laden
```typescript
// Cookie-Konfiguration aus Production laden
const config = await fetch('https://cms.c2sgmbh.de/api/cookie-configurations?where[tenant][equals]=4')
.then(r => r.json())
// config.docs enthält:
// - Kategorien (necessary, analytics, marketing, etc.)
// - Cookie-Details pro Kategorie
// - Texte für Banner (lokalisiert)
```
### Consent loggen
```typescript
await fetch('https://cms.c2sgmbh.de/api/consent-logs', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
tenant: 4,
consentGiven: true,
categories: ['necessary', 'analytics'],
ipAddress: '...', // Optional, für DSGVO
userAgent: navigator.userAgent
})
})
```
---
## SEO-Integration (Production-Daten)
### Global SEO-Settings
```typescript
// SEO-Defaults aus Production laden
const seoSettings = await fetch('https://cms.c2sgmbh.de/api/globals/seo-settings')
.then(r => r.json())
// Enthält:
// - defaultTitle, titleTemplate
// - defaultDescription
// - defaultImage (OG-Image)
// - robotsDefault
```
### Page-spezifische SEO
```typescript
// SEO-Daten aus Page laden
const page = await fetch('https://cms.c2sgmbh.de/api/pages?where[slug][equals]=about&where[tenant][equals]=4')
.then(r => r.json())
// page.docs[0].meta enthält:
// - title, description
// - image (OG-Image Override)
// - noIndex, noFollow
```
### Sitemap
Die Sitemap wird automatisch von Payload generiert:
- **URL:** https://cms.c2sgmbh.de/sitemap.xml
- Enthält alle publizierten Pages und Posts
---
## Kontaktformular
Formular-Submissions werden über die Forms-Collection verarbeitet:
```typescript
await fetch('https://cms.c2sgmbh.de/api/form-submissions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
form: 1, // Form-ID
submissionData: [
{ field: 'name', value: 'Max Mustermann' },
{ field: 'email', value: 'max@example.com' },
{ field: 'message', value: 'Ihre Nachricht...' }
]
})
})
```
---
## Authentifizierung (optional)
Falls User-Authentifizierung benötigt wird:
```typescript
// Login
const { token, user } = await fetch('https://cms.c2sgmbh.de/api/users/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password })
}).then(r => r.json())
// Authentifizierte Requests
fetch('https://cms.c2sgmbh.de/api/...', {
headers: { 'Authorization': `JWT ${token}` }
})
```
---
## Entwicklungshinweise
### TypeScript-Typen
Die Payload-Typen können aus dem Backend exportiert werden:
```bash
# Auf dem Payload-Server (Production)
ssh payload@162.55.85.18
cd ~/payload-cms
pnpm payload generate:types
# Datei: src/payload-types.ts
```
Diese Datei kann ins Frontend-Projekt kopiert werden für typsichere API-Calls.
### Rate-Limits
| Endpoint | Limit |
|----------|-------|
| Öffentliche API | 60/min |
| Suche | 30/min |
| Newsletter | 5/10min |
| Formulare | 5/10min |
### Caching
- API-Responses werden serverseitig gecacht (Redis)
- TTL: 60 Sekunden für Suche
- Cache wird bei Content-Änderungen invalidiert
### CORS
Die Production-API erlaubt Requests von:
- `*.porwoll.tech` (Development)
- `porwoll.de`, `complexcaresolutions.de`, `gunshin.de` (Production)
---
## 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
codex # Codex CLI
gemini # Gemini CLI
```
### Service-Management
```bash
# Systemd Service starten
systemctl start frontend-porwoll
# Service stoppen
systemctl stop frontend-porwoll
# Logs anzeigen
journalctl -u frontend-porwoll -f
```
---
## Community Management Integration
### YouTube-Inhalte abrufen
```typescript
// Alle Videos eines Kanals
const videos = await fetch(
'https://cms.c2sgmbh.de/api/youtube-content?where[channel][equals]=1&where[contentType][equals]=video'
).then(r => r.json())
// Serien eines Kanals
const series = await fetch(
'https://cms.c2sgmbh.de/api/yt-series?where[channel][equals]=1&where[isActive][equals]=true'
).then(r => r.json())
// Videos einer Serie
const seriesVideos = await fetch(
'https://cms.c2sgmbh.de/api/youtube-content?where[series][equals]=1'
).then(r => r.json())
```
### Kommentare/Interaktionen (Auth erforderlich)
```typescript
// Kommentare eines Videos
const comments = await fetch(
'https://cms.c2sgmbh.de/api/community-interactions?where[contentId][equals]=VIDEO_ID',
{ headers: { 'Authorization': `JWT ${token}` } }
).then(r => r.json())
// Ungelesene Benachrichtigungen
const notifications = await fetch(
'https://cms.c2sgmbh.de/api/yt-notifications?where[status][equals]=unread',
{ headers: { 'Authorization': `JWT ${token}` } }
).then(r => r.json())
```
---
## Ressourcen
- **Payload CMS Docs:** https://payloadcms.com/docs
- **API-Dokumentation:** https://cms.c2sgmbh.de/api/docs
- **Backend-Repository:** https://github.com/complexcaresolutions/cms.c2sgmbh.git
- **Analytics:** https://analytics.c2sgmbh.de
---
*Letzte Aktualisierung: 17.01.2026*

View file

@ -0,0 +1,805 @@
# Anleitung: Prompt-Erstellung für Tenant-Setup in Payload CMS
Diese Anleitung erklärt, wie du als Konzept-KI einen strukturierten Prompt für Claude Code erstellen kannst, um einen neuen Tenant im Payload CMS anzulegen und mit Inhalten zu befüllen.
---
## 1. Projektkontext
Das Payload CMS ist ein Multi-Tenant-fähiges Headless CMS mit folgenden Eigenschaften:
- **Framework:** Payload 3.69.0 + Next.js 15.5.9
- **Datenbank:** PostgreSQL 17
- **Sprachen:** Deutsch (de, Standard) und Englisch (en)
- **Tenant-Isolation:** Jede Collection ist automatisch tenant-spezifisch
---
## 2. Prompt-Struktur
Erstelle deinen Prompt nach folgendem Schema:
```markdown
## Tenant-Informationen
**Name:** [Firmenname]
**Slug:** [url-freundlicher-name]
**Domain(s):** [domain1.de, domain2.com]
### E-Mail-Konfiguration (optional)
- From-Adresse: [email]
- From-Name: [Name]
- Reply-To: [email]
- Eigener SMTP: [ja/nein]
- Host: [smtp.example.com]
- Port: [587]
- User: [user]
- Passwort: [pass]
---
## Inhalte
### Site-Settings
- Logo: [Beschreibung/URL]
- Favicon: [Beschreibung/URL]
- Primärfarbe: [#hex]
- Sekundärfarbe: [#hex]
- Footer-Text: [Text]
### Navigation
[Liste der Menüpunkte mit Links]
### Seiten
[Für jede Seite: Titel, Slug, Blocks]
### Blog-Posts (optional)
[Für jeden Post: Titel, Kategorie, Inhalt]
### Weitere Inhalte
[Services, Team, Testimonials, etc.]
```
---
## 3. Verfügbare Collections
### Kern-Collections
| Collection | Beschreibung | Wichtige Felder |
|------------|--------------|-----------------|
| `tenants` | Tenant-Konfiguration | name, slug, domains, email |
| `site-settings` | Website-Einstellungen | logo, colors, footer, contact |
| `navigations` | Menü-Strukturen | items (array mit label, link, children) |
| `pages` | Website-Seiten | title, slug, blocks (array) |
| `media` | Bilder/Dateien | file, alt, caption |
### Content-Collections
| Collection | Beschreibung | Wichtige Felder |
|------------|--------------|-----------------|
| `posts` | Blog-Artikel | title, content, categories, tags, authors, type |
| `categories` | Post-Kategorien | name, slug |
| `tags` | Post-Tags | name, slug |
| `authors` | Autoren | name, bio, image |
| `testimonials` | Kundenstimmen | quote, author, company, rating |
| `faqs` | FAQ-Einträge | question, answer |
| `team` | Team-Mitglieder | name, position, bio, image |
| `services` | Dienstleistungen | title, description, icon |
| `service-categories` | Service-Kategorien | name, slug |
### Erweiterte Collections
| Collection | Beschreibung | Wichtige Felder |
|------------|--------------|-----------------|
| `portfolios` | Portfolio-Einträge | title, images, description, category |
| `products` | Produkte | name, price, description, images |
| `events` | Veranstaltungen | title, date, location, description |
| `jobs` | Stellenangebote | title, department, description, requirements |
| `locations` | Standorte | name, address, coordinates, hours |
| `partners` | Partner/Logos | name, logo, website |
| `downloads` | Downloads | title, file, description |
| `videos` | Video-Bibliothek | title, source (youtube/vimeo/upload), url |
| `timelines` | Zeitstrahlen | title, type, entries |
| `workflows` | Prozesse | title, phases, steps |
### Formular & Newsletter
| Collection | Beschreibung |
|------------|--------------|
| `forms` | Formular-Builder |
| `newsletter-subscribers` | Newsletter-Abonnenten |
### Spezial-Collections (tenant-spezifisch)
| Collection | Tenant | Beschreibung |
|------------|--------|--------------|
| `bookings` | porwoll.de | Fotografie-Buchungen |
| `certifications` | c2s | Zertifizierungen |
| `projects` | gunshin | Spieleprojekte |
| `favorites` | BlogWoman | Affiliate-Produkte |
| `series` | BlogWoman | YouTube-Serien |
---
## 4. Verfügbare Blocks (42 Stück)
### Layout-Blocks
```
hero-block - Hero-Banner mit Bild
hero-slider-block - Hero-Slider mit mehreren Slides
image-slider-block - Bild-Karussell
text-block - Rich-Text-Inhalt
image-text-block - Bild + Text nebeneinander
card-grid-block - Karten-Raster
quote-block - Zitat/Blockquote
cta-block - Call-to-Action Button
divider-block - Visueller Trenner
```
### Media-Blocks
```
video-block - Video (YouTube/Vimeo/Upload)
video-embed-block - Video-Einbettung mit Privacy-Mode
```
### Content-Blocks
```
posts-list-block - Blog-Post-Liste mit Pagination
testimonials-block - Testimonial-Karussell
newsletter-block - Newsletter-Anmeldung
process-steps-block - Schritt-für-Schritt-Prozess
faq-block - FAQ-Akkordeon
team-block - Team-Mitglieder-Grid
services-block - Service-Auflistung
```
### Blog-Blocks
```
author-bio-block - Autoren-Info
related-posts-block - Verwandte Artikel
share-buttons-block - Social-Share-Buttons
table-of-contents-block - Inhaltsverzeichnis
```
### Team-Blocks
```
team-filter-block - Team mit Filter
org-chart-block - Organigramm
```
### Feature-Blocks
```
locations-block - Standorte mit Karte
logo-grid-block - Partner-/Kunden-Logos
stats-block - Statistiken/Zahlen
jobs-block - Stellenangebote
downloads-block - Download-Bereich
map-block - Karten-Einbettung
events-block - Veranstaltungs-Kalender
pricing-block - Preis-Tabellen
tabs-block - Tab-Inhalte
accordion-block - Akkordeon-Sektionen
comparison-block - Vergleichs-Tabellen
timeline-block - Zeitleisten-Visualisierung
before-after-block - Vorher/Nachher-Vergleich
```
### BlogWoman-Blocks
```
favorites-block - Affiliate-Produkte
series-block - YouTube-Serien-Übersicht
series-detail-block - Einzelne Serie mit Hero
featured-content-block - Kuratierte Inhalte
```
---
## 5. Block-Konfiguration (Details)
### hero-block
```json
{
"blockType": "hero-block",
"heading": "Willkommen",
"subheading": "Untertitel",
"backgroundImage": "<media-id>",
"ctaText": "Mehr erfahren",
"ctaLink": "/ueber-uns",
"alignment": "center",
"overlay": true,
"overlayOpacity": 0.5
}
```
### hero-slider-block
```json
{
"blockType": "hero-slider-block",
"slides": [
{
"heading": "Slide 1",
"subheading": "Text",
"backgroundImage": "<media-id>",
"ctaText": "Button",
"ctaLink": "/link"
}
],
"autoplay": true,
"autoplaySpeed": 5000,
"showDots": true,
"showArrows": true
}
```
### text-block
```json
{
"blockType": "text-block",
"content": {
"root": {
"type": "root",
"children": [
{
"type": "paragraph",
"children": [{"text": "Ihr Text hier..."}]
}
]
}
}
}
```
### image-text-block
```json
{
"blockType": "image-text-block",
"image": "<media-id>",
"heading": "Überschrift",
"content": "<rich-text>",
"imagePosition": "left",
"ctaText": "Button",
"ctaLink": "/link"
}
```
### card-grid-block
```json
{
"blockType": "card-grid-block",
"cards": [
{
"title": "Karte 1",
"description": "Beschreibung",
"image": "<media-id>",
"link": "/link"
}
],
"columns": 3
}
```
### faq-block
```json
{
"blockType": "faq-block",
"heading": "Häufige Fragen",
"faqs": ["<faq-id-1>", "<faq-id-2>"]
}
```
### testimonials-block
```json
{
"blockType": "testimonials-block",
"heading": "Kundenstimmen",
"testimonials": ["<testimonial-id-1>", "<testimonial-id-2>"],
"layout": "carousel"
}
```
### team-block
```json
{
"blockType": "team-block",
"heading": "Unser Team",
"teamMembers": ["<team-id-1>", "<team-id-2>"],
"showBio": true,
"columns": 4
}
```
### services-block
```json
{
"blockType": "services-block",
"heading": "Unsere Leistungen",
"services": ["<service-id-1>", "<service-id-2>"],
"layout": "grid"
}
```
### posts-list-block
```json
{
"blockType": "posts-list-block",
"heading": "Neueste Artikel",
"categories": ["<category-id>"],
"limit": 6,
"showPagination": true
}
```
### cta-block
```json
{
"blockType": "cta-block",
"heading": "Jetzt starten",
"text": "Kontaktieren Sie uns noch heute.",
"buttonText": "Kontakt aufnehmen",
"buttonLink": "/kontakt",
"backgroundColor": "#1a1a1a"
}
```
### stats-block
```json
{
"blockType": "stats-block",
"stats": [
{"number": "500+", "label": "Kunden"},
{"number": "10", "label": "Jahre Erfahrung"},
{"number": "24/7", "label": "Support"}
]
}
```
### pricing-block
```json
{
"blockType": "pricing-block",
"heading": "Unsere Preise",
"plans": [
{
"name": "Basic",
"price": "29",
"period": "monatlich",
"features": ["Feature 1", "Feature 2"],
"ctaText": "Auswählen",
"ctaLink": "/checkout/basic",
"highlighted": false
}
]
}
```
---
## 6. Beispiel-Prompt (Vollständig)
```markdown
# Tenant anlegen: Musterfirma GmbH
## Tenant-Informationen
**Name:** Musterfirma GmbH
**Slug:** musterfirma
**Domains:** musterfirma.de, www.musterfirma.de
### E-Mail-Konfiguration
- From-Adresse: info@musterfirma.de
- From-Name: Musterfirma GmbH
- Reply-To: kontakt@musterfirma.de
- Eigener SMTP: nein (globalen SMTP verwenden)
---
## Site-Settings
- **Logo:** Musterfirma-Logo (blauer Kreis mit weißem "M")
- **Favicon:** Kleines "M" auf blauem Hintergrund
- **Primärfarbe:** #2563eb (Blau)
- **Sekundärfarbe:** #1e40af (Dunkelblau)
- **Akzentfarbe:** #f59e0b (Orange)
- **Footer-Text:** "© 2026 Musterfirma GmbH. Alle Rechte vorbehalten."
- **Kontakt-E-Mail:** info@musterfirma.de
- **Telefon:** +49 123 456789
- **Adresse:** Musterstraße 1, 12345 Musterstadt
---
## Navigation
### Hauptmenü
1. Startseite → /
2. Über uns → /ueber-uns
3. Leistungen → /leistungen
- Beratung → /leistungen/beratung
- Entwicklung → /leistungen/entwicklung
- Support → /leistungen/support
4. Referenzen → /referenzen
5. Blog → /blog
6. Kontakt → /kontakt
### Footer-Navigation
1. Impressum → /impressum
2. Datenschutz → /datenschutz
3. AGB → /agb
---
## Seiten
### Startseite (/)
**Blocks:**
1. hero-slider-block
- Slide 1: "Willkommen bei Musterfirma" / "Ihr Partner für digitale Lösungen" / CTA: "Jetzt beraten lassen" → /kontakt
- Slide 2: "Innovation trifft Expertise" / "Seit 10 Jahren erfolgreich" / CTA: "Mehr erfahren" → /ueber-uns
2. stats-block
- 500+ zufriedene Kunden
- 10 Jahre Erfahrung
- 50 Mitarbeiter
- 24/7 Support
3. services-block
- Überschrift: "Unsere Leistungen"
- Zeige alle Services
4. testimonials-block
- Überschrift: "Das sagen unsere Kunden"
- 3 Testimonials im Karussell
5. cta-block
- "Bereit für Ihr nächstes Projekt?"
- Button: "Kostenloses Erstgespräch" → /kontakt
### Über uns (/ueber-uns)
**Blocks:**
1. hero-block
- "Über Musterfirma"
- "Lernen Sie uns kennen"
2. image-text-block
- Bild: Team-Foto
- Text: Firmengeschichte und Vision
- Bild links
3. timeline-block
- Firmengeschichte als Zeitstrahl
- 2016: Gründung
- 2018: Erster Großkunde
- 2020: 25 Mitarbeiter
- 2024: Expansion
4. team-block
- "Unser Team"
- Alle Team-Mitglieder
### Leistungen (/leistungen)
**Blocks:**
1. hero-block
- "Unsere Leistungen"
- "Maßgeschneiderte Lösungen für Ihren Erfolg"
2. services-block
- Alle Services mit Icons
3. process-steps-block
- "So arbeiten wir"
- Schritt 1: Analyse
- Schritt 2: Konzept
- Schritt 3: Umsetzung
- Schritt 4: Betreuung
4. cta-block
- "Interesse geweckt?"
- Button: "Jetzt anfragen"
### Kontakt (/kontakt)
**Blocks:**
1. hero-block
- "Kontakt"
- "Wir freuen uns auf Ihre Nachricht"
2. image-text-block
- Kontaktinformationen
- Bild: Büro-Foto
3. contact-form-block
- Kontaktformular
4. map-block
- Standort auf Karte
### Blog (/blog)
**Blocks:**
1. hero-block
- "Unser Blog"
- "Insights und Neuigkeiten"
2. posts-list-block
- Alle Blog-Posts
- 6 pro Seite
- Mit Pagination
---
## Kategorien
1. **Technologie** (slug: technologie)
2. **Trends** (slug: trends)
3. **Case Studies** (slug: case-studies)
4. **Tipps & Tricks** (slug: tipps-tricks)
---
## Tags
1. Digitalisierung
2. Innovation
3. KI
4. Cloud
5. Sicherheit
---
## Autoren
### Max Mustermann
- **Position:** CEO & Gründer
- **Bio:** Max ist Gründer und CEO der Musterfirma GmbH. Mit über 15 Jahren Erfahrung in der IT-Branche...
- **Bild:** Professionelles Porträt
### Anna Schmidt
- **Position:** Head of Content
- **Bio:** Anna leitet das Content-Team und ist verantwortlich für alle redaktionellen Inhalte...
- **Bild:** Professionelles Porträt
---
## Team-Mitglieder
### Max Mustermann
- **Position:** CEO & Gründer
- **Bio:** Gründer mit Vision für digitale Transformation
- **E-Mail:** max@musterfirma.de
- **LinkedIn:** linkedin.com/in/maxmustermann
### Anna Schmidt
- **Position:** Head of Content
- **Bio:** Content-Strategin mit Leidenschaft für Storytelling
- **E-Mail:** anna@musterfirma.de
### Tim Weber
- **Position:** Lead Developer
- **Bio:** Full-Stack-Entwickler mit Fokus auf skalierbare Lösungen
- **E-Mail:** tim@musterfirma.de
### Lisa Müller
- **Position:** UX Designer
- **Bio:** Kreiert nutzerzentrierte Designs für digitale Produkte
- **E-Mail:** lisa@musterfirma.de
---
## Services
### Beratung
- **Icon:** lightbulb
- **Kurzbeschreibung:** Strategische IT-Beratung für Ihr Unternehmen
- **Beschreibung:** Wir analysieren Ihre Geschäftsprozesse und entwickeln maßgeschneiderte Digitalisierungsstrategien...
### Entwicklung
- **Icon:** code
- **Kurzbeschreibung:** Individuelle Softwareentwicklung
- **Beschreibung:** Unser Entwicklerteam setzt Ihre Ideen in leistungsstarke Anwendungen um...
### Support
- **Icon:** headset
- **Kurzbeschreibung:** Zuverlässiger 24/7 Support
- **Beschreibung:** Unser Support-Team steht Ihnen rund um die Uhr zur Verfügung...
---
## Testimonials
### Testimonial 1
- **Zitat:** "Die Zusammenarbeit mit Musterfirma hat unsere digitale Transformation beschleunigt. Hervorragende Arbeit!"
- **Name:** Dr. Peter Schneider
- **Position:** CTO
- **Unternehmen:** TechCorp AG
- **Rating:** 5
### Testimonial 2
- **Zitat:** "Professionell, zuverlässig und innovativ. Genau der Partner, den wir gesucht haben."
- **Name:** Maria Hofmann
- **Position:** Geschäftsführerin
- **Unternehmen:** Digital Solutions GmbH
- **Rating:** 5
### Testimonial 3
- **Zitat:** "Das Team von Musterfirma versteht es, komplexe Anforderungen in elegante Lösungen zu verwandeln."
- **Name:** Thomas Klein
- **Position:** IT-Leiter
- **Unternehmen:** InnoTech AG
- **Rating:** 5
---
## FAQs
### Wie lange dauert ein typisches Projekt?
Die Projektdauer hängt vom Umfang ab. Kleine Projekte dauern 2-4 Wochen, größere Projekte 3-6 Monate.
### Bieten Sie auch Wartung an?
Ja, wir bieten verschiedene Wartungs- und Support-Pakete an, die auf Ihre Bedürfnisse zugeschnitten sind.
### Wie läuft die Zusammenarbeit ab?
Nach einem kostenlosen Erstgespräch erstellen wir ein Angebot. Bei Beauftragung starten wir mit einer Analysephase.
### Arbeiten Sie auch mit kleinen Unternehmen?
Ja, wir betreuen Unternehmen jeder Größe, vom Startup bis zum Konzern.
---
## Blog-Posts
### Post 1: "Die Zukunft der KI im Mittelstand"
- **Autor:** Max Mustermann
- **Kategorie:** Technologie
- **Tags:** KI, Innovation, Digitalisierung
- **Type:** blog
- **Excerpt:** Wie mittelständische Unternehmen von künstlicher Intelligenz profitieren können...
- **Content:**
- Einleitung zur KI-Revolution
- 3 Anwendungsbeispiele
- Implementierungstipps
- Fazit und Ausblick
- **Status:** published
### Post 2: "5 Tipps für erfolgreiche Digitalisierung"
- **Autor:** Anna Schmidt
- **Kategorie:** Tipps & Tricks
- **Tags:** Digitalisierung, Tipps
- **Type:** blog
- **Excerpt:** Praktische Ratschläge für Unternehmen auf dem Weg zur Digitalisierung...
- **Content:**
- Einleitung
- Tipp 1-5 mit Erklärungen
- Zusammenfassung
- **Status:** published
### Post 3: "Case Study: TechCorp digitalisiert Prozesse"
- **Autor:** Max Mustermann
- **Kategorie:** Case Studies
- **Tags:** Case Study, Digitalisierung
- **Type:** blog
- **Excerpt:** Wie wir TechCorp bei der Prozessdigitalisierung unterstützt haben...
- **Content:**
- Ausgangslage
- Herausforderungen
- Lösung
- Ergebnisse
- **Status:** published
---
## Rechtliche Seiten
### Impressum (/impressum)
- Angaben gemäß § 5 TMG
- Kontaktdaten
- Geschäftsführer
- Handelsregister
- USt-IdNr.
### Datenschutz (/datenschutz)
- DSGVO-konforme Datenschutzerklärung
- Verantwortlicher
- Datenerfassung
- Cookies
- Rechte der Betroffenen
### AGB (/agb)
- Allgemeine Geschäftsbedingungen
---
## Formulare
### Kontaktformular
- Felder: Name, E-Mail, Telefon (optional), Betreff, Nachricht
- Bestätigungs-E-Mail an Absender
- Benachrichtigungs-E-Mail an info@musterfirma.de
```
---
## 7. Wichtige Hinweise für den Prompt
### Pflichtangaben
- [ ] Tenant-Name und Slug
- [ ] Mindestens eine Domain
- [ ] Site-Settings (Logo, Farben)
- [ ] Navigation (Header, Footer)
- [ ] Mindestens eine Seite mit Blocks
### Empfohlene Angaben
- [ ] E-Mail-Konfiguration
- [ ] Team-Mitglieder
- [ ] Services/Leistungen
- [ ] Testimonials
- [ ] FAQ-Einträge
- [ ] Blog-Kategorien und Posts
- [ ] Rechtliche Seiten
### Formatierung
- Verwende Markdown für Strukturierung
- Nutze klare Überschriften und Listen
- Gib Block-Typen explizit an
- Beschreibe Inhalte so detailliert wie möglich
- Gib Beziehungen zwischen Inhalten an (z.B. Post → Kategorie)
### Lokalisierung
- Standard-Sprache ist Deutsch (de)
- Gib bei Bedarf englische Übersetzungen an:
```
**Titel (DE):** Über uns
**Titel (EN):** About Us
```
---
## 8. Ausgabe-Format für Claude
Dein Prompt sollte Claude bitten, folgende Aufgaben auszuführen:
1. **Tenant erstellen** via Payload API
2. **Site-Settings anlegen** mit allen Konfigurationen
3. **Navigationen erstellen** (Header, Footer)
4. **Media-Assets hochladen** (falls URLs bereitgestellt)
5. **Kategorien/Tags anlegen** für Blog
6. **Autoren erstellen** für Blog
7. **Team-Mitglieder anlegen**
8. **Services erstellen**
9. **Testimonials anlegen**
10. **FAQs erstellen**
11. **Seiten mit Blocks erstellen**
12. **Blog-Posts anlegen**
13. **Formulare konfigurieren**
---
## 9. Beispiel-Einleitung für deinen Prompt
```markdown
# Auftrag: Neuen Tenant anlegen und befüllen
Bitte lege im Payload CMS einen neuen Tenant mit folgenden Daten an.
Erstelle alle notwendigen Inhalte (Collections, Seiten, Posts) gemäß
der Spezifikation unten.
Nutze die Payload Local API oder REST API für die Datenerstellung.
Stelle sicher, dass alle Beziehungen korrekt verknüpft werden
(z.B. Posts → Categories, Pages → Blocks).
## Technische Hinweise
- Tenant-ID wird automatisch generiert
- Media-Uploads: Erstelle Platzhalter oder nutze bereitgestellte URLs
- Lexical-Format für Rich-Text-Inhalte verwenden
- Status: 'published' für alle Live-Inhalte
---
[Hier folgen deine Tenant-Daten...]
```
---
## 10. Checkliste vor Prompt-Übergabe
- [ ] Alle Pflichtfelder ausgefüllt?
- [ ] Block-Typen korrekt angegeben?
- [ ] Beziehungen zwischen Inhalten definiert?
- [ ] Bilder/Media beschrieben oder URLs bereitgestellt?
- [ ] Texte vollständig formuliert (nicht nur Platzhalter)?
- [ ] Navigation logisch strukturiert?
- [ ] Rechtliche Seiten berücksichtigt?
- [ ] E-Mail-Adressen korrekt formatiert?
- [ ] Farben als Hex-Werte angegeben?

View file

@ -0,0 +1,378 @@
# SEO-Erweiterung
*Letzte Aktualisierung: 18. Dezember 2025*
## Übersicht
Diese Dokumentation beschreibt die implementierten SEO-Features für das Payload CMS Multi-Tenant System.
**Wichtig:** Frontends verwenden die **Production-API** (cms.c2sgmbh.de) für SEO-Daten, um konsistente Meta-Tags und Structured Data zu gewährleisten.
---
## API-Endpoints für Frontend
### SEO-Daten abrufen
| Endpoint | Beschreibung |
|----------|--------------|
| `GET /api/globals/seo-settings` | Globale SEO-Konfiguration |
| `GET /api/pages?where[slug][equals]=...` | Page-spezifische SEO (meta-Feld) |
| `GET /api/posts?where[slug][equals]=...` | Post-spezifische SEO |
### Beispiel: SEO-Settings laden
```typescript
// Frontend: SEO-Defaults aus Production laden
const seoSettings = await fetch('https://cms.c2sgmbh.de/api/globals/seo-settings')
.then(r => r.json())
// Enthält:
// - metaDefaults (titleSuffix, defaultDescription, defaultImage)
// - organization (name, legalName, logo, foundingDate)
// - contact (email, phone, fax)
// - address (street, city, country, etc.)
// - socialProfiles (Array)
// - localBusiness (type, priceRange, openingHours)
// - robots (indexing, additionalDisallow)
// - verification (google, bing, yandex)
```
### Beispiel: Page-SEO laden
```typescript
// Page mit SEO-Daten laden
const page = await fetch(
'https://cms.c2sgmbh.de/api/pages?where[slug][equals]=about&where[tenant][equals]=4&locale=de'
).then(r => r.json())
// page.docs[0].meta enthält:
// - title (Page-spezifischer Titel)
// - description (Meta-Description)
// - image (OG-Image Override)
// - noIndex, noFollow (Indexierungssteuerung)
```
---
## Implementierte Features
### 1. Dynamische Sitemap (`/sitemap.xml`)
**Datei:** `/src/app/sitemap.ts`
Die Sitemap wird dynamisch aus der Datenbank generiert und enthält:
- Startseite (Priorität: 1.0, Änderungshäufigkeit: täglich)
- Alle veröffentlichten Seiten (Priorität: 0.8, Änderungshäufigkeit: wöchentlich)
- Alle veröffentlichten Posts mit typ-basierter URL (Priorität: 0.6, Änderungshäufigkeit: monatlich)
**URL-Schema für Posts:**
| Post-Typ | URL-Prefix |
|----------|------------|
| blog | `/blog/{slug}` |
| news | `/news/{slug}` |
| press | `/presse/{slug}` |
| announcement | `/aktuelles/{slug}` |
### 2. Robots.txt (`/robots.txt`)
**Datei:** `/src/app/robots.ts`
Konfiguriert Crawler-Zugriff:
```
User-Agent: *
Allow: /
Disallow: /admin
Disallow: /admin/*
Disallow: /api/*
Disallow: /_next/*
Disallow: /media/*
User-Agent: Googlebot
Allow: /
Disallow: /admin
Disallow: /api
Host: https://cms.c2sgmbh.de
Sitemap: https://cms.c2sgmbh.de/sitemap.xml
```
### 3. Structured Data (JSON-LD)
**Datei:** `/src/lib/structuredData.ts`
Bietet Helper-Funktionen für Schema.org-konforme JSON-LD Daten:
#### Verfügbare Funktionen
| Funktion | Beschreibung |
|----------|--------------|
| `generateOrganizationSchema()` | Organization Schema |
| `generateArticleSchema()` | Article Schema für Blog-Posts |
| `generateNewsArticleSchema()` | NewsArticle Schema |
| `generateWebPageSchema()` | WebPage Schema |
| `generateBreadcrumbSchema()` | BreadcrumbList Schema |
| `generateFAQSchema()` | FAQPage Schema |
| `generateReviewSchema()` | Review/Testimonial Schema |
| `generateAggregateRatingSchema()` | AggregateRating Schema |
| `generateLocalBusinessSchema()` | LocalBusiness Schema |
| `generateWebSiteSchema()` | WebSite Schema mit SearchAction |
| `combineSchemas()` | Kombiniert mehrere Schemas |
| `renderJsonLd()` | Sicheres Rendering von JSON-LD |
#### Verwendungsbeispiel (Frontend)
```tsx
// src/components/seo/JsonLd.tsx
import { generateArticleSchema, renderJsonLd } from '@/lib/structuredData'
interface BlogPostProps {
post: {
title: string
excerpt: string
slug: string
publishedAt: string
updatedAt: string
author?: { name: string }
featuredImage?: { url: string }
categories?: Array<{ title: string }>
}
}
export default function BlogPost({ post }: BlogPostProps) {
const schema = generateArticleSchema({
title: post.title,
description: post.excerpt,
slug: post.slug,
publishedAt: post.publishedAt,
updatedAt: post.updatedAt,
author: post.author,
featuredImage: post.featuredImage,
categories: post.categories,
}, 'https://complexcaresolutions.de') // Tenant-Domain
return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: renderJsonLd(schema) }}
/>
<article>...</article>
</>
)
}
```
### 4. SEO Settings Global
**Datei:** `/src/globals/SEOSettings.ts`
Globale SEO-Konfiguration im Admin-Panel unter "Einstellungen > SEO Einstellungen":
#### Meta-Defaults
- Titel-Suffix (z.B. "| Firmenname")
- Standard Meta-Beschreibung
- Standard Social Media Bild
- Standard Keywords
#### Organisation (Schema.org)
- Firmenname & rechtlicher Name
- Unternehmensbeschreibung
- Logo
- Gründungsdatum
#### Kontaktdaten
- E-Mail
- Telefon
- Fax
#### Adresse
- Straße & Hausnummer
- PLZ, Stadt, Region
- Land & Ländercode
#### Geo-Koordinaten
- Breitengrad
- Längengrad
#### Social Media Profile
- Plattform (Facebook, Instagram, Twitter, LinkedIn, YouTube, etc.)
- Profil-URL
#### Local Business
- Schema aktivieren/deaktivieren
- Geschäftstyp (Arztpraxis, Anwaltskanzlei, Restaurant, etc.)
- Preiskategorie (€ bis €€€€)
- Öffnungszeiten
#### Robots & Indexierung
- Indexierung erlauben/verbieten
- Zusätzliche Pfade ausschließen
#### Verifizierungscodes
- Google Search Console
- Bing Webmaster Tools
- Yandex Webmaster
---
## Frontend-Integration
### Next.js Metadata API
```typescript
// src/app/[locale]/page.tsx
import type { Metadata } from 'next'
async function getSeoSettings() {
const res = await fetch('https://cms.c2sgmbh.de/api/globals/seo-settings', {
next: { revalidate: 3600 } // 1 Stunde Cache
})
return res.json()
}
async function getPage(slug: string, tenantId: number, locale: string) {
const res = await fetch(
`https://cms.c2sgmbh.de/api/pages?where[slug][equals]=${slug}&where[tenant][equals]=${tenantId}&locale=${locale}`,
{ next: { revalidate: 60 } }
)
return res.json()
}
export async function generateMetadata({ params }): Promise<Metadata> {
const seoSettings = await getSeoSettings()
const pageData = await getPage(params.slug || 'home', 4, params.locale)
const page = pageData.docs[0]
const title = page?.meta?.title
? `${page.meta.title} ${seoSettings.metaDefaults?.titleSuffix || ''}`
: seoSettings.metaDefaults?.titleSuffix
const description = page?.meta?.description
|| seoSettings.metaDefaults?.defaultDescription
const image = page?.meta?.image?.url
|| seoSettings.metaDefaults?.defaultImage?.url
return {
title,
description,
openGraph: {
title,
description,
images: image ? [{ url: image }] : [],
type: 'website',
},
twitter: {
card: 'summary_large_image',
title,
description,
images: image ? [image] : [],
},
robots: {
index: !page?.meta?.noIndex,
follow: !page?.meta?.noFollow,
},
}
}
```
### Verification Meta Tags
```typescript
// src/app/layout.tsx
export async function generateMetadata(): Promise<Metadata> {
const seoSettings = await getSeoSettings()
return {
verification: {
google: seoSettings.verification?.google,
// Bing und Yandex als other
other: {
'msvalidate.01': seoSettings.verification?.bing,
'yandex-verification': seoSettings.verification?.yandex,
},
},
}
}
```
---
## Multi-Tenant SEO
Jeder Tenant hat eigene SEO-Konfigurationen. Die SEO Settings Global gilt pro Installation, aber Page/Post-SEO ist tenant-spezifisch.
### Tenant-spezifische Domains
| Tenant ID | Domain | Sitemap |
|-----------|--------|---------|
| 1 | porwoll.de | https://porwoll.de/sitemap.xml |
| 4 | complexcaresolutions.de | https://complexcaresolutions.de/sitemap.xml |
| 5 | gunshin.de | https://gunshin.de/sitemap.xml |
### Lokalisierung
SEO-Felder sind lokalisiert (de/en):
```typescript
// Deutscher Content
fetch('https://cms.c2sgmbh.de/api/pages?slug=about&tenant=4&locale=de')
// Englischer Content
fetch('https://cms.c2sgmbh.de/api/pages?slug=about&tenant=4&locale=en')
```
---
## Datenbank-Tabellen
Die Migration `20251130_150000_blocks_tables.ts` erstellt:
- `seo_settings` - Haupttabelle für SEO-Einstellungen
- `seo_settings_meta_defaults_keywords` - Keywords Array
- `seo_settings_social_profiles` - Social Media Profile
- `seo_settings_local_business_opening_hours` - Öffnungszeiten
- `seo_settings_robots_additional_disallow` - Ausgeschlossene Pfade
---
## URLs
### Production (für Frontends)
| Resource | URL |
|----------|-----|
| **API Base** | https://cms.c2sgmbh.de/api |
| **SEO Settings** | https://cms.c2sgmbh.de/api/globals/seo-settings |
| **Sitemap** | https://cms.c2sgmbh.de/sitemap.xml |
| **Robots** | https://cms.c2sgmbh.de/robots.txt |
| **Admin Panel** | https://cms.c2sgmbh.de/admin/globals/seo-settings |
### Development
| Resource | URL |
|----------|-----|
| **API Base** | https://pl.porwoll.tech/api |
| **SEO Settings** | https://pl.porwoll.tech/api/globals/seo-settings |
| **Admin Panel** | https://pl.porwoll.tech/admin/globals/seo-settings |
---
## Checkliste: SEO-Setup pro Tenant
- [ ] SEO Settings im Admin-Panel konfigurieren
- [ ] Organisation (Name, Logo, Beschreibung)
- [ ] Kontaktdaten und Adresse
- [ ] Social Media Profile hinzufügen
- [ ] Local Business aktivieren (falls relevant)
- [ ] Google Search Console Code eintragen
- [ ] JSON-LD in Frontend-Templates einbinden
- [ ] Meta-Tags in Layout integrieren
- [ ] Sitemap bei Google Search Console einreichen
- [ ] robots.txt prüfen
---
*Letzte Aktualisierung: 18. Dezember 2025*

2145
docs/guides/styleguide.md Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,730 @@
# Anleitung: Frontend-Prompt-Erstellung für Payload CMS
*Für Planungs-KIs zur Erstellung von Entwicklungs-Prompts*
---
## Übersicht
Diese Anleitung erklärt, wie du als Planungs-KI einen strukturierten Prompt für die Entwicklung eines Next.js-Frontends erstellst, das Daten aus einem Payload CMS über die REST-API bezieht.
### Architektur-Kontext
```
┌─────────────────────────────────────────────────────────────────┐
│ ARCHITEKTUR │
│ │
│ ┌──────────────────┐ ┌──────────────────────────────┐ │
│ │ PAYLOAD CMS │ │ NEXT.JS FRONTEND │ │
│ │ (Headless) │ REST │ │ │
│ │ │◄────────►│ • Server Components │ │
│ │ cms.c2sgmbh.de │ API │ • Client Components │ │
│ │ /api/* │ │ • API Routes │ │
│ └──────────────────┘ └──────────────────────────────┘ │
│ │
│ Tech-Stack: │
│ • Payload CMS 3.69.0 • Next.js 15.5.9 │
│ • PostgreSQL 17 • React 19.2.3 │
│ • Multi-Tenant-fähig • TypeScript │
│ • Lokalisierung: DE/EN • Tailwind CSS │
└─────────────────────────────────────────────────────────────────┘
```
---
## Teil 1: Benötigte Dateien für die Planungs-KI
### Pflicht-Dateien (unbedingt bereitstellen)
| Datei | Zweck | Wo erhältlich |
|-------|-------|---------------|
| **OpenAPI Spec** | Vollständige API-Dokumentation mit allen Endpoints, Schemas und Parametern | `https://cms.c2sgmbh.de/api/openapi.json` |
| **FRONTEND.md** | Umgebungskonfiguration, API-URLs, Development-Workflow | Projektdokumentation |
| **API_ANLEITUNG.md** | Authentifizierung, Rate-Limits, Collection-Übersicht | Projektdokumentation |
### Empfohlene Zusatz-Dateien
| Datei | Zweck |
|-------|-------|
| **UNIVERSAL_FEATURES.md** | Block-Typen, Collection-Felder, Layout-Optionen |
| **KONZEPT-KI-ANLEITUNG.md** | Block-Konfigurationen mit JSON-Beispielen |
| **payload-types.ts** | TypeScript-Typen für alle Collections (generiert aus Payload) |
### Optional für komplexe Projekte
| Datei | Zweck |
|-------|-------|
| **Analytics.md** | Umami-Integration, Event-Tracking |
| **SEO_ERWEITERUNG.md** | SEO-Konfiguration, Meta-Tags, Structured Data |
---
## Teil 2: API-Struktur verstehen
### Basis-URLs
```
Production API: https://cms.c2sgmbh.de/api
Development API: https://pl.porwoll.tech/api
Swagger UI: https://cms.c2sgmbh.de/api/docs
OpenAPI JSON: https://cms.c2sgmbh.de/api/openapi.json
```
### Tenant-System (Multi-Mandanten)
**Kritisch:** Jeder API-Call muss den Tenant filtern!
```typescript
// RICHTIG: Mit Tenant-Filter
const response = await fetch(
'https://cms.c2sgmbh.de/api/pages?where[tenant][equals]=4&where[slug][equals]=home'
)
// FALSCH: Ohne Tenant → 403 Forbidden oder leere Ergebnisse
const response = await fetch(
'https://cms.c2sgmbh.de/api/pages?where[slug][equals]=home'
)
```
### Tenant-IDs
| ID | Name | Slug | Domain |
|----|------|------|--------|
| 1 | porwoll.de | porwoll | porwoll.de |
| 4 | Complex Care Solutions GmbH | c2s | complexcaresolutions.de |
| 5 | Gunshin | gunshin | gunshin.de |
| ... | (weitere Tenants) | ... | ... |
### Lokalisierung
```typescript
// Deutsche Inhalte (Standard)
fetch('/api/posts?locale=de')
// Englische Inhalte
fetch('/api/posts?locale=en')
// Alle Sprachen gleichzeitig
fetch('/api/posts?locale=all')
```
---
## Teil 3: Collections-Referenz
### Core Collections
| Collection | Slug | Beschreibung | Öffentlich |
|------------|------|--------------|------------|
| Pages | `pages` | Seiten mit Block-Layouts | Ja |
| Media | `media` | Bilder, Dokumente (11 responsive Sizes) | Ja |
| Tenants | `tenants` | Mandanten-Verwaltung | Nein |
| Users | `users` | Benutzer mit Tenant-Zuordnung | Nein |
### Content Collections
| Collection | Slug | Beschreibung |
|------------|------|--------------|
| Posts | `posts` | Blog, News, Presse, Ankündigungen |
| Categories | `categories` | Kategorien für Posts |
| Tags | `tags` | Tags für Posts |
| Authors | `authors` | Autoren für Posts |
| Testimonials | `testimonials` | Kundenbewertungen |
| FAQs | `faqs` | Häufig gestellte Fragen |
| Team | `team` | Team-Mitglieder |
| Services | `services` | Leistungen/Dienstleistungen |
| Service Categories | `service-categories` | Kategorien für Services |
### Portfolio & Media
| Collection | Slug | Beschreibung |
|------------|------|--------------|
| Portfolios | `portfolios` | Portfolio-Galerien |
| Portfolio Categories | `portfolio-categories` | Kategorien |
| Videos | `videos` | Video-Bibliothek (YouTube/Vimeo/Upload) |
| Video Categories | `video-categories` | Kategorien |
### Kommunikation
| Collection | Slug | Beschreibung |
|------------|------|--------------|
| Newsletter Subscribers | `newsletter-subscribers` | Abonnenten (DSGVO) |
| Form Submissions | `form-submissions` | Formular-Eingaben |
| Contact Requests | `contact-requests` | Kontaktanfragen |
### Konfiguration
| Collection | Slug | Beschreibung |
|------------|------|--------------|
| Site Settings | `site-settings` | Logo, Farben, SEO-Defaults |
| Navigations | `navigations` | Header, Footer, Mobile-Nav |
| Cookie Configurations | `cookie-configurations` | Cookie-Banner-Settings |
| Redirects | `redirects` | URL-Weiterleitungen |
---
## Teil 4: Block-Typen für Pages
Die `pages` Collection verwendet ein Block-basiertes Layout-System. Jede Page hat ein `layout`-Array mit Blocks.
### Verfügbare Block-Typen
#### Layout-Blocks
| Block | Slug | Beschreibung |
|-------|------|--------------|
| Hero | `hero-block` | Hauptbanner mit Bild, Text, CTA |
| Hero Slider | `hero-slider-block` | Karussell mit mehreren Slides |
| Text | `text-block` | Rich-Text-Inhalt (Lexical) |
| Image-Text | `image-text-block` | Bild + Text nebeneinander |
| Card Grid | `card-grid-block` | Karten im Grid-Layout |
| CTA | `cta-block` | Call-to-Action Sektion |
| Divider | `divider-block` | Visueller Trenner |
#### Content-Blocks (laden Collection-Daten)
| Block | Slug | Datenquelle |
|-------|------|-------------|
| Posts List | `posts-list-block` | Posts Collection |
| Testimonials | `testimonials-block` | Testimonials Collection |
| FAQ | `faq-block` | FAQs Collection |
| Team | `team-block` | Team Collection |
| Services | `services-block` | Services Collection |
| Newsletter | `newsletter-block` | Newsletter-Anmeldung |
#### Interaktive Blocks
| Block | Slug | Beschreibung |
|-------|------|--------------|
| Contact Form | `contact-form-block` | Kontaktformular |
| Timeline | `timeline-block` | Chronologische Darstellung |
| Process Steps | `process-steps-block` | Prozess-Visualisierung |
| Video | `video-block` | Video-Einbettung |
### Block-Konfiguration Beispiele
```json
// Hero Block
{
"blockType": "hero-block",
"heading": "Willkommen",
"subheading": "Untertitel",
"backgroundImage": "<media-id>",
"ctaText": "Mehr erfahren",
"ctaLink": "/ueber-uns",
"alignment": "center",
"overlay": true,
"overlayOpacity": 0.5
}
// Posts List Block
{
"blockType": "posts-list-block",
"heading": "Neueste Artikel",
"type": "blog",
"categories": ["<category-id>"],
"limit": 6,
"layout": "grid",
"showPagination": true
}
// FAQ Block
{
"blockType": "faq-block",
"heading": "Häufige Fragen",
"displayMode": "selected",
"selectedFaqs": ["<faq-id-1>", "<faq-id-2>"],
"layout": "accordion",
"expandFirst": true,
"showSchema": true
}
// Testimonials Block
{
"blockType": "testimonials-block",
"heading": "Kundenstimmen",
"displayMode": "all",
"limit": 6,
"layout": "slider"
}
```
---
## Teil 5: API-Patterns für Frontend
### Seite laden
```typescript
// lib/api.ts
const PAYLOAD_URL = process.env.NEXT_PUBLIC_PAYLOAD_URL
const TENANT_ID = process.env.NEXT_PUBLIC_TENANT_ID
export async function getPage(slug: string, locale = 'de') {
const res = await fetch(
`${PAYLOAD_URL}/api/pages?` +
`where[tenant][equals]=${TENANT_ID}&` +
`where[slug][equals]=${slug}&` +
`where[status][equals]=published&` +
`locale=${locale}&` +
`depth=2`,
{ next: { revalidate: 60 } }
)
const data = await res.json()
return data.docs[0] || null
}
```
### Posts laden
```typescript
export async function getPosts(options: {
type?: 'blog' | 'news' | 'press'
category?: string
limit?: number
page?: number
locale?: string
}) {
const params = new URLSearchParams({
'where[tenant][equals]': TENANT_ID,
'where[status][equals]': 'published',
'sort': '-publishedAt',
'limit': String(options.limit || 10),
'page': String(options.page || 1),
'locale': options.locale || 'de',
'depth': '1'
})
if (options.type) {
params.append('where[type][equals]', options.type)
}
if (options.category) {
params.append('where[categories][contains]', options.category)
}
const res = await fetch(`${PAYLOAD_URL}/api/posts?${params}`)
return res.json()
}
```
### Navigation laden
```typescript
export async function getNavigation(type: 'header' | 'footer' | 'mobile') {
const res = await fetch(
`${PAYLOAD_URL}/api/navigations?` +
`where[tenant][equals]=${TENANT_ID}&` +
`where[type][equals]=${type}&` +
`depth=2`
)
const data = await res.json()
return data.docs[0] || null
}
```
### Site Settings laden
```typescript
export async function getSiteSettings() {
const res = await fetch(
`${PAYLOAD_URL}/api/site-settings?` +
`where[tenant][equals]=${TENANT_ID}&` +
`depth=2`
)
const data = await res.json()
return data.docs[0] || null
}
```
### Newsletter-Anmeldung
```typescript
export async function subscribeNewsletter(email: string, firstName?: string) {
const res = await fetch(`${PAYLOAD_URL}/api/newsletter/subscribe`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email,
firstName,
tenantId: Number(TENANT_ID),
source: 'website'
})
})
return res.json()
}
```
### Kontaktformular
```typescript
export async function submitContactForm(data: {
name: string
email: string
phone?: string
subject: string
message: string
}) {
const res = await fetch(`${PAYLOAD_URL}/api/form-submissions`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
form: 1, // Form-ID aus Payload
submissionData: [
{ field: 'name', value: data.name },
{ field: 'email', value: data.email },
{ field: 'phone', value: data.phone || '' },
{ field: 'subject', value: data.subject },
{ field: 'message', value: data.message }
]
})
})
return res.json()
}
```
---
## Teil 6: Prompt-Struktur für Frontend-Entwicklung
### Template
```markdown
# Frontend [Projektname] - Entwicklungs-Prompt
## Kontext
**Projektverzeichnis:** /pfad/zum/frontend
**Tech-Stack:** Next.js 15.5.9, React 19.2.3, TypeScript, Tailwind CSS
**API-Basis:** https://cms.c2sgmbh.de/api
**Tenant-ID:** [ID]
**Tenant-Slug:** [slug]
**Domain:** [domain.de]
### Referenz-Dokumente
- OpenAPI Spec: [Pfad oder URL zur openapi.json]
- Payload Types: [Pfad zur payload-types.ts]
---
## Environment Variables
```env
NEXT_PUBLIC_PAYLOAD_URL=https://cms.c2sgmbh.de
NEXT_PUBLIC_API_URL=https://cms.c2sgmbh.de/api
NEXT_PUBLIC_TENANT_ID=[ID]
NEXT_PUBLIC_TENANT_SLUG=[slug]
NEXT_PUBLIC_SITE_URL=https://[domain.de]
# Analytics (optional)
NEXT_PUBLIC_UMAMI_HOST=https://analytics.c2sgmbh.de
NEXT_PUBLIC_UMAMI_WEBSITE_ID=[website-id]
```
---
## Aufgaben
### 1. Projektstruktur
```
src/
├── app/
│ ├── layout.tsx # Root-Layout mit Header/Footer
│ ├── page.tsx # Startseite
│ ├── [slug]/
│ │ └── page.tsx # Dynamische Seiten
│ ├── blog/
│ │ ├── page.tsx # Blog-Übersicht
│ │ └── [slug]/
│ │ └── page.tsx # Blog-Artikel
│ └── api/ # Optional: Proxy-Routes
├── components/
│ ├── layout/
│ │ ├── Header.tsx
│ │ ├── Footer.tsx
│ │ └── Navigation.tsx
│ ├── blocks/ # Block-Komponenten (je Block)
│ │ ├── HeroBlock.tsx
│ │ ├── TextBlock.tsx
│ │ ├── ImageTextBlock.tsx
│ │ ├── CardGridBlock.tsx
│ │ ├── PostsListBlock.tsx
│ │ ├── TestimonialsBlock.tsx
│ │ ├── FAQBlock.tsx
│ │ ├── TeamBlock.tsx
│ │ ├── ServicesBlock.tsx
│ │ ├── NewsletterBlock.tsx
│ │ ├── CTABlock.tsx
│ │ ├── ContactFormBlock.tsx
│ │ └── index.tsx # Block-Renderer
│ └── ui/ # Wiederverwendbare UI-Komponenten
├── lib/
│ ├── api.ts # API-Funktionen
│ ├── utils.ts # Hilfsfunktionen
│ └── types.ts # TypeScript-Typen
└── styles/
└── globals.css # Tailwind + Custom Styles
```
### 2. API-Integration
#### 2.1 API-Client erstellen
**Datei:** `src/lib/api.ts`
Implementiere Funktionen für:
- `getPage(slug, locale)` - Seite laden
- `getPosts(options)` - Blog-Posts laden
- `getNavigation(type)` - Navigation laden
- `getSiteSettings()` - Site-Settings laden
- `getCategories()` - Kategorien laden
- `subscribeNewsletter(email, firstName)` - Newsletter
- `submitForm(formId, data)` - Formular absenden
**Wichtig:**
- Immer `where[tenant][equals]=${TENANT_ID}` mitgeben
- Caching mit `next: { revalidate: 60 }` für statische Daten
- Error-Handling implementieren
### 3. Block-Komponenten
Für jeden Block-Typ eine React-Komponente erstellen.
#### 3.1 Block-Renderer
**Datei:** `src/components/blocks/index.tsx`
```typescript
import { HeroBlock } from './HeroBlock'
import { TextBlock } from './TextBlock'
// ... weitere Imports
const blockComponents = {
'hero-block': HeroBlock,
'text-block': TextBlock,
'image-text-block': ImageTextBlock,
// ... weitere Mappings
}
export function BlockRenderer({ blocks }) {
return (
<>
{blocks?.map((block, index) => {
const Component = blockComponents[block.blockType]
if (!Component) return null
return <Component key={index} {...block} />
})}
</>
)
}
```
#### 3.2 [Block-Name] Block
**Datei:** `src/components/blocks/[BlockName]Block.tsx`
[Für jeden benötigten Block beschreiben:]
- Props-Interface (aus API-Response)
- Tailwind-Styling
- Responsive Verhalten
- Interaktivität (falls Client Component)
### 4. Seiten
#### 4.1 Layout
**Datei:** `src/app/layout.tsx`
- Header mit Navigation laden
- Footer mit Navigation laden
- Site-Settings für Meta-Tags
- Cookie-Banner (DSGVO)
#### 4.2 Dynamische Seiten
**Datei:** `src/app/[slug]/page.tsx`
- Seite aus API laden
- Metadata generieren
- BlockRenderer für Layout
- 404 bei nicht gefundener Seite
### 5. SEO & Meta
- `generateMetadata()` für jede Seite
- Open Graph Tags
- JSON-LD für FAQ-Blocks (Schema.org)
- Sitemap aus `/api/sitemap.xml`
- robots.txt
### 6. Cookie-Banner
- Cookie-Konfiguration aus API laden
- Consent-State in localStorage
- Consent an Backend loggen
- Analytics erst nach Consent laden
---
## Erfolgskriterien
- [ ] `pnpm lint` ohne Errors
- [ ] `pnpm build` erfolgreich
- [ ] Alle Block-Typen gerendert
- [ ] Navigation funktioniert
- [ ] Blog-Übersicht und Detailseiten
- [ ] Kontaktformular sendet Daten
- [ ] Newsletter-Anmeldung funktioniert
- [ ] SEO-Meta-Tags korrekt
- [ ] Mobile-responsive
- [ ] Lighthouse Score > 90
---
## Escape Hatch
Nach 15 Iterationen ohne Fortschritt:
- Dokumentiere Blocker in BLOCKERS.md
- Liste versuchte Lösungen auf
- Output <promise>BLOCKED</promise>
---
## Fertig?
Wenn ALLE Aufgaben erledigt sind:
<promise>FRONTEND_COMPLETE</promise>
```
---
## Teil 7: Checkliste für Planungs-KI
### Vor der Prompt-Erstellung
- [ ] OpenAPI Spec liegt vor (`openapi.json`)
- [ ] Tenant-ID und Slug bekannt
- [ ] Domain(s) bekannt
- [ ] Design-Vorgaben/Mockups vorhanden (optional)
- [ ] Block-Typen für Seiten definiert
### Im Prompt enthalten
- [ ] Projektverzeichnis angegeben
- [ ] Tech-Stack spezifiziert
- [ ] Environment Variables definiert
- [ ] API-Patterns mit Tenant-Filter
- [ ] Alle benötigten Block-Komponenten gelistet
- [ ] Seiten-Struktur beschrieben
- [ ] Erfolgskriterien messbar formuliert
- [ ] Escape Hatch für Blockaden
- [ ] Promise-Token am Ende
### Dateien zur Übergabe an Entwicklungs-KI
1. **PROMPT.md** - Dein erstellter Prompt
2. **openapi.json** - API-Spezifikation
3. **payload-types.ts** - TypeScript-Typen (falls verfügbar)
4. **Design-Assets** - Mockups, Styleguide (falls vorhanden)
---
## Teil 8: Beispiel-Prompt (Kurzversion)
```markdown
# Frontend porwoll.de - Phase 1
## Kontext
- **Verzeichnis:** /home/frontend/porwoll.de
- **API:** https://cms.c2sgmbh.de/api
- **Tenant-ID:** 1
- **Referenz:** /docs/openapi.json
## Aufgaben
### 1. Setup
- Next.js 15 Projekt initialisieren
- Tailwind CSS konfigurieren
- Environment Variables setzen
### 2. API-Client
- `src/lib/api.ts` mit allen Fetch-Funktionen
- Tenant-Filter in jedem Call
### 3. Blocks
Komponenten für:
- HeroBlock
- TextBlock
- ImageTextBlock
- PostsListBlock
- FAQBlock
- NewsletterBlock
- CTABlock
- ContactFormBlock
### 4. Seiten
- `/` - Startseite
- `/[slug]` - Dynamische Seiten
- `/blog` - Blog-Übersicht
- `/blog/[slug]` - Blog-Artikel
### 5. Layout
- Header mit Navigation
- Footer mit Navigation
- Cookie-Banner
## Erfolgskriterien
- [ ] Build erfolgreich
- [ ] Alle Blocks rendern
- [ ] API-Calls funktionieren
## Fertig?
<promise>PHASE1_DONE</promise>
```
---
## Anhang: API Query-Parameter Referenz
### Filtering (where)
```
where[field][equals]=value
where[field][not_equals]=value
where[field][contains]=value
where[field][in]=value1,value2
where[field][greater_than]=value
where[field][less_than]=value
where[field][exists]=true
```
### Sorting
```
sort=fieldName # Aufsteigend
sort=-fieldName # Absteigend
```
### Pagination
```
limit=10 # Ergebnisse pro Seite
page=1 # Seitennummer
```
### Depth (Relations)
```
depth=0 # Keine Relations laden
depth=1 # Eine Ebene Relations
depth=2 # Zwei Ebenen (Default)
```
### Lokalisierung
```
locale=de # Deutsche Inhalte
locale=en # Englische Inhalte
locale=all # Alle Sprachen
```
---
*Anleitung erstellt: Januar 2026*
*Für: Complex Care Solutions GmbH - Payload CMS Multi-Tenant*