Files
portfolio/DIRECTUS_MIGRATION.md
2026-01-22 20:56:35 +01:00

222 lines
6.3 KiB
Markdown

# Directus Integration - Migration Guide
## 🎯 Was wurde geändert?
Das Portfolio nutzt jetzt **Directus als CMS** für alle Texte. Die Integration ist **hybrid**:
-**Directus** (primär) → Texte werden aus Directus CMS geladen
-**JSON Fallback** (sekundär) → Falls Directus nicht erreichbar, nutzen wir messages/*.json
## 📁 Neue Dateien
### Core Infrastructure
- `lib/directus.ts` - REST Client für Directus (nutzt `de-DE`, `en-US`)
- `lib/i18n-loader.ts` - Lädt Texte mit Fallback-Chain
- `lib/translations-loader.ts` - Batch-Loader für alle Sections
- `types/translations.ts` - TypeScript Types für alle Translation Objects
### Components
- `app/components/Header.server.tsx` - Server Wrapper für Header
- `app/components/HeaderClient.tsx` - Client Implementation mit Props
- `app/components/ClientWrappers.tsx` - Wrapper für Hero, About, Projects, Contact, Footer
- `app/_ui/HomePageServer.tsx` - Server Component lädt alle Translations
## 🔄 Architektur
### Vorher (next-intl only)
```
Client Component → useTranslations("nav") → JSON File
```
### Jetzt (Directus + Fallback)
```
Server Component → getNavTranslations(locale)
→ Directus API (de-DE/en-US)
→ Falls nicht gefunden: JSON File (de/en)
→ Props an Client Component
Client Component → Nutzt translations aus Props
```
## 🗄️ Directus Setup
### 1. Collection: `messages`
**Felder:**
- `id` (Primary Key, UUID, auto)
- `key` (String, required) - z.B. "nav.home"
- `locale` (String, required) - **WICHTIG:** `de-DE` oder `en-US` (mit `-`)
- `value` (Text, required) - Der übersetzte Text
- `translations` (Translations) - **Directus Native Translations Feature**
**WICHTIG:** Du hast zwei Optionen:
#### Option A: Directus Native Translations (Empfohlen)
1. Aktiviere "Translations" für `messages` Collection
2. Definiere `de-DE` und `en-US` als Languages
3. Felder: `key` (unique), `value` (translatable)
4. Pro Key nur ein Eintrag, Directus managed Translations intern
#### Option B: Flat Structure (Einfacher)
1. Keine Translations Feature
2. Felder: `key` + `locale` + `value`
3. Pro Key/Locale Kombination ein Eintrag
4. Beispiel:
- Row 1: key="nav.home", locale="de-DE", value="Startseite"
- Row 2: key="nav.home", locale="en-US", value="Home"
### 2. Collection: `content_pages` (Optional)
Für längere Inhalte (z.B. Datenschutz, Impressum):
**Felder:**
- `id` (Primary Key, UUID)
- `slug` (String, unique) - z.B. "privacy-policy"
- `locale` (String) - `de-DE` oder `en-US`
- `title` (String)
- `content` (Rich Text oder Long Text)
### 3. Permissions
**Public Role:**
- `messages`: Read access (alle Felder)
- `content_pages`: Read access (alle Felder)
## 📝 Keys eintragen
Alle Keys aus `DIRECTUS_CHECKLIST.md` müssen in Directus eingetragen werden.
**Beispiel Keys:**
```
nav.home
nav.about
nav.projects
nav.contact
home.hero.greeting
home.hero.name
home.hero.role
home.hero.description
...
```
**Wichtig:** Keys sind **dot-separated** (wie in JSON), aber **Locale nutzt `-`**:
-`key="nav.home"`, `locale="de-DE"`
-`key="nav_home"`, `locale="de"`
## 🔧 Environment Variables
In `.env.local`:
```bash
DIRECTUS_URL=https://cms.dk0.dev
DIRECTUS_STATIC_TOKEN=ogUMcHCa1CAYU1YifsoeJ_7V76o1atYG
```
## 🚀 Wie funktioniert's?
### 1. Seite wird geladen
```tsx
// app/[locale]/page.tsx
export default async function Page({ params }) {
const { locale } = await params;
return <HomePageServer locale={locale} />;
}
```
### 2. Server Component lädt Translations
```tsx
// app/_ui/HomePageServer.tsx
export default async function HomePageServer({ locale }) {
const heroT = await getHeroTranslations(locale);
// ...
return <HeroClient locale={locale} translations={heroT} />;
}
```
### 3. Translation Loader fetcht von Directus
```tsx
// lib/translations-loader.ts
export async function getHeroTranslations(locale: string) {
// Batch-Load aus Directus
// locale='de' wird zu 'de-DE' gemapped
const values = await Promise.all([...]);
return { greeting, name, role, ... };
}
```
### 4. Client Component nutzt Props
```tsx
// app/components/ClientWrappers.tsx
export function HeroClient({ locale, translations }) {
// Konvertiert zu next-intl Format
return (
<NextIntlClientProvider messages={messages}>
<Hero />
</NextIntlClientProvider>
);
}
```
## 🔍 Fallback Chain
Für jeden Key wird gesucht:
1. **Directus (requested locale)** - z.B. `de-DE`
2. **Directus (EN fallback)** - Falls nicht gefunden: `en-US`
3. **JSON (normalized locale)** - Falls Directus down: `messages/de.json`
4. **JSON (EN fallback)** - Falls Key nicht existiert: `messages/en.json`
5. **Key selbst** - Als letzter Fallback: return "nav.home"
## 🎨 Cache
- In-Memory Cache mit 5 min TTL
- Cache Key: `msg:${key}:${locale}`
- Läuft im Server Memory (nicht persistent)
- Bei Deploy/Restart wird Cache geleert
## ✅ Testing
1. **Mit Directus:** Trage einen Test-Key ein:
- Key: `test`
- Locale: `de-DE`
- Value: `Hallo von Directus!`
- Prüfe: `await getLocalizedMessage('test', 'de')` → "Hallo von Directus!"
2. **Ohne Directus:** Stoppe Directus
- Prüfe: Messages sollten aus JSON files kommen
- Website sollte normal funktionieren (degraded mode)
3. **Build Test:**
```bash
npm run build
```
- Sollte ohne Errors durchlaufen
## 🐛 Troubleshooting
### "Key nicht gefunden"
- Prüfe Directus GUI: Key exakt gleich? (`nav.home` nicht `nav_home`)
- Prüfe Locale: `de-DE` oder `en-US` (mit `-`)?
- Prüfe Permissions: Public role hat Read access?
### "Directus nicht erreichbar"
- Prüfe `DIRECTUS_URL` in .env
- Prüfe Token: `DIRECTUS_STATIC_TOKEN`
- Test: `curl -H "Authorization: Bearer TOKEN" https://cms.dk0.dev/items/messages`
### "Texte ändern sich nicht"
- Cache! Warte 5 Minuten oder restart Server
- Oder: Clear Cache manuell (`clearI18nCache()` in lib/i18n-loader.ts)
## 📚 Next Steps
1. **Directus deployen** (Docker auf IONOS)
2. **Collections erstellen** (messages, content_pages)
3. **Keys eintragen** (aus DIRECTUS_CHECKLIST.md)
4. **Testen** (dev environment)
5. **Production** (wenn alles funktioniert)
## 🎯 Benefits
-**Keine Rebuilds** für Text-Änderungen
-**Non-Tech Editor** kann Texte ändern (Directus GUI)
-**Graceful Degradation** (JSON Fallback)
-**Type Safety** (TypeScript Types für alle Translations)
-**Performance** (Server-side caching, parallel loading)