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