mirror of
https://github.com/complexcaresolutions/frontend.blogwoman.de.git
synced 2026-03-17 22:03:58 +00:00
- 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>
1114 lines
29 KiB
Markdown
1114 lines
29 KiB
Markdown
# Frontend BlogWoman.de - Entwicklungs-Prompt
|
||
|
||
*Erstellt: 20. Januar 2026*
|
||
|
||
---
|
||
|
||
## 1. Kontext & Konfiguration
|
||
|
||
**Projektverzeichnis:** `/home/frontend/frontend.blogwoman.de`
|
||
**Tech-Stack:** Next.js 15.5.9, React 19.2.3, TypeScript, Tailwind CSS
|
||
**API-Basis:** https://cms.c2sgmbh.de/api
|
||
**Tenant-ID:** 9
|
||
**Tenant-Slug:** blogwoman
|
||
**Domain:** blogwoman.de
|
||
|
||
### Referenz-Dokumente
|
||
|
||
| Dokument | Pfad / URL |
|
||
|----------|------------|
|
||
| OpenAPI Spec | https://cms.c2sgmbh.de/api/openapi.json |
|
||
| Swagger UI | https://cms.c2sgmbh.de/api/docs |
|
||
| Styleguide | `/docs/guides/styleguide.md` |
|
||
| Universal Features | `/docs/architecture/UNIVERSAL_FEATURES.md` |
|
||
| API-Anleitung | `/docs/api/API_ANLEITUNG.md` |
|
||
| SEO-Erweiterung | `/docs/guides/SEO_ERWEITERUNG.md` |
|
||
| Analytics | `/docs/architecture/Analytics.md` |
|
||
|
||
### Zielgruppe
|
||
|
||
**Persona:** Berufstätige Mütter (35-45 Jahre)
|
||
- Wenig Zeit → **Schnell scanbar, klare CTAs**
|
||
- Qualität schätzen → **Premium-Look, keine billige Ästhetik**
|
||
- Professionalität gewohnt → **Seriös, aber nicht steif**
|
||
|
||
---
|
||
|
||
## 2. Environment Variables
|
||
|
||
```env
|
||
# API-Endpunkte (PRODUKTION)
|
||
NEXT_PUBLIC_PAYLOAD_URL=https://cms.c2sgmbh.de
|
||
NEXT_PUBLIC_API_URL=https://cms.c2sgmbh.de/api
|
||
|
||
# Tenant-Konfiguration
|
||
NEXT_PUBLIC_TENANT_ID=9
|
||
NEXT_PUBLIC_TENANT_SLUG=blogwoman
|
||
NEXT_PUBLIC_SITE_URL=https://blogwoman.de
|
||
|
||
# Analytics (Umami)
|
||
NEXT_PUBLIC_UMAMI_HOST=https://analytics.c2sgmbh.de
|
||
NEXT_PUBLIC_UMAMI_WEBSITE_ID=<website-id>
|
||
```
|
||
|
||
---
|
||
|
||
## 3. Design-System (Styleguide-Referenz)
|
||
|
||
### 3.1 Design-Philosophie: "Editorial Warmth"
|
||
|
||
- **Premium, nicht protzig** – Qualität durch Zurückhaltung
|
||
- **Warm, nicht kalt** – Einladend, nicht steril
|
||
- **Klar, nicht überladen** – Luft zum Atmen, klare Hierarchien
|
||
- **System, nicht Chaos** – Strukturiert, wiedererkennbar, konsistent
|
||
|
||
### 3.2 Farbsystem
|
||
|
||
#### Primärfarben (60/30/10 Regel)
|
||
|
||
| Name | Hex | Verwendung |
|
||
|------|-----|------------|
|
||
| **Ivory** | `#F7F3EC` | Hintergründe, große Flächen (60%) |
|
||
| **Sand/Camel** | `#C6A47E` | Cards, Module, Labels (30%) |
|
||
| **Espresso** | `#2B2520` | Text, Headlines, Kontrast |
|
||
|
||
#### Akzentfarben (10%)
|
||
|
||
| Name | Hex | Verwendung |
|
||
|------|-----|------------|
|
||
| **Muted Brass** | `#B08D57` | Primary Buttons, Highlights, Premium-Signal |
|
||
| **Bordeaux/Wine** | `#6B1F2B` | Pleasure-Akzent, P&L-Serie |
|
||
| **Rosé** | `#D4A5A5` | SPARK-Serie, feminine Akzente |
|
||
| **Gold** | `#C9A227` | Inner Circle, Premium-Badges |
|
||
|
||
#### Neutrale Farben
|
||
|
||
| Name | Hex | Verwendung |
|
||
|------|-----|------------|
|
||
| **Soft White** | `#FBF8F3` | Cards, helle Flächen |
|
||
| **Warm Gray** | `#DDD4C7` | Borders, Dividers |
|
||
| **Warm Gray Dark** | `#B8ADA0` | Placeholder-Text |
|
||
|
||
#### Funktionsfarben
|
||
|
||
| Name | Hex | Verwendung |
|
||
|------|-----|------------|
|
||
| **Success** | `#4A7C59` | Erfolg, Bestätigung |
|
||
| **Warning** | `#D4A574` | Hinweise |
|
||
| **Error** | `#8B3A3A` | Fehler |
|
||
| **Info** | `#6B8E9B` | Informationen |
|
||
|
||
### 3.3 CSS Custom Properties
|
||
|
||
```css
|
||
:root {
|
||
/* Primärfarben */
|
||
--color-ivory: #F7F3EC;
|
||
--color-sand: #C6A47E;
|
||
--color-espresso: #2B2520;
|
||
|
||
/* Akzentfarben */
|
||
--color-brass: #B08D57;
|
||
--color-bordeaux: #6B1F2B;
|
||
--color-rose: #D4A5A5;
|
||
--color-gold: #C9A227;
|
||
|
||
/* Neutrale */
|
||
--color-soft-white: #FBF8F3;
|
||
--color-warm-gray: #DDD4C7;
|
||
--color-warm-gray-dark: #B8ADA0;
|
||
|
||
/* Semantische Aliase */
|
||
--color-background: var(--color-ivory);
|
||
--color-surface: var(--color-soft-white);
|
||
--color-text-primary: var(--color-espresso);
|
||
--color-text-secondary: var(--color-warm-gray-dark);
|
||
--color-border: var(--color-warm-gray);
|
||
--color-primary: var(--color-brass);
|
||
--color-primary-hover: #9E7E4D;
|
||
}
|
||
```
|
||
|
||
### 3.4 Typografie
|
||
|
||
| Einsatz | Schrift | Import |
|
||
|---------|---------|--------|
|
||
| **Headlines** | Playfair Display | `family=Playfair+Display:wght@400;500;600;700` |
|
||
| **Body/UI** | Inter | `family=Inter:wght@400;500;600;700` |
|
||
|
||
```css
|
||
:root {
|
||
--font-headline: 'Playfair Display', Georgia, serif;
|
||
--font-body: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
||
}
|
||
```
|
||
|
||
**Wichtig:** Keine CAPSLOCK Headlines – wirkt unpremium!
|
||
|
||
### 3.5 Spacing-System (8px Base)
|
||
|
||
```css
|
||
:root {
|
||
--space-1: 0.25rem; /* 4px */
|
||
--space-2: 0.5rem; /* 8px */
|
||
--space-3: 0.75rem; /* 12px */
|
||
--space-4: 1rem; /* 16px */
|
||
--space-6: 1.5rem; /* 24px */
|
||
--space-8: 2rem; /* 32px */
|
||
--space-12: 3rem; /* 48px */
|
||
--space-16: 4rem; /* 64px */
|
||
--space-20: 5rem; /* 80px */
|
||
}
|
||
```
|
||
|
||
### 3.6 Border Radius
|
||
|
||
```css
|
||
:root {
|
||
--radius-sm: 4px;
|
||
--radius-md: 8px;
|
||
--radius-lg: 12px; /* Buttons */
|
||
--radius-xl: 16px;
|
||
--radius-2xl: 20px; /* Cards */
|
||
--radius-full: 9999px; /* Pills, Avatare */
|
||
}
|
||
```
|
||
|
||
### 3.7 Schatten (warm getönt)
|
||
|
||
```css
|
||
:root {
|
||
--shadow-sm: 0 1px 2px rgba(43, 37, 32, 0.05);
|
||
--shadow-md: 0 4px 12px rgba(43, 37, 32, 0.08);
|
||
--shadow-lg: 0 8px 24px rgba(43, 37, 32, 0.1);
|
||
--shadow-xl: 0 12px 40px rgba(43, 37, 32, 0.12);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 4. Projektstruktur
|
||
|
||
```
|
||
src/
|
||
├── app/
|
||
│ ├── layout.tsx # Root-Layout mit Header/Footer/Analytics
|
||
│ ├── page.tsx # Startseite
|
||
│ ├── [slug]/
|
||
│ │ └── page.tsx # Dynamische Seiten
|
||
│ ├── blog/
|
||
│ │ ├── page.tsx # Blog-Übersicht
|
||
│ │ └── [slug]/
|
||
│ │ └── page.tsx # Blog-Artikel
|
||
│ ├── serien/ # BlogWoman-spezifisch
|
||
│ │ ├── page.tsx # Serien-Übersicht
|
||
│ │ └── [slug]/
|
||
│ │ └── page.tsx # Serien-Detailseite
|
||
│ ├── favoriten/ # BlogWoman-spezifisch
|
||
│ │ └── page.tsx # Affiliate-Produkte
|
||
│ └── api/ # Optional: Proxy-Routes
|
||
├── components/
|
||
│ ├── layout/
|
||
│ │ ├── Header.tsx
|
||
│ │ ├── Footer.tsx
|
||
│ │ ├── Navigation.tsx
|
||
│ │ ├── MobileMenu.tsx
|
||
│ │ └── CookieBanner.tsx
|
||
│ ├── blocks/ # Block-Komponenten
|
||
│ │ ├── HeroBlock.tsx
|
||
│ │ ├── HeroSliderBlock.tsx
|
||
│ │ ├── TextBlock.tsx
|
||
│ │ ├── ImageTextBlock.tsx
|
||
│ │ ├── CardGridBlock.tsx
|
||
│ │ ├── CTABlock.tsx
|
||
│ │ ├── DividerBlock.tsx
|
||
│ │ ├── PostsListBlock.tsx
|
||
│ │ ├── TestimonialsBlock.tsx
|
||
│ │ ├── FAQBlock.tsx
|
||
│ │ ├── NewsletterBlock.tsx
|
||
│ │ ├── ContactFormBlock.tsx
|
||
│ │ ├── VideoBlock.tsx
|
||
│ │ ├── TimelineBlock.tsx
|
||
│ │ ├── ProcessStepsBlock.tsx
|
||
│ │ │
|
||
│ │ │ # BlogWoman-spezifische Blocks
|
||
│ │ ├── FavoritesBlock.tsx
|
||
│ │ ├── SeriesBlock.tsx
|
||
│ │ ├── SeriesDetailBlock.tsx
|
||
│ │ ├── VideoEmbedBlock.tsx
|
||
│ │ ├── FeaturedContentBlock.tsx
|
||
│ │ │
|
||
│ │ └── index.tsx # Block-Renderer
|
||
│ ├── ui/ # Wiederverwendbare UI-Komponenten
|
||
│ │ ├── Button.tsx
|
||
│ │ ├── Card.tsx
|
||
│ │ ├── VideoCard.tsx
|
||
│ │ ├── ProductCard.tsx
|
||
│ │ ├── SeriesPill.tsx
|
||
│ │ ├── Badge.tsx
|
||
│ │ ├── Input.tsx
|
||
│ │ ├── Textarea.tsx
|
||
│ │ └── ...
|
||
│ └── analytics/
|
||
│ └── UmamiScript.tsx
|
||
├── lib/
|
||
│ ├── api.ts # API-Funktionen
|
||
│ ├── utils.ts # Hilfsfunktionen
|
||
│ ├── types.ts # TypeScript-Typen
|
||
│ └── structuredData.ts # JSON-LD Helpers
|
||
├── hooks/
|
||
│ ├── useAnalytics.ts
|
||
│ └── useCookieConsent.ts
|
||
├── config/
|
||
│ └── analytics.ts
|
||
└── styles/
|
||
└── globals.css # Tailwind + Custom Properties
|
||
```
|
||
|
||
---
|
||
|
||
## 5. API-Client Implementation
|
||
|
||
**Datei:** `src/lib/api.ts`
|
||
|
||
```typescript
|
||
const PAYLOAD_URL = process.env.NEXT_PUBLIC_PAYLOAD_URL
|
||
const TENANT_ID = process.env.NEXT_PUBLIC_TENANT_ID
|
||
|
||
// KRITISCH: Immer Tenant-Filter verwenden!
|
||
// Ohne Tenant-Filter: 403 Forbidden oder leere Ergebnisse
|
||
|
||
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
|
||
}
|
||
|
||
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()
|
||
}
|
||
|
||
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
|
||
}
|
||
|
||
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
|
||
}
|
||
|
||
// BlogWoman-spezifisch: Favorites
|
||
export async function getFavorites(options: {
|
||
category?: 'fashion' | 'beauty' | 'travel' | 'tech' | 'home'
|
||
badge?: 'investment-piece' | 'daily-driver' | 'grfi-approved' | 'new' | 'bestseller'
|
||
limit?: number
|
||
locale?: string
|
||
}) {
|
||
const params = new URLSearchParams({
|
||
'where[tenant][equals]': TENANT_ID!,
|
||
'where[isActive][equals]': 'true',
|
||
'limit': String(options.limit || 12),
|
||
'locale': options.locale || 'de',
|
||
'depth': '1'
|
||
})
|
||
|
||
if (options.category) {
|
||
params.append('where[category][equals]', options.category)
|
||
}
|
||
if (options.badge) {
|
||
params.append('where[badge][equals]', options.badge)
|
||
}
|
||
|
||
const res = await fetch(`${PAYLOAD_URL}/api/favorites?${params}`)
|
||
return res.json()
|
||
}
|
||
|
||
// BlogWoman-spezifisch: Series
|
||
export async function getSeries(options?: {
|
||
limit?: number
|
||
locale?: string
|
||
}) {
|
||
const params = new URLSearchParams({
|
||
'where[tenant][equals]': TENANT_ID!,
|
||
'where[isActive][equals]': 'true',
|
||
'locale': options?.locale || 'de',
|
||
'depth': '2'
|
||
})
|
||
|
||
const res = await fetch(`${PAYLOAD_URL}/api/series?${params}`)
|
||
return res.json()
|
||
}
|
||
|
||
export async function getSeriesBySlug(slug: string, locale = 'de') {
|
||
const res = await fetch(
|
||
`${PAYLOAD_URL}/api/series?` +
|
||
`where[tenant][equals]=${TENANT_ID}&` +
|
||
`where[slug][equals]=${slug}&` +
|
||
`locale=${locale}&` +
|
||
`depth=2`
|
||
)
|
||
const data = await res.json()
|
||
return data.docs[0] || null
|
||
}
|
||
|
||
// Newsletter-Anmeldung
|
||
export async function subscribeNewsletter(email: string, firstName?: string, source?: 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: source || 'website'
|
||
})
|
||
})
|
||
return res.json()
|
||
}
|
||
|
||
// Kontaktformular
|
||
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()
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 6. Block-Komponenten
|
||
|
||
### 6.1 Block-Renderer
|
||
|
||
**Datei:** `src/components/blocks/index.tsx`
|
||
|
||
```typescript
|
||
import { HeroBlock } from './HeroBlock'
|
||
import { HeroSliderBlock } from './HeroSliderBlock'
|
||
import { TextBlock } from './TextBlock'
|
||
import { ImageTextBlock } from './ImageTextBlock'
|
||
import { CardGridBlock } from './CardGridBlock'
|
||
import { CTABlock } from './CTABlock'
|
||
import { DividerBlock } from './DividerBlock'
|
||
import { PostsListBlock } from './PostsListBlock'
|
||
import { TestimonialsBlock } from './TestimonialsBlock'
|
||
import { FAQBlock } from './FAQBlock'
|
||
import { NewsletterBlock } from './NewsletterBlock'
|
||
import { ContactFormBlock } from './ContactFormBlock'
|
||
import { VideoBlock } from './VideoBlock'
|
||
import { TimelineBlock } from './TimelineBlock'
|
||
import { ProcessStepsBlock } from './ProcessStepsBlock'
|
||
// BlogWoman-spezifisch
|
||
import { FavoritesBlock } from './FavoritesBlock'
|
||
import { SeriesBlock } from './SeriesBlock'
|
||
import { SeriesDetailBlock } from './SeriesDetailBlock'
|
||
import { VideoEmbedBlock } from './VideoEmbedBlock'
|
||
import { FeaturedContentBlock } from './FeaturedContentBlock'
|
||
|
||
const blockComponents: Record<string, React.ComponentType<any>> = {
|
||
'hero-block': HeroBlock,
|
||
'hero-slider-block': HeroSliderBlock,
|
||
'text-block': TextBlock,
|
||
'image-text-block': ImageTextBlock,
|
||
'card-grid-block': CardGridBlock,
|
||
'cta-block': CTABlock,
|
||
'divider-block': DividerBlock,
|
||
'posts-list-block': PostsListBlock,
|
||
'testimonials-block': TestimonialsBlock,
|
||
'faq-block': FAQBlock,
|
||
'newsletter-block': NewsletterBlock,
|
||
'contact-form-block': ContactFormBlock,
|
||
'video-block': VideoBlock,
|
||
'timeline-block': TimelineBlock,
|
||
'process-steps-block': ProcessStepsBlock,
|
||
// BlogWoman-spezifisch
|
||
'favorites-block': FavoritesBlock,
|
||
'series-block': SeriesBlock,
|
||
'series-detail-block': SeriesDetailBlock,
|
||
'video-embed-block': VideoEmbedBlock,
|
||
'featured-content-block': FeaturedContentBlock,
|
||
}
|
||
|
||
interface BlockRendererProps {
|
||
blocks: Array<{ blockType: string; [key: string]: any }>
|
||
}
|
||
|
||
export function BlockRenderer({ blocks }: BlockRendererProps) {
|
||
if (!blocks || blocks.length === 0) return null
|
||
|
||
return (
|
||
<>
|
||
{blocks.map((block, index) => {
|
||
const Component = blockComponents[block.blockType]
|
||
if (!Component) {
|
||
console.warn(`Unknown block type: ${block.blockType}`)
|
||
return null
|
||
}
|
||
return <Component key={`${block.blockType}-${index}`} {...block} />
|
||
})}
|
||
</>
|
||
)
|
||
}
|
||
```
|
||
|
||
### 6.2 Standard Blocks
|
||
|
||
#### Hero Block
|
||
|
||
```typescript
|
||
// src/components/blocks/HeroBlock.tsx
|
||
interface HeroBlockProps {
|
||
heading: string
|
||
subheading?: string
|
||
backgroundImage?: { url: string }
|
||
ctaText?: string
|
||
ctaLink?: string
|
||
alignment?: 'left' | 'center' | 'right'
|
||
overlay?: boolean
|
||
overlayOpacity?: number
|
||
}
|
||
```
|
||
|
||
#### Posts List Block
|
||
|
||
```typescript
|
||
// src/components/blocks/PostsListBlock.tsx
|
||
interface PostsListBlockProps {
|
||
title?: string
|
||
subtitle?: string
|
||
postType?: 'blog' | 'news' | 'press' | 'announcement'
|
||
layout?: 'grid' | 'list' | 'featured' | 'compact' | 'masonry'
|
||
columns?: 2 | 3 | 4
|
||
limit?: number
|
||
showFeaturedOnly?: boolean
|
||
filterByCategory?: string
|
||
showExcerpt?: boolean
|
||
showDate?: boolean
|
||
showAuthor?: boolean
|
||
showCategory?: boolean
|
||
showPagination?: boolean
|
||
backgroundColor?: 'white' | 'ivory' | 'sand'
|
||
}
|
||
```
|
||
|
||
#### FAQ Block (mit Schema.org)
|
||
|
||
```typescript
|
||
// src/components/blocks/FAQBlock.tsx
|
||
interface FAQBlockProps {
|
||
title?: string
|
||
subtitle?: string
|
||
displayMode: 'all' | 'selected' | 'byCategory'
|
||
selectedFaqs?: Array<{ id: string; question: string; answer: any }>
|
||
filterCategory?: string
|
||
layout?: 'accordion' | 'list' | 'grid'
|
||
expandFirst?: boolean
|
||
showSchema?: boolean // JSON-LD für SEO
|
||
}
|
||
```
|
||
|
||
#### Newsletter Block
|
||
|
||
```typescript
|
||
// src/components/blocks/NewsletterBlock.tsx
|
||
interface NewsletterBlockProps {
|
||
title?: string
|
||
subtitle?: string
|
||
buttonText?: string
|
||
layout?: 'inline' | 'stacked' | 'with-image' | 'minimal' | 'card'
|
||
backgroundImage?: { url: string }
|
||
showPrivacyNote?: boolean
|
||
}
|
||
```
|
||
|
||
### 6.3 BlogWoman-spezifische Blocks
|
||
|
||
#### Favorites Block (Affiliate-Produkte)
|
||
|
||
```typescript
|
||
// src/components/blocks/FavoritesBlock.tsx
|
||
interface FavoritesBlockProps {
|
||
title?: string
|
||
subtitle?: string
|
||
displayMode: 'all' | 'selected' | 'byCategory'
|
||
selectedFavorites?: Array<{
|
||
id: string
|
||
title: string
|
||
description?: any
|
||
image?: { url: string }
|
||
affiliateUrl: string
|
||
price?: string
|
||
category?: 'fashion' | 'beauty' | 'travel' | 'tech' | 'home'
|
||
badge?: 'investment-piece' | 'daily-driver' | 'grfi-approved' | 'new' | 'bestseller'
|
||
priceRange?: 'budget' | 'mid' | 'premium' | 'luxury'
|
||
}>
|
||
filterCategory?: string
|
||
layout?: 'grid' | 'list' | 'carousel'
|
||
columns?: 2 | 3 | 4
|
||
limit?: number
|
||
showPrice?: boolean
|
||
showBadge?: boolean
|
||
}
|
||
```
|
||
|
||
#### Series Block (YouTube-Serien)
|
||
|
||
```typescript
|
||
// src/components/blocks/SeriesBlock.tsx
|
||
interface SeriesBlockProps {
|
||
title?: string
|
||
subtitle?: string
|
||
displayMode: 'all' | 'selected'
|
||
selectedSeries?: Array<{
|
||
id: string
|
||
title: string
|
||
slug: string
|
||
description?: any
|
||
logo?: { url: string }
|
||
coverImage?: { url: string }
|
||
brandColor?: string
|
||
youtubePlaylistId?: string
|
||
}>
|
||
layout?: 'grid' | 'list' | 'featured'
|
||
showDescription?: boolean
|
||
}
|
||
```
|
||
|
||
#### Series Detail Block (Hero für Serien-Seiten)
|
||
|
||
```typescript
|
||
// src/components/blocks/SeriesDetailBlock.tsx
|
||
interface SeriesDetailBlockProps {
|
||
series: {
|
||
id: string
|
||
title: string
|
||
description?: any
|
||
logo?: { url: string }
|
||
coverImage?: { url: string }
|
||
brandColor?: string
|
||
youtubePlaylistId?: string
|
||
}
|
||
layout?: 'hero' | 'compact' | 'sidebar'
|
||
showLogo?: boolean
|
||
showPlaylistLink?: boolean
|
||
useBrandColor?: boolean
|
||
}
|
||
```
|
||
|
||
#### Video Embed Block (Privacy Mode)
|
||
|
||
```typescript
|
||
// src/components/blocks/VideoEmbedBlock.tsx
|
||
interface VideoEmbedBlockProps {
|
||
videoUrl: string
|
||
title?: string
|
||
aspectRatio?: '16:9' | '4:3' | '1:1' | '9:16'
|
||
privacyMode?: boolean // youtube-nocookie.com
|
||
autoplay?: boolean
|
||
showControls?: boolean
|
||
thumbnailImage?: { url: string }
|
||
}
|
||
```
|
||
|
||
#### Featured Content Block (Mixed Content)
|
||
|
||
```typescript
|
||
// src/components/blocks/FeaturedContentBlock.tsx
|
||
interface FeaturedContentBlockProps {
|
||
title?: string
|
||
subtitle?: string
|
||
items: Array<{
|
||
type: 'post' | 'video' | 'favorite' | 'series' | 'external'
|
||
post?: { id: string; title: string; slug: string; excerpt?: string; featuredImage?: { url: string } }
|
||
video?: { id: string; title: string; videoUrl: string; thumbnailImage?: { url: string } }
|
||
favorite?: { id: string; title: string; affiliateUrl: string; image?: { url: string } }
|
||
series?: { id: string; title: string; slug: string; logo?: { url: string } }
|
||
externalUrl?: string
|
||
externalTitle?: string
|
||
externalImage?: { url: string }
|
||
}>
|
||
layout?: 'grid' | 'masonry' | 'featured'
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 7. Serien-Pills (UI-Komponente)
|
||
|
||
BlogWoman nutzt farbcodierte Pills für die YouTube-Serien:
|
||
|
||
```typescript
|
||
// src/components/ui/SeriesPill.tsx
|
||
const seriesColors: Record<string, { bg: string; text: string }> = {
|
||
'grfi': { bg: 'bg-sand', text: 'text-espresso' },
|
||
'investment': { bg: 'bg-brass', text: 'text-soft-white' },
|
||
'pl': { bg: 'bg-bordeaux', text: 'text-soft-white' }, // Pleasure & Loss
|
||
'spark': { bg: 'bg-rose', text: 'text-espresso' },
|
||
'inner-circle': { bg: 'bg-gold', text: 'text-espresso' },
|
||
'reset': { bg: 'bg-sand', text: 'text-espresso' },
|
||
'decision': { bg: 'bg-sand', text: 'text-espresso' },
|
||
'regeneration': { bg: 'bg-sand', text: 'text-espresso' },
|
||
'm2m': { bg: 'bg-sand', text: 'text-espresso' }, // Mom to Mom
|
||
'backstage': { bg: 'bg-warm-gray', text: 'text-espresso' },
|
||
}
|
||
|
||
interface SeriesPillProps {
|
||
series: string
|
||
children: React.ReactNode
|
||
size?: 'sm' | 'md' | 'lg'
|
||
}
|
||
|
||
export function SeriesPill({ series, children, size = 'md' }: SeriesPillProps) {
|
||
const colors = seriesColors[series.toLowerCase()] || seriesColors['grfi']
|
||
|
||
const sizeClasses = {
|
||
sm: 'px-2 py-1 text-[10px]',
|
||
md: 'px-3.5 py-1.5 text-[11px]',
|
||
lg: 'px-6 py-2.5 text-[13px]',
|
||
}
|
||
|
||
return (
|
||
<span className={`
|
||
inline-flex items-center
|
||
${sizeClasses[size]}
|
||
${colors.bg} ${colors.text}
|
||
font-bold tracking-[0.08em] uppercase
|
||
rounded-full
|
||
`}>
|
||
{children}
|
||
</span>
|
||
)
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 8. Seiten-Implementierung
|
||
|
||
### 8.1 Root Layout
|
||
|
||
**Datei:** `src/app/layout.tsx`
|
||
|
||
```typescript
|
||
import { Inter, Playfair_Display } from 'next/font/google'
|
||
import { Header } from '@/components/layout/Header'
|
||
import { Footer } from '@/components/layout/Footer'
|
||
import { CookieBanner } from '@/components/layout/CookieBanner'
|
||
import { UmamiScript } from '@/components/analytics/UmamiScript'
|
||
import { getNavigation, getSiteSettings } from '@/lib/api'
|
||
import './globals.css'
|
||
|
||
const inter = Inter({ subsets: ['latin'], variable: '--font-body' })
|
||
const playfair = Playfair_Display({ subsets: ['latin'], variable: '--font-headline' })
|
||
|
||
export default async function RootLayout({ children }: { children: React.ReactNode }) {
|
||
const [headerNav, footerNav, siteSettings] = await Promise.all([
|
||
getNavigation('header'),
|
||
getNavigation('footer'),
|
||
getSiteSettings(),
|
||
])
|
||
|
||
return (
|
||
<html lang="de" className={`${inter.variable} ${playfair.variable}`}>
|
||
<body className="bg-ivory text-espresso font-body antialiased">
|
||
<Header navigation={headerNav} settings={siteSettings} />
|
||
<main>{children}</main>
|
||
<Footer navigation={footerNav} settings={siteSettings} />
|
||
<CookieBanner />
|
||
|
||
{/* Umami Analytics - Cookieless, DSGVO-konform */}
|
||
<UmamiScript
|
||
websiteId={process.env.NEXT_PUBLIC_UMAMI_WEBSITE_ID!}
|
||
host={process.env.NEXT_PUBLIC_UMAMI_HOST}
|
||
/>
|
||
</body>
|
||
</html>
|
||
)
|
||
}
|
||
```
|
||
|
||
### 8.2 Dynamische Seiten
|
||
|
||
**Datei:** `src/app/[slug]/page.tsx`
|
||
|
||
```typescript
|
||
import { notFound } from 'next/navigation'
|
||
import { getPage } from '@/lib/api'
|
||
import { BlockRenderer } from '@/components/blocks'
|
||
import type { Metadata } from 'next'
|
||
|
||
interface PageProps {
|
||
params: Promise<{ slug: string }>
|
||
}
|
||
|
||
export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
|
||
const { slug } = await params
|
||
const page = await getPage(slug)
|
||
|
||
if (!page) return {}
|
||
|
||
return {
|
||
title: page.meta?.title || page.title,
|
||
description: page.meta?.description,
|
||
openGraph: {
|
||
title: page.meta?.title || page.title,
|
||
description: page.meta?.description,
|
||
images: page.meta?.image ? [{ url: page.meta.image.url }] : [],
|
||
},
|
||
robots: {
|
||
index: !page.meta?.noIndex,
|
||
follow: !page.meta?.noFollow,
|
||
},
|
||
}
|
||
}
|
||
|
||
export default async function Page({ params }: PageProps) {
|
||
const { slug } = await params
|
||
const page = await getPage(slug)
|
||
|
||
if (!page) notFound()
|
||
|
||
return <BlockRenderer blocks={page.layout} />
|
||
}
|
||
```
|
||
|
||
### 8.3 Serien-Detailseite (BlogWoman)
|
||
|
||
**Datei:** `src/app/serien/[slug]/page.tsx`
|
||
|
||
```typescript
|
||
import { notFound } from 'next/navigation'
|
||
import { getSeriesBySlug } from '@/lib/api'
|
||
import { SeriesDetailBlock } from '@/components/blocks/SeriesDetailBlock'
|
||
import { VideoEmbedBlock } from '@/components/blocks/VideoEmbedBlock'
|
||
import type { Metadata } from 'next'
|
||
|
||
interface PageProps {
|
||
params: Promise<{ slug: string }>
|
||
}
|
||
|
||
export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
|
||
const { slug } = await params
|
||
const series = await getSeriesBySlug(slug)
|
||
|
||
if (!series) return {}
|
||
|
||
return {
|
||
title: `${series.title} | BlogWoman`,
|
||
description: series.description,
|
||
openGraph: {
|
||
title: series.title,
|
||
images: series.coverImage ? [{ url: series.coverImage.url }] : [],
|
||
},
|
||
}
|
||
}
|
||
|
||
export default async function SeriesPage({ params }: PageProps) {
|
||
const { slug } = await params
|
||
const series = await getSeriesBySlug(slug)
|
||
|
||
if (!series) notFound()
|
||
|
||
return (
|
||
<div>
|
||
<SeriesDetailBlock
|
||
series={series}
|
||
layout="hero"
|
||
showLogo
|
||
showPlaylistLink
|
||
useBrandColor
|
||
/>
|
||
|
||
{/* Videos der Serie werden hier geladen */}
|
||
{/* Implementierung: YouTube API oder Payload YouTube-Content Collection */}
|
||
</div>
|
||
)
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 9. SEO & Meta-Tags
|
||
|
||
### 9.1 Metadata API
|
||
|
||
```typescript
|
||
// src/app/layout.tsx (erweitert)
|
||
export async function generateMetadata(): Promise<Metadata> {
|
||
const siteSettings = await getSiteSettings()
|
||
|
||
return {
|
||
title: {
|
||
default: 'BlogWoman | Caroline Porwoll',
|
||
template: '%s | BlogWoman',
|
||
},
|
||
description: siteSettings?.description || 'Für Frauen, die Karriere, Familie & Stil ernst nehmen.',
|
||
metadataBase: new URL('https://blogwoman.de'),
|
||
openGraph: {
|
||
type: 'website',
|
||
locale: 'de_DE',
|
||
siteName: 'BlogWoman',
|
||
},
|
||
twitter: {
|
||
card: 'summary_large_image',
|
||
},
|
||
robots: {
|
||
index: true,
|
||
follow: true,
|
||
},
|
||
}
|
||
}
|
||
```
|
||
|
||
### 9.2 JSON-LD Structured Data
|
||
|
||
```typescript
|
||
// src/lib/structuredData.ts
|
||
export function generateOrganizationSchema(settings: any) {
|
||
return {
|
||
'@context': 'https://schema.org',
|
||
'@type': 'Organization',
|
||
name: 'BlogWoman',
|
||
url: 'https://blogwoman.de',
|
||
logo: settings?.logo?.url,
|
||
sameAs: [
|
||
'https://www.youtube.com/@blogwoman',
|
||
'https://www.instagram.com/blogwoman/',
|
||
// weitere Social Links
|
||
],
|
||
}
|
||
}
|
||
|
||
export function generateFAQSchema(faqs: Array<{ question: string; answer: string }>) {
|
||
return {
|
||
'@context': 'https://schema.org',
|
||
'@type': 'FAQPage',
|
||
mainEntity: faqs.map(faq => ({
|
||
'@type': 'Question',
|
||
name: faq.question,
|
||
acceptedAnswer: {
|
||
'@type': 'Answer',
|
||
text: faq.answer,
|
||
},
|
||
})),
|
||
}
|
||
}
|
||
|
||
export function generateArticleSchema(post: any, baseUrl: string) {
|
||
return {
|
||
'@context': 'https://schema.org',
|
||
'@type': 'Article',
|
||
headline: post.title,
|
||
description: post.excerpt,
|
||
image: post.featuredImage?.url,
|
||
datePublished: post.publishedAt,
|
||
dateModified: post.updatedAt,
|
||
author: {
|
||
'@type': 'Person',
|
||
name: post.author?.name || 'Caroline Porwoll',
|
||
},
|
||
publisher: {
|
||
'@type': 'Organization',
|
||
name: 'BlogWoman',
|
||
url: baseUrl,
|
||
},
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 10. Analytics (Umami)
|
||
|
||
### 10.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'
|
||
}: UmamiScriptProps) {
|
||
if (!websiteId) return null
|
||
|
||
return (
|
||
<Script
|
||
defer
|
||
src={`${host}/script.js`}
|
||
data-website-id={websiteId}
|
||
data-host-url={host}
|
||
strategy="afterInteractive"
|
||
/>
|
||
)
|
||
}
|
||
```
|
||
|
||
### 10.2 Analytics Hook
|
||
|
||
```typescript
|
||
// src/hooks/useAnalytics.ts
|
||
'use client'
|
||
|
||
import { useCallback } from 'react'
|
||
|
||
declare global {
|
||
interface Window {
|
||
umami?: {
|
||
track: (eventName: string, eventData?: Record<string, unknown>) => void
|
||
}
|
||
}
|
||
}
|
||
|
||
export function useAnalytics() {
|
||
const trackEvent = useCallback((
|
||
eventName: string,
|
||
eventData?: Record<string, unknown>
|
||
) => {
|
||
if (typeof window !== 'undefined' && window.umami) {
|
||
window.umami.track(eventName, eventData)
|
||
}
|
||
}, [])
|
||
|
||
return {
|
||
trackEvent,
|
||
trackNewsletterSubscribe: (source: string) => trackEvent('newsletter_subscribe', { source }),
|
||
trackCtaClick: (ctaName: string, location: string) => trackEvent('cta_click', { cta_name: ctaName, location }),
|
||
trackAffiliateClick: (productName: string, category: string) => trackEvent('affiliate_click', { product: productName, category }),
|
||
trackSeriesClick: (seriesName: string) => trackEvent('series_click', { series: seriesName }),
|
||
trackVideoPlay: (videoTitle: string, seriesName?: string) => trackEvent('video_play', { title: videoTitle, series: seriesName }),
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 11. Erfolgskriterien
|
||
|
||
### Technisch
|
||
|
||
- [ ] `pnpm lint` ohne Errors
|
||
- [ ] `pnpm build` erfolgreich
|
||
- [ ] TypeScript ohne Fehler
|
||
- [ ] Lighthouse Score > 90 (Performance, Accessibility, Best Practices, SEO)
|
||
|
||
### Funktional
|
||
|
||
- [ ] Alle Standard-Block-Typen gerendert
|
||
- [ ] BlogWoman-spezifische Blocks (Favorites, Series, Video) funktionieren
|
||
- [ ] Navigation (Header, Footer, Mobile) funktioniert
|
||
- [ ] Blog-Übersicht und Detailseiten
|
||
- [ ] Serien-Übersicht und Detailseiten
|
||
- [ ] Favoriten-Seite mit Kategorien/Badges
|
||
- [ ] Kontaktformular sendet Daten
|
||
- [ ] Newsletter-Anmeldung funktioniert (Double Opt-In)
|
||
- [ ] Cookie-Banner DSGVO-konform
|
||
|
||
### SEO
|
||
|
||
- [ ] Meta-Tags korrekt auf allen Seiten
|
||
- [ ] Open Graph Tags für Social Sharing
|
||
- [ ] JSON-LD Structured Data (Organization, FAQ, Article)
|
||
- [ ] Sitemap generiert
|
||
- [ ] robots.txt korrekt
|
||
|
||
### Design
|
||
|
||
- [ ] Styleguide-Farben korrekt implementiert
|
||
- [ ] Playfair Display für Headlines
|
||
- [ ] Inter für Body/UI
|
||
- [ ] Mobile-responsive (alle Breakpoints)
|
||
- [ ] Serien-Pills mit korrekten Farben
|
||
|
||
---
|
||
|
||
## 12. Escape Hatch
|
||
|
||
Nach 15 Iterationen ohne Fortschritt:
|
||
1. Dokumentiere Blocker in `BLOCKERS.md`
|
||
2. Liste versuchte Lösungen auf
|
||
3. Output: `<promise>BLOCKED</promise>`
|
||
|
||
---
|
||
|
||
## 13. Fertig?
|
||
|
||
Wenn ALLE Aufgaben erledigt sind:
|
||
|
||
```
|
||
<promise>BLOGWOMAN_FRONTEND_COMPLETE</promise>
|
||
```
|
||
|
||
---
|
||
|
||
*Prompt erstellt: 20. Januar 2026*
|
||
*Für: BlogWoman.de - Payload CMS Multi-Tenant Frontend*
|