style: auto-format codebase with Prettier

Applied formatting to 1487+ files using pnpm format:write
  - TypeScript/JavaScript files
  - Svelte components
  - Astro pages
  - JSON configs
  - Markdown docs

  13 files still need manual review (Astro JSX comments)
This commit is contained in:
Wuesteon 2025-11-27 18:33:16 +01:00
parent 0241f5554c
commit d36b321d9d
3952 changed files with 661498 additions and 739751 deletions

View file

@ -5,24 +5,27 @@ Die `links` Collection hat noch nicht die neuen Felder. Diese müssen **manuell
## Schritt-für-Schritt Anleitung:
### 1. PocketBase Admin öffnen
Gehe zu: https://pb.ulo.ad/_/
### 2. Links Collection bearbeiten
- Navigiere zu: Collections → links
- Klicke auf "Edit collection"
### 3. Diese Felder hinzufügen:
#### a) System-Timestamps (WICHTIGSTE!)
- **created** (Type: autodate)
- onCreate: ✅ aktiviert
- onUpdate: ❌ deaktiviert
- **updated** (Type: autodate)
- onCreate: ✅ aktiviert
- onUpdate: ✅ aktiviert
#### b) Neue Funktionsfelder
- **use_username** (Type: bool)
- Default value: false
- Required: No
@ -37,6 +40,7 @@ Gehe zu: https://pb.ulo.ad/_/
- Required: No
#### c) Marketing-Felder (optional)
- **utm_source** (Type: text)
- Max length: 255
- Required: No
@ -50,6 +54,7 @@ Gehe zu: https://pb.ulo.ad/_/
- Required: No
### 4. Speichern
Klicke auf "Save collection"
## Frontend-Anpassungen die bereits gemacht wurden:
@ -61,6 +66,7 @@ Klicke auf "Save collection"
## Was noch geprüft werden muss:
Nach dem Hinzufügen der Felder in PocketBase:
1. Teste das Erstellen eines neuen Links
2. Prüfe ob created/updated gesetzt werden
3. Teste die use_username Checkbox Funktion
3. Teste die use_username Checkbox Funktion

View file

@ -7,11 +7,13 @@ The application now supports automatic switching between development and product
## Configuration
### Development Mode
- **Database URL**: `http://localhost:8090`
- **Config File**: `.env` (for local development)
- **MCP Config**: `.mcp.development.json`
### Production Mode
- **Database URL**: `https://pb.ulo.ad`
- **Config File**: `.env.production`
- **MCP Config**: `.mcp.json`
@ -21,6 +23,7 @@ The application now supports automatic switching between development and product
### Local Development
1. **Start the local PocketBase server**:
```bash
npm run backend
# or
@ -28,11 +31,13 @@ cd backend && ./pocketbase serve
```
2. **Start the development server**:
```bash
npm run dev
```
3. **Or start both together**:
```bash
npm run dev:all
```
@ -51,11 +56,13 @@ npm run preview
## Switching Between Environments
### To use local database:
1. Ensure `.env` contains: `PUBLIC_POCKETBASE_URL=http://localhost:8090`
2. Start local PocketBase: `npm run backend`
3. Run development server: `npm run dev`
### To use production database (NOT RECOMMENDED for development):
1. Change `.env` to: `PUBLIC_POCKETBASE_URL=https://pb.ulo.ad`
2. Restart development server: `npm run dev`
@ -82,16 +89,19 @@ Is Production: false
## Troubleshooting
### Local PocketBase not starting
- Check if port 8090 is already in use
- Ensure PocketBase binary is executable: `chmod +x backend/pocketbase`
- Check logs: `cat pocketbase-dev.log`
### Still connecting to production
- Clear browser cache and restart dev server
- Verify `.env` file contains correct URL
- Check console output for which URL is being used
### MCP Server issues
- For local development, use `.mcp.development.json`
- For production, use `.mcp.json`
- Restart Claude Code after changing MCP configuration
- Restart Claude Code after changing MCP configuration

View file

@ -45,6 +45,7 @@ The team invitation system is fully implemented and ready to send emails. To ena
- Copy the API key
3. **Add to `.env`**
```env
BREVO_API_KEY=your-api-key-here
```
@ -60,6 +61,7 @@ The team invitation system is fully implemented and ready to send emails. To ena
3. **Get your API key**
4. **Add to `.env`**
```env
RESEND_API_KEY=your-api-key-here
```
@ -71,6 +73,7 @@ The team invitation system is fully implemented and ready to send emails. To ena
## Testing Email Setup
1. **Restart the dev server** (if you added environment variables)
```bash
npm run dev
```
@ -103,4 +106,4 @@ All templates are bilingual (German/English) and mobile-responsive.
- **Gmail blocking?** Use App Password, not regular password
- **Rate limits?** Free tiers have daily limits
For production, we recommend using PocketBase SMTP with a professional email service like SendGrid, Mailgun, or Amazon SES.
For production, we recommend using PocketBase SMTP with a professional email service like SendGrid, Mailgun, or Amazon SES.

View file

@ -1,41 +1,48 @@
# 🚨 SOFORTIGE LÖSUNG: Created/Updated Felder hinzufügen
## Das Problem
Die `links` Collection hat keine `created` und `updated` Felder, deshalb zeigt die UI "N/A" an.
## ✅ EINFACHSTE LÖSUNG - Manuelle Schritte in PocketBase Admin:
### 1. PocketBase Admin öffnen
Gehe zu: **https://pb.ulo.ad/_/**
### 2. Links Collection bearbeiten
### 2. Links Collection bearbeiten
1. Navigiere zu: **Collections → links**
2. Klicke auf **"Edit collection"**
### 3. Diese 2 wichtigsten Felder hinzufügen:
#### a) **created** (Type: autodate) - WICHTIGSTE!
- **Field name**: `created`
- **Type**: `autodate`
- **Type**: `autodate`
- **onCreate**: ✅ **aktiviert**
- **onUpdate**: ❌ deaktiviert
#### b) **updated** (Type: autodate) - WICHTIGSTE!
#### b) **updated** (Type: autodate) - WICHTIGSTE!
- **Field name**: `updated`
- **Type**: `autodate`
- **onCreate**: ✅ **aktiviert**
- **onCreate**: ✅ **aktiviert**
- **onUpdate**: ✅ **aktiviert**
### 4. Optional - Weitere nützliche Felder:
#### c) **use_username** (Type: bool)
- **Field name**: `use_username`
- **Type**: `bool`
- **Default value**: `false`
- **Required**: No
#### d) **click_count** (Type: number)
- **Field name**: `click_count`
- **Field name**: `click_count`
- **Type**: `number`
- **Default value**: `0`
- **Only integers**: ✅
@ -43,20 +50,25 @@ Gehe zu: **https://pb.ulo.ad/_/**
- **Required**: No
### 5. Speichern
Klicke auf **"Save collection"**
## ✅ ERGEBNIS
Nach dem Hinzufügen der Felder:
- **Neue Links** bekommen automatisch Timestamps ✅
- **UI zeigt Datum korrekt an** statt "N/A" ✅
- **Alle anderen Features** funktionieren weiterhin ✅
## 🧹 Aufräumen (Optional)
Nach erfolgreicher Implementierung:
- Lösche die `links_improved` Collection (war nur ein Test)
- Die Anwendung nutzt wieder die ursprüngliche `links` Collection
---
**⏱️ Zeitaufwand**: 2-3 Minuten
**🎯 Resultat**: Problem komplett gelöst!
**🎯 Resultat**: Problem komplett gelöst!

View file

@ -3,42 +3,49 @@
## 🎯 Übersicht
Wir haben jetzt **ZWEI separate MCP-Konfigurationen**:
- **Development**: Lokale PocketBase (`localhost:8090`)
- **Production**: Cloud PocketBase (`pb.ulo.ad`)
## 📁 Konfigurationsdateien
### `.mcp.development.json` (Lokale Entwicklung)
```json
{
"mcpServers": {
"pocketbase-dev": {
"command": "node",
"args": ["/Users/tillschneider/Documents/__00__Code/uload/mcp-servers/pocketbase-mcp/build/index.js"],
"env": {
"POCKETBASE_URL": "http://localhost:8090",
"POCKETBASE_ADMIN_EMAIL": "till.schneider@memoro.ai",
"POCKETBASE_ADMIN_PASSWORD": "p0ck3t-RAJ"
}
}
}
"mcpServers": {
"pocketbase-dev": {
"command": "node",
"args": [
"/Users/tillschneider/Documents/__00__Code/uload/mcp-servers/pocketbase-mcp/build/index.js"
],
"env": {
"POCKETBASE_URL": "http://localhost:8090",
"POCKETBASE_ADMIN_EMAIL": "till.schneider@memoro.ai",
"POCKETBASE_ADMIN_PASSWORD": "p0ck3t-RAJ"
}
}
}
}
```
### `.mcp.json` (Production)
```json
{
"mcpServers": {
"pocketbase-prod": {
"command": "node",
"args": ["/Users/tillschneider/Documents/__00__Code/uload/mcp-servers/pocketbase-mcp/build/index.js"],
"env": {
"POCKETBASE_URL": "https://pb.ulo.ad",
"POCKETBASE_ADMIN_EMAIL": "till.schneider@memoro.ai",
"POCKETBASE_ADMIN_PASSWORD": "p0ck3t-RA1N" // Anderes Passwort!
}
}
}
"mcpServers": {
"pocketbase-prod": {
"command": "node",
"args": [
"/Users/tillschneider/Documents/__00__Code/uload/mcp-servers/pocketbase-mcp/build/index.js"
],
"env": {
"POCKETBASE_URL": "https://pb.ulo.ad",
"POCKETBASE_ADMIN_EMAIL": "till.schneider@memoro.ai",
"POCKETBASE_ADMIN_PASSWORD": "p0ck3t-RA1N" // Anderes Passwort!
}
}
}
}
```
@ -47,11 +54,13 @@ Wir haben jetzt **ZWEI separate MCP-Konfigurationen**:
### Option 1: Config-Datei wechseln
**Für Development:**
1. Umbenennen: `.mcp.development.json``.mcp.json`
2. Claude Desktop neu starten
3. Du hast Zugriff auf lokale DB
**Für Production:**
1. Umbenennen zurück
2. Claude Desktop neu starten
3. Du hast Zugriff auf Prod DB
@ -62,43 +71,49 @@ Beide Configs in einer `.mcp.json` kombinieren:
```json
{
"mcpServers": {
"pocketbase-dev": {
"command": "node",
"args": ["/Users/tillschneider/Documents/__00__Code/uload/mcp-servers/pocketbase-mcp/build/index.js"],
"env": {
"POCKETBASE_URL": "http://localhost:8090",
"POCKETBASE_ADMIN_EMAIL": "till.schneider@memoro.ai",
"POCKETBASE_ADMIN_PASSWORD": "p0ck3t-RAJ"
}
},
"pocketbase-prod": {
"command": "node",
"args": ["/Users/tillschneider/Documents/__00__Code/uload/mcp-servers/pocketbase-mcp/build/index.js"],
"env": {
"POCKETBASE_URL": "https://pb.ulo.ad",
"POCKETBASE_ADMIN_EMAIL": "till.schneider@memoro.ai",
"POCKETBASE_ADMIN_PASSWORD": "p0ck3t-RA1N"
}
},
"stripe": {
"command": "npx",
"args": ["-y", "@stripe/mcp", "--tools=all"],
"env": {
"STRIPE_SECRET_KEY": "sk_test_..."
}
}
}
"mcpServers": {
"pocketbase-dev": {
"command": "node",
"args": [
"/Users/tillschneider/Documents/__00__Code/uload/mcp-servers/pocketbase-mcp/build/index.js"
],
"env": {
"POCKETBASE_URL": "http://localhost:8090",
"POCKETBASE_ADMIN_EMAIL": "till.schneider@memoro.ai",
"POCKETBASE_ADMIN_PASSWORD": "p0ck3t-RAJ"
}
},
"pocketbase-prod": {
"command": "node",
"args": [
"/Users/tillschneider/Documents/__00__Code/uload/mcp-servers/pocketbase-mcp/build/index.js"
],
"env": {
"POCKETBASE_URL": "https://pb.ulo.ad",
"POCKETBASE_ADMIN_EMAIL": "till.schneider@memoro.ai",
"POCKETBASE_ADMIN_PASSWORD": "p0ck3t-RA1N"
}
},
"stripe": {
"command": "npx",
"args": ["-y", "@stripe/mcp", "--tools=all"],
"env": {
"STRIPE_SECRET_KEY": "sk_test_..."
}
}
}
}
```
Dann in Claude:
- `mcp__pocketbase-dev__*` für lokale DB
- `mcp__pocketbase-prod__*` für Production DB
## 🔧 Setup für neue Session
### 1. Lokale PocketBase starten
```bash
cd backend
./pocketbase serve
@ -115,6 +130,7 @@ cd backend
### 4. Collections erstellen mit MCP
In Claude kannst du dann:
```
mcp__pocketbase-dev__create_collection
```
@ -123,13 +139,13 @@ Mit dem Schema aus `docs/COMPLETE-POCKETBASE-DEV-SETUP.md`
## ⚠️ Wichtige Unterschiede
| Aspekt | Development | Production |
|--------|------------|------------|
| URL | http://localhost:8090 | https://pb.ulo.ad |
| Admin PW | p0ck3t-RAJ | p0ck3t-RA1N |
| MCP Name | pocketbase-dev | pocketbase-prod |
| Daten | Test-Daten | Echte Daten |
| Änderungen | Sicher | Vorsichtig! |
| Aspekt | Development | Production |
| ---------- | --------------------- | ----------------- |
| URL | http://localhost:8090 | https://pb.ulo.ad |
| Admin PW | p0ck3t-RAJ | p0ck3t-RA1N |
| MCP Name | pocketbase-dev | pocketbase-prod |
| Daten | Test-Daten | Echte Daten |
| Änderungen | Sicher | Vorsichtig! |
## 🎯 Vorteile
@ -152,4 +168,4 @@ Wenn du Schema-Änderungen von Prod nach Dev synchronisieren willst:
1. Export aus Prod mit `mcp__pocketbase-prod__get_collection`
2. Import in Dev mit `mcp__pocketbase-dev__create_collection`
Aber **NIEMALS** echte Daten kopieren!
Aber **NIEMALS** echte Daten kopieren!

View file

@ -70,10 +70,10 @@ export default defineConfig({
plugins: [
paraglide({
project: './project.inlang',
outdir: './src/paraglide' // Wichtig: Konsistenter Pfad!
outdir: './src/paraglide', // Wichtig: Konsistenter Pfad!
}),
sveltekit()
]
sveltekit(),
],
});
```
@ -94,15 +94,15 @@ export const i18n = createI18n(runtime, {
de: '/ueber-uns',
fr: '/a-propos',
it: '/chi-siamo',
es: '/acerca-de'
es: '/acerca-de',
},
'/dashboard': {
de: '/dashboard',
fr: '/tableau-de-bord',
it: '/cruscotto',
es: '/panel'
}
}
es: '/panel',
},
},
});
```
@ -195,9 +195,9 @@ const config = {
adapter: adapter(),
alias: {
$paraglide: './src/paraglide',
'$paraglide/*': './src/paraglide/*'
}
}
'$paraglide/*': './src/paraglide/*',
},
},
};
export default config;
@ -248,7 +248,7 @@ export default config;
{ code: 'de', name: 'Deutsch', flag: '🇩🇪' },
{ code: 'it', name: 'Italiano', flag: '🇮🇹' },
{ code: 'fr', name: 'Français', flag: '🇫🇷' },
{ code: 'es', name: 'Español', flag: '🇪🇸' }
{ code: 'es', name: 'Español', flag: '🇪🇸' },
];
let currentLanguage = $derived(

View file

@ -21,6 +21,7 @@ PocketBase Container (Port 8090) - Separat in Coolify
## 📦 Was wurde geändert?
### Entfernte Komponenten:
- ❌ Embedded PocketBase im Container
- ❌ Supervisor Process Manager
- ❌ Docker Entrypoint Script
@ -29,6 +30,7 @@ PocketBase Container (Port 8090) - Separat in Coolify
- ❌ Volume für pb_data
### Neue Struktur:
- ✅ Schlanker Container nur mit SvelteKit
- ✅ Externe PocketBase Verbindung via `PUBLIC_POCKETBASE_URL`
- ✅ Direkte Node.js Ausführung (kein Supervisor)
@ -37,6 +39,7 @@ PocketBase Container (Port 8090) - Separat in Coolify
## 🔧 Deployment in Coolify
### 1. PocketBase Service (falls noch nicht vorhanden)
```yaml
# Als separater Service in Coolify
Service: PocketBase
@ -45,13 +48,14 @@ Persistent Volume: /pb_data
```
### 2. uLoad App Service
```yaml
# docker-compose.coolify.yml verwenden
Service: uLoad
Port: 3000
Build: Dockerfile
Environment:
- PUBLIC_POCKETBASE_URL=http://pocketbase:8090 # Interner Service Name
- PUBLIC_POCKETBASE_URL=http://pocketbase:8090 # Interner Service Name
- ORIGIN=https://ulo.ad
- PUBLIC_UMAMI_URL=...
- PUBLIC_UMAMI_WEBSITE_ID=...
@ -62,6 +66,7 @@ Environment:
### Für bestehende Deployments:
1. **PocketBase Daten sichern** (falls noch embedded):
```bash
docker cp container_name:/app/pb_data ./pb_data_backup
```
@ -71,6 +76,7 @@ Environment:
3. **Daten importieren** in neue PocketBase Instanz
4. **Environment Variables anpassen**:
```env
PUBLIC_POCKETBASE_URL=http://pocketbase:8090
```
@ -93,6 +99,7 @@ PUBLIC_UMAMI_WEBSITE_ID=xxx-xxx-xxx
## 🏗️ Build & Deploy
### Lokal testen:
```bash
# Build
docker build -t uload:latest .
@ -105,6 +112,7 @@ docker run -p 3000:3000 \
```
### Production Deploy:
```bash
# Via Coolify mit docker-compose.coolify.yml
# Oder direkt:
@ -124,4 +132,4 @@ docker compose -f docker-compose.yml up -d
- PocketBase Hooks (`pb_hooks/`) müssen manuell zur externen PocketBase migriert werden
- Schema (`backend/pb_schema.json`) muss in der externen PocketBase importiert werden
- Interne Coolify Networking nutzen (nicht über Public URL)
- Health Check läuft nur auf Port 3000 (nicht mehr auf 8090)
- Health Check läuft nur auf Port 3000 (nicht mehr auf 8090)

View file

@ -3,12 +3,15 @@
## Was wurde gemacht:
### 1. Redis lokal installiert
- Redis via Homebrew installiert: `brew install redis`
- Redis-Service gestartet: `brew services start redis`
- Läuft jetzt permanent im Hintergrund
### 2. Umgebungsvariablen konfiguriert
**Lokal (.env.development):**
```env
REDIS_HOST=localhost
REDIS_PORT=6379
@ -16,6 +19,7 @@ REDIS_PASSWORD=
```
**Produktion (.env):**
```env
# Füge deine Coolify Redis-Daten hier ein:
REDIS_HOST=your-redis-host
@ -25,6 +29,7 @@ REDIS_PASSWORD=your-redis-password
```
### 3. Robuster Fallback-Mechanismus
- App funktioniert auch OHNE Redis
- Automatische Erkennung ob Redis verfügbar ist
- Graceful degradation wenn Redis offline ist
@ -32,6 +37,7 @@ REDIS_PASSWORD=your-redis-password
## So prüfst du ob Redis funktioniert:
### 1. Status-Endpoint
```bash
# Lokal
curl http://localhost:5173/api/redis-status
@ -41,15 +47,19 @@ curl https://ulo.ad/api/redis-status
```
### 2. Console Logs
Beim App-Start siehst du:
- ✅ Redis: Connected successfully (wenn verbunden)
- ⚠️ Redis: Disabled (wenn nicht konfiguriert)
### 3. Performance Test
1. Besuche einen Link zum ersten Mal → "Cache MISS"
2. Besuche denselben Link nochmal → "Cache HIT!" (viel schneller!)
### 4. Redis CLI
```bash
# Zeige alle gecachten Links
redis-cli keys "redirect:*"
@ -96,8 +106,8 @@ redis-cli flushall
## Cache-Strategie:
- **Normale Links**: 5 Minuten Cache
- **Populäre Links**: 24 Stunden Cache
- **Populäre Links**: 24 Stunden Cache
- **Passwort-geschützte Links**: Kein Cache
- **Abgelaufene Links**: Kein Cache
Der Cache beschleunigt Redirects von ~100ms auf ~10ms! 🚀
Der Cache beschleunigt Redirects von ~100ms auf ~10ms! 🚀

View file

@ -69,7 +69,7 @@ const config = {
api: 'https://ulo.ad/api/collections/',
// Admin NUR über SSH oder separaten Port
admin: 'Nicht öffentlich!'
admin: 'Nicht öffentlich!',
};
```

View file

@ -97,7 +97,7 @@ export function getVariantContent(variantId: string): VariantContent {
name: 'Control',
headline: m.hero_control_headline(),
subheadline: m.hero_control_subheadline(),
ctaText: m.hero_control_cta()
ctaText: m.hero_control_cta(),
// ...
};
case 'a1':
@ -107,7 +107,7 @@ export function getVariantContent(variantId: string): VariantContent {
headline: m.hero_a1_headline(),
subheadline: m.hero_a1_subheadline(),
ctaText: m.hero_a1_cta(),
features: [m.hero_a1_feature_1(), m.hero_a1_feature_2(), m.hero_a1_feature_3()]
features: [m.hero_a1_feature_1(), m.hero_a1_feature_2(), m.hero_a1_feature_3()],
// ...
};
// ...

File diff suppressed because it is too large Load diff

View file

@ -7,7 +7,7 @@ Basierend auf der Analyse empfehle ich die **PocketBase-Integration (Option 1)**
## Projektziele
- ✅ Content Marketing Platform für SEO & Thought Leadership
- ✅ Nahtlose Integration in bestehendes uload-System
- ✅ Nahtlose Integration in bestehendes uload-System
- ✅ Skalierbar für 100+ Artikel
- ✅ DSGVO-konform
- ✅ Mobile-optimiert
@ -16,75 +16,90 @@ Basierend auf der Analyse empfehle ich die **PocketBase-Integration (Option 1)**
## Timeline & Milestones
### Tag 1: Database & Backend Setup
**Ziel**: Komplette Datenbank-Struktur und API-Endpoints
#### Vormittag (4h)
- [ ] PocketBase Collections erstellen
- [ ] Relationen definieren
- [ ] Validation Rules setzen
- [ ] Test-Daten einfügen
#### Nachmittag (4h)
- [ ] Server-Routes implementieren
- [ ] API-Endpoints testen
- [ ] Error Handling
- [ ] Pagination Logic
### Tag 2: Frontend Basis-Komponenten
**Ziel**: Blog-Übersicht und Artikel-Ansicht funktionsfähig
#### Vormittag (4h)
- [ ] Blog-Übersichtsseite
- [ ] BlogCard Komponente
- [ ] Kategorie-Filter
- [ ] Tag-Cloud
#### Nachmittag (4h)
- [ ] Artikel-Detailseite
- [ ] Reading Progress Bar
- [ ] Table of Contents
- [ ] Share Buttons
### Tag 3: Admin-Interface
**Ziel**: Vollständiges CMS für Blog-Verwaltung
#### Vormittag (4h)
- [ ] Admin-Dashboard
- [ ] Artikel-Liste mit Status
- [ ] Bulk-Actions
- [ ] Suchfunktion
#### Nachmittag (4h)
- [ ] Rich-Text Editor (Tiptap)
- [ ] Media Upload
- [ ] Preview-Funktion
- [ ] Auto-Save
### Tag 4: SEO & Performance
**Ziel**: Optimale Sichtbarkeit und Geschwindigkeit
#### Vormittag (4h)
- [ ] Meta-Tags Management
- [ ] Schema.org Markup
- [ ] XML Sitemap
- [ ] RSS/Atom Feed
#### Nachmittag (4h)
- [ ] Image Optimization
- [ ] Lazy Loading
- [ ] Cache-Strategy
- [ ] CDN-Integration
### Tag 5: Features & Polish
**Ziel**: Premium-Features und finale Optimierungen
#### Vormittag (4h)
- [ ] Verwandte Artikel
- [ ] Lesezeit-Berechnung
- [ ] Newsletter-Integration
- [ ] Social Media Auto-Post
#### Nachmittag (4h)
- [ ] Analytics Dashboard
- [ ] A/B Testing Setup
- [ ] Mobile Optimierung
@ -146,21 +161,21 @@ CREATE TABLE blog_post_tags (
```typescript
// Blog API Routes
GET /api/blog/posts // Liste mit Pagination
GET /api/blog/posts/[slug] // Einzelner Artikel
POST /api/blog/posts/[slug]/view // View Counter
GET /api/blog/categories // Alle Kategorien
GET /api/blog/tags // Alle Tags
GET /api/blog/search // Volltextsuche
GET /api/blog/feed.xml // RSS Feed
GET /api/blog/sitemap.xml // Sitemap
GET / api / blog / posts; // Liste mit Pagination
GET / api / blog / posts / [slug]; // Einzelner Artikel
POST / api / blog / posts / [slug] / view; // View Counter
GET / api / blog / categories; // Alle Kategorien
GET / api / blog / tags; // Alle Tags
GET / api / blog / search; // Volltextsuche
GET / api / blog / feed.xml; // RSS Feed
GET / api / blog / sitemap.xml; // Sitemap
// Admin API Routes (auth required)
POST /api/admin/blog/posts // Neuer Artikel
PUT /api/admin/blog/posts/[id] // Update
DELETE /api/admin/blog/posts/[id] // Löschen
POST /api/admin/blog/posts/[id]/publish // Veröffentlichen
POST /api/admin/blog/upload // Bild-Upload
POST / api / admin / blog / posts; // Neuer Artikel
PUT / api / admin / blog / posts / [id]; // Update
DELETE / api / admin / blog / posts / [id]; // Löschen
POST / api / admin / blog / posts / [id] / publish; // Veröffentlichen
POST / api / admin / blog / upload; // Bild-Upload
```
### Komponenten-Architektur
@ -212,18 +227,21 @@ src/lib/components/blog/
### Content-Kalender (erste 3 Monate)
**Monat 1**: Foundations (4 Artikel/Monat)
- Woche 1: Psychology & UX
- Woche 2: Technical Tutorial
- Woche 3: Case Study
- Woche 4: Industry Trends
**Monat 2**: Deep Dives (6 Artikel/Monat)
- Analytics Deep Dives
- Integration Guides
- Performance Optimization
- Security Best Practices
**Monat 3**: Growth (8 Artikel/Monat)
- Guest Posts
- User Success Stories
- Feature Announcements
@ -234,11 +252,13 @@ src/lib/components/blog/
### Target Keywords
**Primary Keywords** (Schwierigkeit: Mittel)
- "url kürzen" (9.900 Suchen/Monat)
- "link verkürzen" (6.600 Suchen/Monat)
- "kurze urls" (2.400 Suchen/Monat)
**Long-Tail Keywords** (Schwierigkeit: Leicht)
- "kostenlos url kürzen ohne anmeldung"
- "eigene domain für kurze links"
- "qr code mit logo erstellen"
@ -260,12 +280,14 @@ src/lib/components/blog/
## Performance KPIs
### Technical Metrics
- **Page Load**: < 2s (Mobile 3G)
- **Time to Interactive**: < 3.5s
- **Core Web Vitals**: Alle grün
- **Lighthouse Score**: > 90
### Business Metrics
- **Organic Traffic**: +50% in 3 Monaten
- **Conversion Rate**: Blog → Sign-up > 3%
- **Engagement Rate**: > 2 Min Average Time
@ -275,26 +297,29 @@ src/lib/components/blog/
### Potenzielle Risiken & Mitigationen
| Risiko | Wahrscheinlichkeit | Impact | Mitigation |
|--------|-------------------|---------|------------|
| Editor-Komplexität | Mittel | Hoch | Start mit SimpleMDE, später Upgrade |
| Performance-Probleme | Niedrig | Mittel | Caching-Strategy von Anfang an |
| Content-Erstellung | Hoch | Mittel | Freelancer-Pool aufbauen |
| SEO-Konkurrenz | Mittel | Niedrig | Nischen-Keywords fokussieren |
| Risiko | Wahrscheinlichkeit | Impact | Mitigation |
| -------------------- | ------------------ | ------- | ----------------------------------- |
| Editor-Komplexität | Mittel | Hoch | Start mit SimpleMDE, später Upgrade |
| Performance-Probleme | Niedrig | Mittel | Caching-Strategy von Anfang an |
| Content-Erstellung | Hoch | Mittel | Freelancer-Pool aufbauen |
| SEO-Konkurrenz | Mittel | Niedrig | Nischen-Keywords fokussieren |
## Budget & Ressourcen
### Entwicklung (5 Tage)
- **Developer**: 5 Tage × 8h = 40h
- **Design Assets**: Vorhandene UI-Komponenten nutzen
- **Testing**: 1 Tag zusätzlich
### Content (Ongoing)
- **Launch Content**: 5 Artikel (intern)
- **Monthly Content**: 4-8 Artikel
- **Freelancer Budget**: €500-1000/Monat
### Tools & Services
- **Grammarly**: Rechtschreibprüfung
- **Canva Pro**: Grafiken
- **Unsplash+**: Stock-Fotos
@ -305,6 +330,7 @@ src/lib/components/blog/
### Pre-Launch Checklist
#### Funktionalität
- [ ] Alle CRUD-Operationen funktionieren
- [ ] Pagination arbeitet korrekt
- [ ] Suche liefert relevante Ergebnisse
@ -312,24 +338,28 @@ src/lib/components/blog/
- [ ] RSS Feed validiert
#### Performance
- [ ] Lighthouse Audit > 90
- [ ] Mobile-Responsive
- [ ] Bilder optimiert
- [ ] Caching aktiviert
#### SEO
- [ ] Meta-Tags vollständig
- [ ] Sitemap generiert
- [ ] Schema.org implementiert
- [ ] Social Cards funktionieren
#### Security
- [ ] Input-Validation
- [ ] XSS-Protection
- [ ] CSRF-Token
- [ ] Rate Limiting
#### Accessibility
- [ ] WCAG 2.1 AA konform
- [ ] Keyboard-Navigation
- [ ] Screen-Reader kompatibel
@ -338,18 +368,21 @@ src/lib/components/blog/
## Post-Launch Plan
### Woche 1
- Monitoring Setup
- Bug Fixes
- Performance Tuning
- Erste Analytics
### Monat 1
- Content-Pipeline etablieren
- SEO-Optimierungen
- User-Feedback sammeln
- A/B Tests starten
### Quartal 1
- Feature-Erweiterungen
- Newsletter-Integration
- Kommentar-System
@ -358,16 +391,19 @@ src/lib/components/blog/
## Success Metrics
### Launch (Tag 1)
- ✅ Blog live und funktional
- ✅ 5 Launch-Artikel online
- ✅ Keine kritischen Bugs
### Monat 1
- ✅ 20+ Blog-Artikel
- ✅ 1000+ Unique Visitors
- ✅ 5+ Backlinks
### Quartal 1
- ✅ 50+ Blog-Artikel
- ✅ 10.000+ Monthly Visitors
- ✅ Top 10 Rankings für Target Keywords
@ -394,4 +430,4 @@ src/lib/components/blog/
**Empfehlung**: Sofortiger Start mit der PocketBase-Integration. Der vorgeschlagene 5-Tage-Plan ist realistisch und liefert ein production-ready Blog-System, das perfekt in die bestehende uload-Architektur passt.
**Kontakt für Rückfragen**: Bei Fragen zur Implementierung oder für Detail-Diskussionen stehe ich zur Verfügung.
**Kontakt für Rückfragen**: Bei Fragen zur Implementierung oder für Detail-Diskussionen stehe ich zur Verfügung.

View file

@ -3,7 +3,9 @@
## Übersicht: 3 Ansätze für statische Markdown-Blogs
### 1. **mdsvex** - Markdown als Svelte-Komponenten (Empfohlen)
### 2. **Vite Glob Import** - Dynamisches Laden zur Build-Zeit
### 2. **Vite Glob Import** - Dynamisches Laden zur Build-Zeit
### 3. **Content Collections** - Strukturierte Markdown-Verwaltung
---
@ -26,7 +28,7 @@ import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [sveltekit()]
plugins: [sveltekit()],
});
```
@ -41,23 +43,20 @@ import rehypeAutolinkHeadings from 'rehype-autolink-headings';
/** @type {import('mdsvex').MdsvexOptions} */
const mdsvexOptions = {
extensions: ['.md', '.mdx'],
layout: {
blog: './src/lib/layouts/BlogLayout.svelte'
},
rehypePlugins: [
rehypeSlug,
[rehypeAutolinkHeadings, { behavior: 'wrap' }]
]
extensions: ['.md', '.mdx'],
layout: {
blog: './src/lib/layouts/BlogLayout.svelte',
},
rehypePlugins: [rehypeSlug, [rehypeAutolinkHeadings, { behavior: 'wrap' }]],
};
/** @type {import('@sveltejs/kit').Config} */
const config = {
extensions: ['.svelte', '.md', '.mdx'],
preprocess: [vitePreprocess(), mdsvex(mdsvexOptions)],
kit: {
adapter: adapter()
}
extensions: ['.svelte', '.md', '.mdx'],
preprocess: [vitePreprocess(), mdsvex(mdsvexOptions)],
kit: {
adapter: adapter(),
},
};
export default config;
@ -68,54 +67,54 @@ export default config;
```svelte
<!-- src/lib/layouts/BlogLayout.svelte -->
<script>
export let title = '';
export let date = '';
export let author = '';
export let excerpt = '';
export let tags = [];
export let image = '';
let formattedDate = $derived(
new Date(date).toLocaleDateString('de-DE', {
year: 'numeric',
month: 'long',
day: 'numeric'
})
);
export let title = '';
export let date = '';
export let author = '';
export let excerpt = '';
export let tags = [];
export let image = '';
let formattedDate = $derived(
new Date(date).toLocaleDateString('de-DE', {
year: 'numeric',
month: 'long',
day: 'numeric',
})
);
</script>
<svelte:head>
<title>{title} | uload Blog</title>
<meta name="description" content={excerpt} />
<meta property="og:title" content={title} />
<meta property="og:description" content={excerpt} />
{#if image}
<meta property="og:image" content={image} />
{/if}
<title>{title} | uload Blog</title>
<meta name="description" content={excerpt} />
<meta property="og:title" content={title} />
<meta property="og:description" content={excerpt} />
{#if image}
<meta property="og:image" content={image} />
{/if}
</svelte:head>
<article class="prose prose-lg mx-auto px-4 py-8 max-w-4xl">
<header class="mb-8">
<h1 class="text-4xl font-bold mb-2">{title}</h1>
<div class="text-gray-600 flex gap-4 items-center">
<time datetime={date}>{formattedDate}</time>
<span></span>
<span>{author}</span>
</div>
{#if tags.length > 0}
<div class="flex gap-2 mt-4">
{#each tags as tag}
<span class="bg-gray-100 px-3 py-1 rounded-full text-sm">
{tag}
</span>
{/each}
</div>
{/if}
</header>
<div class="content">
<slot />
</div>
<header class="mb-8">
<h1 class="text-4xl font-bold mb-2">{title}</h1>
<div class="text-gray-600 flex gap-4 items-center">
<time datetime={date}>{formattedDate}</time>
<span></span>
<span>{author}</span>
</div>
{#if tags.length > 0}
<div class="flex gap-2 mt-4">
{#each tags as tag}
<span class="bg-gray-100 px-3 py-1 rounded-full text-sm">
{tag}
</span>
{/each}
</div>
{/if}
</header>
<div class="content">
<slot />
</div>
</article>
```
@ -162,52 +161,52 @@ src/routes/
```javascript
// src/routes/blog/+page.js
export async function load() {
const posts = import.meta.glob('/src/content/blog/*.md');
const postPromises = Object.entries(posts).map(async ([path, resolver]) => {
const { metadata } = await resolver();
const slug = path.split('/').pop().replace('.md', '');
return {
slug,
...metadata
};
});
const allPosts = await Promise.all(postPromises);
// Nach Datum sortieren
allPosts.sort((a, b) => new Date(b.date) - new Date(a.date));
return {
posts: allPosts
};
const posts = import.meta.glob('/src/content/blog/*.md');
const postPromises = Object.entries(posts).map(async ([path, resolver]) => {
const { metadata } = await resolver();
const slug = path.split('/').pop().replace('.md', '');
return {
slug,
...metadata,
};
});
const allPosts = await Promise.all(postPromises);
// Nach Datum sortieren
allPosts.sort((a, b) => new Date(b.date) - new Date(a.date));
return {
posts: allPosts,
};
}
```
```svelte
<!-- src/routes/blog/+page.svelte -->
<script>
let { data } = $props();
let { data } = $props();
</script>
<div class="container mx-auto px-4 py-8">
<h1 class="text-3xl font-bold mb-8">Blog</h1>
<div class="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
{#each data.posts as post}
<article class="border rounded-lg p-6 hover:shadow-lg transition">
<h2 class="text-xl font-semibold mb-2">
<a href="/blog/{post.slug}" class="hover:text-blue-600">
{post.title}
</a>
</h2>
<p class="text-gray-600 mb-4">{post.excerpt}</p>
<div class="text-sm text-gray-500">
{new Date(post.date).toLocaleDateString('de-DE')}
</div>
</article>
{/each}
</div>
<h1 class="text-3xl font-bold mb-8">Blog</h1>
<div class="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
{#each data.posts as post}
<article class="border rounded-lg p-6 hover:shadow-lg transition">
<h2 class="text-xl font-semibold mb-2">
<a href="/blog/{post.slug}" class="hover:text-blue-600">
{post.title}
</a>
</h2>
<p class="text-gray-600 mb-4">{post.excerpt}</p>
<div class="text-sm text-gray-500">
{new Date(post.date).toLocaleDateString('de-DE')}
</div>
</article>
{/each}
</div>
</div>
```
@ -231,13 +230,13 @@ import { marked } from 'marked';
import matter from 'gray-matter';
export function parseMarkdown(content) {
const { data, content: markdown } = matter(content);
const html = marked(markdown);
return {
metadata: data,
html
};
const { data, content: markdown } = matter(content);
const html = marked(markdown);
return {
metadata: data,
html,
};
}
```
@ -248,28 +247,28 @@ export function parseMarkdown(content) {
import { parseMarkdown } from '$lib/utils/markdown';
export async function load() {
const postFiles = import.meta.glob('/src/content/blog/*.md', {
query: '?raw',
import: 'default'
});
const posts = await Promise.all(
Object.entries(postFiles).map(async ([path, resolver]) => {
const content = await resolver();
const { metadata, html } = parseMarkdown(content);
const slug = path.split('/').pop().replace('.md', '');
return {
slug,
...metadata,
content: html
};
})
);
return {
posts: posts.sort((a, b) => new Date(b.date) - new Date(a.date))
};
const postFiles = import.meta.glob('/src/content/blog/*.md', {
query: '?raw',
import: 'default',
});
const posts = await Promise.all(
Object.entries(postFiles).map(async ([path, resolver]) => {
const content = await resolver();
const { metadata, html } = parseMarkdown(content);
const slug = path.split('/').pop().replace('.md', '');
return {
slug,
...metadata,
content: html,
};
})
);
return {
posts: posts.sort((a, b) => new Date(b.date) - new Date(a.date)),
};
}
```
@ -281,37 +280,37 @@ import { parseMarkdown } from '$lib/utils/markdown';
import { error } from '@sveltejs/kit';
export async function load({ params }) {
try {
const post = await import(`../../../content/blog/${params.slug}.md?raw`);
const { metadata, html } = parseMarkdown(post.default);
return {
...metadata,
content: html,
slug: params.slug
};
} catch (e) {
throw error(404, 'Post nicht gefunden');
}
try {
const post = await import(`../../../content/blog/${params.slug}.md?raw`);
const { metadata, html } = parseMarkdown(post.default);
return {
...metadata,
content: html,
slug: params.slug,
};
} catch (e) {
throw error(404, 'Post nicht gefunden');
}
}
```
```svelte
<!-- src/routes/blog/[slug]/+page.svelte -->
<script>
let { data } = $props();
let { data } = $props();
</script>
<svelte:head>
<title>{data.title}</title>
<meta name="description" content={data.excerpt} />
<title>{data.title}</title>
<meta name="description" content={data.excerpt} />
</svelte:head>
<article class="prose mx-auto px-4 py-8">
<h1>{data.title}</h1>
<time>{new Date(data.date).toLocaleDateString('de-DE')}</time>
{@html data.content}
<h1>{data.title}</h1>
<time>{new Date(data.date).toLocaleDateString('de-DE')}</time>
{@html data.content}
</article>
```
@ -340,20 +339,20 @@ src/content/
import { z } from 'zod';
export const blogSchema = z.object({
title: z.string(),
date: z.date(),
author: z.string(),
excerpt: z.string(),
tags: z.array(z.string()),
image: z.string().optional(),
draft: z.boolean().default(false)
title: z.string(),
date: z.date(),
author: z.string(),
excerpt: z.string(),
tags: z.array(z.string()),
image: z.string().optional(),
draft: z.boolean().default(false),
});
export const collections = {
blog: {
schema: blogSchema,
directory: 'src/content/blog'
}
blog: {
schema: blogSchema,
directory: 'src/content/blog',
},
};
```
@ -366,45 +365,45 @@ import matter from 'gray-matter';
import { marked } from 'marked';
export async function getCollection(collection) {
const posts = import.meta.glob('/src/content/blog/*.md', {
query: '?raw',
import: 'default'
});
const entries = await Promise.all(
Object.entries(posts).map(async ([path, resolver]) => {
// Drafts überspringen
if (path.includes('_drafts')) return null;
const content = await resolver();
const { data, content: markdown } = matter(content);
// Schema validieren
const metadata = blogSchema.parse({
...data,
date: new Date(data.date)
});
// Draft-Posts in Production ausblenden
if (metadata.draft && import.meta.env.PROD) return null;
const slug = path.split('/').pop().replace('.md', '');
const html = marked(markdown);
return {
slug,
...metadata,
content: html
};
})
);
return entries.filter(Boolean);
const posts = import.meta.glob('/src/content/blog/*.md', {
query: '?raw',
import: 'default',
});
const entries = await Promise.all(
Object.entries(posts).map(async ([path, resolver]) => {
// Drafts überspringen
if (path.includes('_drafts')) return null;
const content = await resolver();
const { data, content: markdown } = matter(content);
// Schema validieren
const metadata = blogSchema.parse({
...data,
date: new Date(data.date),
});
// Draft-Posts in Production ausblenden
if (metadata.draft && import.meta.env.PROD) return null;
const slug = path.split('/').pop().replace('.md', '');
const html = marked(markdown);
return {
slug,
...metadata,
content: html,
};
})
);
return entries.filter(Boolean);
}
export async function getEntry(collection, slug) {
const posts = await getCollection(collection);
return posts.find(post => post.slug === slug);
const posts = await getCollection(collection);
return posts.find((post) => post.slug === slug);
}
```
@ -419,31 +418,35 @@ export async function getEntry(collection, slug) {
import { getCollection } from '$lib/content';
export async function GET() {
const posts = await getCollection('blog');
const site = 'https://ulo.ad';
const xml = `<?xml version="1.0" encoding="UTF-8"?>
const posts = await getCollection('blog');
const site = 'https://ulo.ad';
const xml = `<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
<channel>
<title>uload Blog</title>
<link>${site}/blog</link>
<description>Insights über URLs, Marketing und Psychologie</description>
${posts.map(post => `
${posts
.map(
(post) => `
<item>
<title>${post.title}</title>
<link>${site}/blog/${post.slug}</link>
<description>${post.excerpt}</description>
<pubDate>${new Date(post.date).toUTCString()}</pubDate>
</item>
`).join('')}
`
)
.join('')}
</channel>
</rss>`;
return new Response(xml, {
headers: {
'Content-Type': 'application/xml'
}
});
return new Response(xml, {
headers: {
'Content-Type': 'application/xml',
},
});
}
```
@ -452,26 +455,30 @@ export async function GET() {
```javascript
// src/routes/sitemap.xml/+server.js
export async function GET() {
const posts = await getCollection('blog');
const site = 'https://ulo.ad';
const xml = `<?xml version="1.0" encoding="UTF-8"?>
const posts = await getCollection('blog');
const site = 'https://ulo.ad';
const xml = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${posts.map(post => `
${posts
.map(
(post) => `
<url>
<loc>${site}/blog/${post.slug}</loc>
<lastmod>${new Date(post.date).toISOString()}</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
`).join('')}
`
)
.join('')}
</urlset>`;
return new Response(xml, {
headers: {
'Content-Type': 'application/xml'
}
});
return new Response(xml, {
headers: {
'Content-Type': 'application/xml',
},
});
}
```
@ -486,10 +493,10 @@ npm install -D shiki
import { codeToHtml } from 'shiki';
export async function highlightCode(code, lang = 'javascript') {
return await codeToHtml(code, {
lang,
theme: 'github-dark'
});
return await codeToHtml(code, {
lang,
theme: 'github-dark',
});
}
```
@ -498,15 +505,15 @@ export async function highlightCode(code, lang = 'javascript') {
```javascript
// src/lib/utils/reading-time.js
export function calculateReadingTime(content) {
const wordsPerMinute = 200;
const words = content.split(/\s+/).length;
const minutes = Math.ceil(words / wordsPerMinute);
return {
minutes,
words,
text: `${minutes} Min. Lesezeit`
};
const wordsPerMinute = 200;
const words = content.split(/\s+/).length;
const minutes = Math.ceil(words / wordsPerMinute);
return {
minutes,
words,
text: `${minutes} Min. Lesezeit`,
};
}
```
@ -515,19 +522,19 @@ export function calculateReadingTime(content) {
```javascript
// src/lib/utils/toc.js
export function extractHeadings(html) {
const regex = /<h([2-3])[^>]*id="([^"]*)"[^>]*>(.*?)<\/h\1>/g;
const headings = [];
let match;
while ((match = regex.exec(html)) !== null) {
headings.push({
level: parseInt(match[1]),
id: match[2],
text: match[3].replace(/<[^>]*>/g, '')
});
}
return headings;
const regex = /<h([2-3])[^>]*id="([^"]*)"[^>]*>(.*?)<\/h\1>/g;
const headings = [];
let match;
while ((match = regex.exec(html)) !== null) {
headings.push({
level: parseInt(match[1]),
id: match[2],
text: match[3].replace(/<[^>]*>/g, ''),
});
}
return headings;
}
```
@ -546,17 +553,17 @@ export const prerender = true; // Statisch zur Build-Zeit generieren
```svelte
<script>
import { inview } from 'svelte-inview';
let isInView = $state(false);
import { inview } from 'svelte-inview';
let isInView = $state(false);
</script>
<div use:inview on:inview_enter={() => isInView = true}>
{#if isInView}
<img src={image} alt={alt} loading="lazy" />
{:else}
<div class="skeleton h-64 bg-gray-200" />
{/if}
<div use:inview on:inview_enter={() => (isInView = true)}>
{#if isInView}
<img src={image} {alt} loading="lazy" />
{:else}
<div class="skeleton h-64 bg-gray-200" />
{/if}
</div>
```
@ -568,16 +575,16 @@ const cache = new Map();
const CACHE_DURATION = 1000 * 60 * 5; // 5 Minuten
export function getCached(key, fetcher) {
const cached = cache.get(key);
if (cached && Date.now() - cached.timestamp < CACHE_DURATION) {
return cached.data;
}
const data = fetcher();
cache.set(key, { data, timestamp: Date.now() });
return data;
const cached = cache.get(key);
if (cached && Date.now() - cached.timestamp < CACHE_DURATION) {
return cached.data;
}
const data = fetcher();
cache.set(key, { data, timestamp: Date.now() });
return data;
}
```
@ -590,16 +597,16 @@ export function getCached(key, fetcher) {
```javascript
// vite.config.js
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks: {
'markdown': ['marked', 'gray-matter'],
'highlight': ['shiki']
}
}
}
}
build: {
rollupOptions: {
output: {
manualChunks: {
markdown: ['marked', 'gray-matter'],
highlight: ['shiki'],
},
},
},
},
});
```
@ -614,17 +621,17 @@ npm install -D @sveltejs/adapter-static
import adapter from '@sveltejs/adapter-static';
export default {
kit: {
adapter: adapter({
pages: 'build',
assets: 'build',
fallback: null,
precompress: true
}),
prerender: {
entries: ['*'] // Alle Seiten prerendern
}
}
kit: {
adapter: adapter({
pages: 'build',
assets: 'build',
fallback: null,
precompress: true,
}),
prerender: {
entries: ['*'], // Alle Seiten prerendern
},
},
};
```
@ -692,4 +699,4 @@ Besuche `http://localhost:5173/blog`
---
**Empfehlung**: Starte mit **mdsvex** (Ansatz 1) - es bietet die beste Balance zwischen Einfachheit und Features, plus du kannst Svelte-Components direkt in Markdown verwenden!
**Empfehlung**: Starte mit **mdsvex** (Ansatz 1) - es bietet die beste Balance zwischen Einfachheit und Features, plus du kannst Svelte-Components direkt in Markdown verwenden!

File diff suppressed because it is too large Load diff

View file

@ -30,7 +30,7 @@ In einer Welt, in der über 60% des Web-Traffics von mobilen Geräten kommt, sin
Die Cognitive Load Theory erklärt, warum kurze URLs so effektiv sind. Unser Gehirn ist darauf programmiert, Energie zu sparen es ist evolutionär faul, aber auf eine intelligente Weise. Bei der Verarbeitung von Informationen sucht es immer nach dem Weg des geringsten Widerstands.
Wenn wir einen kurzen, klaren Link sehen, kann unser Gehirn ihn schnell verarbeiten und kategorisieren. Diese mühelose Verarbeitung erzeugt ein positives Gefühl wir verbinden "einfach" automatisch mit "sicher" und "vertrauenswürdig".
Wenn wir einen kurzen, klaren Link sehen, kann unser Gehirn ihn schnell verarbeiten und kategorisieren. Diese mühelose Verarbeitung erzeugt ein positives Gefühl wir verbinden "einfach" automatisch mit "sicher" und "vertrauenswürdig".
### Der Halo-Effekt kurzer URLs
@ -43,6 +43,7 @@ Unsere Analyse von über 10.000 Link-Klicks hat vier Hauptfaktoren identifiziert
### 1. Erkennbare Domain (60% Wichtigkeit)
Menschen wollen wissen, wo sie landen werden. Eine klare, erkennbare Domain ist der wichtigste Vertrauensfaktor. Das bedeutet:
- Verwenden Sie Ihre Marken-Domain wenn möglich
- Bei Kurz-URLs: Wählen Sie einen Service mit gutem Ruf
- Vermeiden Sie obskure URL-Shortener
@ -50,6 +51,7 @@ Menschen wollen wissen, wo sie landen werden. Eine klare, erkennbare Domain ist
### 2. Keine kryptischen Zeichen (25% Wichtigkeit)
Zufällige Zahlen-Buchstaben-Kombinationen wie "x7h9k2p" schrecken Nutzer ab. Stattdessen:
- Nutzen Sie sprechende Begriffe
- Verwenden Sie relevante Keywords
- Halten Sie es lesbar und merkbar
@ -57,6 +59,7 @@ Zufällige Zahlen-Buchstaben-Kombinationen wie "x7h9k2p" schrecken Nutzer ab. St
### 3. Optimale Länge (10% Wichtigkeit)
Die magische Grenze liegt bei etwa 50 Zeichen. Alles darüber hinaus wird als zu lang wahrgenommen. Studien zeigen:
- 15-30 Zeichen: Optimal für Social Media
- 30-50 Zeichen: Ideal für E-Mail-Marketing
- Über 50 Zeichen: Deutlicher Rückgang der Klickrate
@ -81,6 +84,7 @@ Sonderzeichen wie %, &, = oder ? in der sichtbaren URL verwirren Nutzer und ersc
### 3. Die 50-Zeichen-Regel
Halten Sie Ihre URLs unter 50 Zeichen. Das ist:
- Kurz genug für Twitter/X
- Lesbar auf Mobilgeräten
- Merkbar für Nutzer
@ -89,12 +93,14 @@ Halten Sie Ihre URLs unter 50 Zeichen. Das ist:
### 4. A/B-Testing ist Ihr Freund
Testen Sie verschiedene URL-Varianten:
- Kurz vs. deskriptiv
- Mit Markenname vs. ohne
- Verschiedene Keywords
- Unterschiedliche Strukturen
Messen Sie dabei:
- Klickrate (CTR)
- Conversion Rate
- Bounce Rate
@ -103,6 +109,7 @@ Messen Sie dabei:
### 5. Performance-Tracking implementieren
Ohne Daten keine Optimierung. Moderne Link-Management-Tools bieten:
- Detaillierte Klick-Statistiken
- Geografische Verteilung
- Geräteerkennung
@ -112,18 +119,22 @@ Ohne Daten keine Optimierung. Moderne Link-Management-Tools bieten:
## Psychologische Trigger in URLs nutzen
### Urgency (Dringlichkeit)
- `ulo.ad/flash-sale-24h`
- `ulo.ad/limited-offer`
### Curiosity (Neugier)
- `ulo.ad/geheimtipp-2024`
- `ulo.ad/insider-trick`
### Value (Wert)
- `ulo.ad/gratis-guide`
- `ulo.ad/50-prozent-rabatt`
### Social Proof
- `ulo.ad/bestseller-2024`
- `ulo.ad/meistgelesen`
@ -139,7 +150,8 @@ Zu viele technische Parameter lassen einen Link "unmenschlich" wirken. Nutzer sp
### Mobile Usability Disaster
Auf Smartphones werden lange URLs oft abgeschnitten oder umbrechen. Das Resultat:
Auf Smartphones werden lange URLs oft abgeschnitten oder umbrechen. Das Resultat:
- Unleserlichkeit
- Professioneller Eindruck geht verloren
- Nutzer können das Ziel nicht einschätzen
@ -150,6 +162,7 @@ Auf Smartphones werden lange URLs oft abgeschnitten oder umbrechen. Das Resultat
### E-Commerce: 67% mehr Conversions
Ein großer Online-Händler verkürzte seine Produkt-URLs von durchschnittlich 120 auf 45 Zeichen. Das Ergebnis:
- 67% höhere Conversion Rate
- 42% mehr Social Shares
- 31% niedrigere Bounce Rate
@ -157,6 +170,7 @@ Ein großer Online-Händler verkürzte seine Produkt-URLs von durchschnittlich 1
### Newsletter-Marketing: Verdoppelte Klickrate
Ein B2B-Unternehmen wechselte von langen Tracking-URLs zu personalisierten Kurz-URLs:
- Vorher: `company.com/newsletter/2024/march/article-5?utm_source=email&utm_medium=newsletter&subscriber=12345`
- Nachher: `co.link/cloud-guide`
- Resultat: 2,1x höhere Klickrate
@ -164,6 +178,7 @@ Ein B2B-Unternehmen wechselte von langen Tracking-URLs zu personalisierten Kurz-
### Social Media: 3x mehr Engagement
Ein Influencer nutzte branded Short-URLs statt generischer Affiliate-Links:
- Engagement stieg um 300%
- Trust-Score verbesserte sich um 85%
- Follower-Wachstum +45%
@ -173,6 +188,7 @@ Ein Influencer nutzte branded Short-URLs statt generischer Affiliate-Links:
### KI-optimierte Personalisierung
Moderne Systeme nutzen KI, um für jeden Nutzer die optimale URL-Variante zu generieren basierend auf:
- Demografischen Daten
- Bisherigem Klickverhalten
- Kontext der Interaktion
@ -181,6 +197,7 @@ Moderne Systeme nutzen KI, um für jeden Nutzer die optimale URL-Variante zu gen
### Voice-First Optimization
Mit dem Aufstieg von Sprachassistenten werden "sprechbare" URLs wichtiger:
- Einfache Wörter statt Buchstaben-Zahlen-Kombinationen
- Vermeidung ähnlich klingender Begriffe
- Klare, eindeutige Aussprache
@ -233,6 +250,7 @@ Die Psychologie kurzer URLs ist keine Raketenwissenschaft, aber ihre Auswirkunge
### Ein Werkzeug, das hilft
Tools wie [uload](https://ulo.ad) wurden speziell entwickelt, um die Erkenntnisse der URL-Psychologie in die Praxis umzusetzen. Mit Features wie:
- Personalisierte Kurz-URLs
- Detaillierte Analytics
- A/B-Testing
@ -246,10 +264,11 @@ können Sie sofort damit beginnen, Ihre Link-Performance zu optimieren.
**Über diesen Artikel**: Basierend auf aktuellen Studien zur Nutzerpsychologie und realen Performance-Daten von über 10 Millionen Link-Klicks. Die präsentierten Strategien wurden in der Praxis getestet und validiert.
**Weiterführende Ressourcen**:
- [Cognitive Load Theory in UX Design](https://www.nngroup.com/articles/minimize-cognitive-load/)
- [The Psychology of Web Performance](https://www.smashingmagazine.com/2024/01/psychology-web-performance/)
- [Mobile-First URL Strategy Guide](https://moz.com/blog/mobile-first-indexing)
---
*Haben Sie Fragen oder möchten Sie Ihre eigenen Erfahrungen teilen? Kontaktieren Sie uns oder hinterlassen Sie einen Kommentar. Wir freuen uns auf den Austausch!*
_Haben Sie Fragen oder möchten Sie Ihre eigenen Erfahrungen teilen? Kontaktieren Sie uns oder hinterlassen Sie einen Kommentar. Wir freuen uns auf den Austausch!_

View file

@ -88,9 +88,9 @@ await cardsStore.createCard({
id: 'header-1',
type: 'header',
props: { title: 'My Card' },
order: 0
}
]
order: 0,
},
],
});
// Advanced mode card
@ -98,7 +98,7 @@ await cardsStore.createCard({
mode: 'advanced',
template: '<h1>{{title}}</h1>',
variables: [{ name: 'title', type: 'text', label: 'Title' }],
values: { title: 'My Title' }
values: { title: 'My Title' },
});
```

View file

@ -29,10 +29,10 @@ Das Card System in uload ist eine flexible, modulare Architektur für die Erstel
type: 'header',
props: {
title: 'Meine Karte',
subtitle: 'Eine Beispielkarte'
}
}
]
subtitle: 'Eine Beispielkarte',
},
},
],
};
</script>

View file

@ -148,7 +148,7 @@ const theme = {
text: '#111827',
textMuted: '#6b7280',
border: '#e5e7eb',
hover: '#f3f4f6'
hover: '#f3f4f6',
},
typography: {
fontFamily: 'Inter, sans-serif',
@ -157,23 +157,23 @@ const theme = {
sm: '0.875rem',
md: '1rem',
lg: '1.125rem',
xl: '1.25rem'
}
xl: '1.25rem',
},
},
spacing: {
xs: '0.25rem',
sm: '0.5rem',
md: '1rem',
lg: '1.5rem',
xl: '2rem'
xl: '2rem',
},
borderRadius: {
sm: '0.25rem',
md: '0.5rem',
lg: '0.75rem',
xl: '1rem',
full: '9999px'
}
full: '9999px',
},
};
```
@ -279,7 +279,7 @@ interface LinksCardProps {
layout={{
padding: '1.5rem',
columns: 2,
gap: '1rem'
gap: '1rem',
}}
modules={[
{
@ -287,8 +287,8 @@ interface LinksCardProps {
props: {
title: 'Dashboard',
subtitle: 'Übersicht deiner Aktivitäten',
icon: '📊'
}
icon: '📊',
},
},
{
type: 'stats',
@ -296,21 +296,21 @@ interface LinksCardProps {
stats: [
{ label: 'Links', value: 42, icon: '🔗' },
{ label: 'Clicks', value: '1.2k', icon: '👆' },
{ label: 'Conversion', value: '24%', icon: '📈' }
{ label: 'Conversion', value: '24%', icon: '📈' },
],
layout: 'grid'
}
layout: 'grid',
},
},
{
type: 'actions',
props: {
actions: [
{ label: 'Neuer Link', variant: 'primary' },
{ label: 'Statistiken', variant: 'secondary' }
{ label: 'Statistiken', variant: 'secondary' },
],
layout: 'horizontal'
}
}
layout: 'horizontal',
},
},
]}
/>
```

View file

@ -17,9 +17,9 @@
props: {
title: 'Meine erste Karte',
subtitle: 'Ein einfaches Beispiel',
icon: '🎉'
}
}
icon: '🎉',
},
},
]}
/>
```
@ -35,15 +35,15 @@
order: 0,
props: {
title: 'Komplexere Karte',
subtitle: 'Mit mehreren Modulen'
}
subtitle: 'Mit mehreren Modulen',
},
},
{
type: 'content',
order: 1,
props: {
text: 'Dies ist der Hauptinhalt der Karte. Hier kann beliebiger Text stehen.'
}
text: 'Dies ist der Hauptinhalt der Karte. Hier kann beliebiger Text stehen.',
},
},
{
type: 'actions',
@ -51,10 +51,10 @@
props: {
actions: [
{ label: 'Mehr erfahren', variant: 'primary' },
{ label: 'Schließen', variant: 'ghost' }
]
}
}
{ label: 'Schließen', variant: 'ghost' },
],
},
},
]}
/>
```
@ -71,7 +71,7 @@
name: 'Max Mustermann',
role: 'Software Developer',
avatar: '/avatars/max.jpg',
bio: 'Passionate about creating amazing web experiences.'
bio: 'Passionate about creating amazing web experiences.',
};
</script>
@ -83,15 +83,15 @@
props: {
title: userData.name,
subtitle: userData.role,
avatar: userData.avatar
}
avatar: userData.avatar,
},
},
{
type: 'content',
props: {
text: userData.bio
}
}
text: userData.bio,
},
},
]}
/>
```
@ -109,14 +109,14 @@
title: 'Jane Doe',
subtitle: 'UX Designer',
avatar: '/avatars/jane.jpg',
badge: 'PRO'
}
badge: 'PRO',
},
},
{
type: 'content',
props: {
text: 'Creating beautiful and functional user experiences since 2015.'
}
text: 'Creating beautiful and functional user experiences since 2015.',
},
},
{
type: 'stats',
@ -124,10 +124,10 @@
stats: [
{ label: 'Projects', value: 127, icon: '📁' },
{ label: 'Clients', value: 45, icon: '👥' },
{ label: 'Awards', value: 8, icon: '🏆' }
{ label: 'Awards', value: 8, icon: '🏆' },
],
layout: 'grid'
}
layout: 'grid',
},
},
{
type: 'links',
@ -135,13 +135,13 @@
links: [
{ label: 'Portfolio', href: 'https://portfolio.com', icon: '🎨' },
{ label: 'LinkedIn', href: 'https://linkedin.com', icon: '💼' },
{ label: 'GitHub', href: 'https://github.com', icon: '💻' }
{ label: 'GitHub', href: 'https://github.com', icon: '💻' },
],
style: 'button',
showIcon: true
}
}
]
showIcon: true,
},
},
],
};
</script>
@ -160,8 +160,8 @@
type: 'header',
props: {
title: 'Verkaufsstatistik',
subtitle: 'Letzten 30 Tage'
}
subtitle: 'Letzten 30 Tage',
},
},
{
type: 'stats',
@ -169,11 +169,11 @@
stats: [
{ label: 'Umsatz', value: '€12.4k', change: 12, color: 'green' },
{ label: 'Bestellungen', value: 234, change: -5, color: 'red' },
{ label: 'Conversion', value: '3.2%', change: 8, color: 'blue' }
{ label: 'Conversion', value: '3.2%', change: 8, color: 'blue' },
],
layout: 'list'
}
}
layout: 'list',
},
},
]}
/>
```
@ -188,8 +188,8 @@
type: 'header',
props: {
title: 'Letzte Aktivitäten',
icon: '📋'
}
icon: '📋',
},
},
{
type: 'content',
@ -198,10 +198,10 @@
{ label: 'Neuer Benutzer', value: 'vor 5 Min.', icon: '👤' },
{ label: 'Bestellung #1234', value: 'vor 15 Min.', icon: '🛒' },
{ label: 'Kommentar erhalten', value: 'vor 1 Std.', icon: '💬' },
{ label: 'System-Update', value: 'vor 2 Std.', icon: '🔄' }
]
}
}
{ label: 'System-Update', value: 'vor 2 Std.', icon: '🔄' },
],
},
},
]}
/>
```
@ -218,8 +218,8 @@
type: 'header',
props: {
title: 'Folge mir',
subtitle: 'auf Social Media'
}
subtitle: 'auf Social Media',
},
},
{
type: 'links',
@ -228,14 +228,14 @@
{ label: 'Instagram', href: 'https://instagram.com/user', icon: '📷' },
{ label: 'Twitter', href: 'https://twitter.com/user', icon: '🐦' },
{ label: 'YouTube', href: 'https://youtube.com/user', icon: '📺' },
{ label: 'TikTok', href: 'https://tiktok.com/@user', icon: '🎵' }
{ label: 'TikTok', href: 'https://tiktok.com/@user', icon: '🎵' },
],
style: 'button',
columns: 2,
buttonVariant: 'secondary',
showIcon: true
}
}
showIcon: true,
},
},
]}
/>
```
@ -249,8 +249,8 @@
{
type: 'header',
props: {
title: 'Nützliche Ressourcen'
}
title: 'Nützliche Ressourcen',
},
},
{
type: 'links',
@ -260,26 +260,26 @@
label: 'Dokumentation',
href: '/docs',
icon: '📚',
description: 'Vollständige API-Dokumentation'
description: 'Vollständige API-Dokumentation',
},
{
label: 'Tutorials',
href: '/tutorials',
icon: '🎓',
description: 'Schritt-für-Schritt Anleitungen'
description: 'Schritt-für-Schritt Anleitungen',
},
{
label: 'Community Forum',
href: '/forum',
icon: '💬',
description: 'Hilfe von der Community'
}
description: 'Hilfe von der Community',
},
],
style: 'card',
showDescription: true,
showIcon: true
}
}
showIcon: true,
},
},
]}
/>
```
@ -295,8 +295,8 @@
{
type: 'header',
props: {
title: 'Projekt Screenshots'
}
title: 'Projekt Screenshots',
},
},
{
type: 'media',
@ -304,15 +304,15 @@
type: 'image',
src: '/screenshots/dashboard.png',
alt: 'Dashboard Screenshot',
aspectRatio: '16/9'
}
aspectRatio: '16/9',
},
},
{
type: 'content',
props: {
text: 'Das neue Dashboard-Design mit verbesserter Benutzerführung.'
}
}
text: 'Das neue Dashboard-Design mit verbesserter Benutzerführung.',
},
},
]}
/>
```
@ -327,8 +327,8 @@
type: 'header',
props: {
title: 'Mein QR-Code',
subtitle: 'Scanne für Kontaktdaten'
}
subtitle: 'Scanne für Kontaktdaten',
},
},
{
type: 'media',
@ -336,9 +336,9 @@
type: 'qr',
qrData: 'https://example.com/contact',
qrSize: 200,
qrColor: '#000000'
}
}
qrColor: '#000000',
},
},
]}
/>
```
@ -361,8 +361,8 @@
text: '#f9fafb',
textMuted: '#9ca3af',
border: '#374151',
hover: '#374151'
}
hover: '#374151',
},
};
</script>
@ -374,9 +374,9 @@
type: 'header',
props: {
title: 'Dark Mode Karte',
subtitle: 'Mit custom Theme'
}
}
subtitle: 'Mit custom Theme',
},
},
]}
/>
</ThemeProvider>
@ -392,8 +392,8 @@
secondary: '#4ecdc4',
accent: '#45b7d1',
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
text: '#ffffff'
}
text: '#ffffff',
},
};
</script>
@ -405,9 +405,9 @@
type: 'header',
props: {
title: 'Gradient Card',
subtitle: 'Mit Farbverlauf'
}
}
subtitle: 'Mit Farbverlauf',
},
},
]}
/>
```
@ -495,28 +495,28 @@
breakpoints: {
sm: '640px',
md: '768px',
lg: '1024px'
lg: '1024px',
},
mobileLayout: 'stack'
mobileLayout: 'stack',
}}
modules={[
{
type: 'header',
visibility: 'always',
props: { title: 'Responsive Karte' }
props: { title: 'Responsive Karte' },
},
{
type: 'content',
visibility: 'desktop', // Nur auf Desktop
props: { text: 'Dieser Text ist nur auf Desktop sichtbar.' }
props: { text: 'Dieser Text ist nur auf Desktop sichtbar.' },
},
{
type: 'actions',
visibility: 'mobile', // Nur auf Mobile
props: {
actions: [{ label: 'Mobile Action', variant: 'primary' }]
}
}
actions: [{ label: 'Mobile Action', variant: 'primary' }],
},
},
]}
/>
```
@ -532,16 +532,16 @@
hover: true,
entrance: 'slide',
duration: 300,
delay: 100
delay: 100,
}}
modules={[
{
type: 'header',
props: {
title: 'Animierte Karte',
subtitle: 'Mit Slide-In Effekt'
}
}
subtitle: 'Mit Slide-In Effekt',
},
},
]}
/>
```

View file

@ -217,7 +217,7 @@ const trafficDistribution = {
control: 40, // Baseline
a1: 20, // Value Test
b1: 20, // Social Test
c1: 20 // Feature Test
c1: 20, // Feature Test
};
```
@ -230,7 +230,7 @@ const trafficDistribution = {
control: 30, // Reduziert
a2: 30, // Winner A (neue Version)
b1: 20, // B bleibt
c2: 20 // C iteriert
c2: 20, // C iteriert
};
```
@ -241,7 +241,7 @@ const trafficDistribution = {
champion: 70, // Bester Performer
challenger1: 10, // Neue Idee
challenger2: 10, // Neue Idee
challenger3: 10 // Neue Idee
challenger3: 10, // Neue Idee
};
```
@ -445,7 +445,7 @@ export class HashManager {
control: 40,
a1: 20,
b1: 20,
c1: 20
c1: 20,
};
getVariant(): string {
@ -508,7 +508,7 @@ export class HashManager {
control: HeroControl,
a1: HeroA1,
a2: HeroA2,
b1: HeroB1
b1: HeroB1,
// ... more mappings
};

View file

@ -3,6 +3,7 @@
## Problemanalyse
Das uLoad-Projekt war kürzlich komplett down, was zu kritischen Problemen geführt hat:
- Hauptanwendung nicht erreichbar
- Weiterleitungen funktionierten nicht
- Benutzererfahrung stark beeinträchtigt
@ -11,6 +12,7 @@ Das uLoad-Projekt war kürzlich komplett down, was zu kritischen Problemen gefü
## Aktuelle Architektur-Analyse
### Technology Stack
- **Frontend:** SvelteKit 2.22 mit Svelte 5.0
- **Backend:** PocketBase (https://pb.ulo.ad)
- **Hosting:** Hetzner VPS mit Coolify
@ -44,32 +46,34 @@ Das uLoad-Projekt war kürzlich komplett down, was zu kritischen Problemen gefü
### 1. Verbesserte Error Handling & Fallbacks
#### PocketBase Connection Resilience
```typescript
// src/lib/pocketbase-resilient.ts
class ResilientPocketBase {
private retryCount = 0;
private maxRetries = 3;
private backoffMs = 1000;
private retryCount = 0;
private maxRetries = 3;
private backoffMs = 1000;
async withRetry<T>(operation: () => Promise<T>): Promise<T> {
for (let i = 0; i <= this.maxRetries; i++) {
try {
return await operation();
} catch (error) {
if (i === this.maxRetries) throw error;
await this.delay(this.backoffMs * Math.pow(2, i));
}
}
throw new Error('Max retries exceeded');
}
async withRetry<T>(operation: () => Promise<T>): Promise<T> {
for (let i = 0; i <= this.maxRetries; i++) {
try {
return await operation();
} catch (error) {
if (i === this.maxRetries) throw error;
await this.delay(this.backoffMs * Math.pow(2, i));
}
}
throw new Error('Max retries exceeded');
}
private delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
private delay(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
}
```
#### Graceful Degradation
- Cache-basierte Fallbacks für kritische Daten
- Offline-Mode für Basis-Funktionalität
- Error Boundaries in allen Komponenten
@ -77,37 +81,40 @@ class ResilientPocketBase {
### 2. Enhanced Monitoring
#### Health Check Verbesserung
```typescript
// src/routes/health/+server.ts erweitern
export const GET: RequestHandler = async () => {
const health = {
status: 'healthy',
timestamp: new Date().toISOString(),
environment: building ? 'build' : 'runtime',
services: {
sveltekit: 'running',
pocketbase: await checkPocketBaseDetailed(),
database: await checkDatabaseHealth(),
memory: process.memoryUsage(),
uptime: process.uptime()
},
checks: {
canCreateLink: await testLinkCreation(),
canAuthenticate: await testAuthentication(),
canServeStatic: await testStaticFiles()
}
};
const health = {
status: 'healthy',
timestamp: new Date().toISOString(),
environment: building ? 'build' : 'runtime',
services: {
sveltekit: 'running',
pocketbase: await checkPocketBaseDetailed(),
database: await checkDatabaseHealth(),
memory: process.memoryUsage(),
uptime: process.uptime(),
},
checks: {
canCreateLink: await testLinkCreation(),
canAuthenticate: await testAuthentication(),
canServeStatic: await testStaticFiles(),
},
};
const overallStatus = Object.values(health.services).every(s => s === 'running')
? 'healthy' : 'degraded';
const overallStatus = Object.values(health.services).every((s) => s === 'running')
? 'healthy'
: 'degraded';
return json(health, {
status: overallStatus === 'healthy' ? 200 : 503
});
return json(health, {
status: overallStatus === 'healthy' ? 200 : 503,
});
};
```
#### Externes Monitoring Setup
- Uptime Robot/Pingdom für externe Überwachung
- Slack/Discord Webhooks für Alerts
- Grafana Dashboard für Metriken
@ -115,32 +122,34 @@ export const GET: RequestHandler = async () => {
### 3. Improved Rate Limiting
#### Redis-Backend für Rate Limiting
```typescript
// src/lib/server/redis-rate-limiter.ts
import Redis from 'ioredis';
class RedisRateLimiter {
private redis: Redis;
private redis: Redis;
constructor() {
this.redis = new Redis(process.env.REDIS_URL || 'redis://localhost:6379');
}
constructor() {
this.redis = new Redis(process.env.REDIS_URL || 'redis://localhost:6379');
}
async checkLimit(key: string, limit: number, windowMs: number): Promise<boolean> {
const multi = this.redis.multi();
multi.incr(key);
multi.expire(key, Math.ceil(windowMs / 1000));
const results = await multi.exec();
const count = results?.[0]?.[1] as number;
return count <= limit;
}
async checkLimit(key: string, limit: number, windowMs: number): Promise<boolean> {
const multi = this.redis.multi();
multi.incr(key);
multi.expire(key, Math.ceil(windowMs / 1000));
const results = await multi.exec();
const count = results?.[0]?.[1] as number;
return count <= limit;
}
}
```
### 4. Database Backup Strategy
#### Automatisierte PocketBase Backups
```bash
#!/bin/bash
# scripts/backup-pocketbase.sh
@ -167,6 +176,7 @@ fi
### 1. Infrastructure Redundancy
#### Load Balancer Setup
```yaml
# docker-compose.prod-ha.yml
version: '3.8'
@ -174,8 +184,8 @@ services:
nginx-lb:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
- '80:80'
- '443:443'
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
depends_on:
@ -201,6 +211,7 @@ volumes:
```
#### Multi-Region Deployment
- Hauptserver: Hetzner Deutschland
- Backup Server: AWS/DigitalOcean anderer Region
- DNS Failover mit niedrigem TTL (60 Sekunden)
@ -208,6 +219,7 @@ volumes:
### 2. Separated Services Architecture
#### PocketBase als separater Service
```yaml
# docker-compose.services.yml
services:
@ -216,10 +228,10 @@ services:
volumes:
- ./pb_data:/pb/pb_data
ports:
- "8090:8090"
- '8090:8090'
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8090/api/health"]
test: ['CMD', 'curl', '-f', 'http://localhost:8090/api/health']
interval: 30s
timeout: 10s
retries: 3
@ -237,32 +249,33 @@ services:
### 3. Caching Layer
#### Redis für Caching & Sessions
```typescript
// src/lib/cache/redis-cache.ts
export class CacheManager {
private redis: Redis;
private redis: Redis;
async get<T>(key: string): Promise<T | null> {
const cached = await this.redis.get(key);
return cached ? JSON.parse(cached) : null;
}
async get<T>(key: string): Promise<T | null> {
const cached = await this.redis.get(key);
return cached ? JSON.parse(cached) : null;
}
async set(key: string, value: any, ttlSeconds: number = 3600): Promise<void> {
await this.redis.setex(key, ttlSeconds, JSON.stringify(value));
}
async set(key: string, value: any, ttlSeconds: number = 3600): Promise<void> {
await this.redis.setex(key, ttlSeconds, JSON.stringify(value));
}
// Cache häufig abgerufene Daten
async getCachedLinks(userId: string): Promise<Link[]> {
const cacheKey = `user:${userId}:links`;
let links = await this.get<Link[]>(cacheKey);
if (!links) {
links = await pb.collection('links').getFullList({ filter: `user_id="${userId}"` });
await this.set(cacheKey, links, 300); // 5 Minuten Cache
}
return links;
}
// Cache häufig abgerufene Daten
async getCachedLinks(userId: string): Promise<Link[]> {
const cacheKey = `user:${userId}:links`;
let links = await this.get<Link[]>(cacheKey);
if (!links) {
links = await pb.collection('links').getFullList({ filter: `user_id="${userId}"` });
await this.set(cacheKey, links, 300); // 5 Minuten Cache
}
return links;
}
}
```
@ -271,6 +284,7 @@ export class CacheManager {
### 1. Database Migration Strategy
#### PostgreSQL als Primary Database
```typescript
// Alternative zu PocketBase für bessere Skalierbarkeit
// src/lib/database/postgresql.ts
@ -289,6 +303,7 @@ export const db = drizzle(client);
```
#### Database Cluster Setup
- Master-Slave Replication
- Read Replicas für Analytics
- Automated Failover
@ -296,6 +311,7 @@ export const db = drizzle(client);
### 2. CDN Integration
#### Cloudflare Setup
```typescript
// src/app.html erweitern
// DNS-Level Protection gegen DDoS
@ -307,6 +323,7 @@ export const db = drizzle(client);
### 3. Microservices Architecture
#### Service Separation
```
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Frontend │ │ API Gateway │ │ Auth Service │
@ -328,6 +345,7 @@ export const db = drizzle(client);
### 1. CI/CD Pipeline Verbesserung
#### GitHub Actions Workflow
```yaml
# .github/workflows/deploy.yml
name: Deploy to Production
@ -354,7 +372,7 @@ jobs:
run: |
# Coolify Deployment
curl -X POST ${{ secrets.COOLIFY_WEBHOOK }}
- name: Health Check
run: |
# Warte auf erfolgreiche Deployment
@ -366,7 +384,7 @@ jobs:
sleep 10
done
exit 1
- name: Rollback on Failure
if: failure()
run: |
@ -377,6 +395,7 @@ jobs:
### 2. Monitoring & Alerting
#### Prometheus + Grafana Setup
```yaml
# monitoring/docker-compose.yml
services:
@ -385,26 +404,27 @@ services:
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
ports:
- "9090:9090"
- '9090:9090'
grafana:
image: grafana/grafana
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
ports:
- "3001:3000"
- '3001:3000'
volumes:
- grafana-storage:/var/lib/grafana
alertmanager:
image: prom/alertmanager
ports:
- "9093:9093"
- '9093:9093'
volumes:
- ./alertmanager.yml:/etc/alertmanager/alertmanager.yml
```
#### Custom Metrics
```typescript
// src/lib/metrics/prometheus.ts
import { register, collectDefaultMetrics, Counter, Histogram } from 'prom-client';
@ -412,27 +432,28 @@ import { register, collectDefaultMetrics, Counter, Histogram } from 'prom-client
collectDefaultMetrics();
export const httpRequestsTotal = new Counter({
name: 'http_requests_total',
help: 'Total number of HTTP requests',
labelNames: ['method', 'route', 'status_code']
name: 'http_requests_total',
help: 'Total number of HTTP requests',
labelNames: ['method', 'route', 'status_code'],
});
export const httpRequestDuration = new Histogram({
name: 'http_request_duration_seconds',
help: 'Duration of HTTP requests in seconds',
labelNames: ['method', 'route']
name: 'http_request_duration_seconds',
help: 'Duration of HTTP requests in seconds',
labelNames: ['method', 'route'],
});
export const linkRedirects = new Counter({
name: 'link_redirects_total',
help: 'Total number of link redirects',
labelNames: ['short_code', 'success']
name: 'link_redirects_total',
help: 'Total number of link redirects',
labelNames: ['short_code', 'success'],
});
```
### 3. Disaster Recovery Plan
#### Automated Recovery Scripts
```bash
#!/bin/bash
# scripts/disaster-recovery.sh
@ -458,10 +479,10 @@ activate_backup() {
restore_from_backup() {
# Download latest backup
aws s3 cp s3://$BACKUP_BUCKET/latest.tar.gz /tmp/restore.tar.gz
# Extract and restore
tar -xzf /tmp/restore.tar.gz -C /app/pb_data/
# Restart services
docker-compose restart
}
@ -470,7 +491,7 @@ restore_from_backup() {
if ! check_services; then
activate_backup
restore_from_backup
# Send alert
curl -X POST $SLACK_WEBHOOK -d '{"text":"Disaster recovery activated for ulo.ad"}'
fi
@ -481,38 +502,40 @@ fi
### 1. Chaos Engineering
#### Fault Injection Tests
```typescript
// tests/chaos/network-failures.test.ts
describe('Network Failure Scenarios', () => {
test('should handle PocketBase timeout gracefully', async () => {
// Simulate PocketBase timeout
const mockPb = mockPocketBaseTimeout();
const response = await app.request('/api/links', {
method: 'POST',
body: JSON.stringify({ url: 'https://example.com' })
});
// Should return cached response or graceful error
expect(response.status).toBeLessThan(500);
});
test('should handle PocketBase timeout gracefully', async () => {
// Simulate PocketBase timeout
const mockPb = mockPocketBaseTimeout();
test('should fallback to cached data when database is unavailable', async () => {
// Simulate database outage
mockDatabaseDown();
const response = await app.request('/my/links');
// Should serve from cache
expect(response.status).toBe(200);
expect(response.headers.get('x-served-from')).toBe('cache');
});
const response = await app.request('/api/links', {
method: 'POST',
body: JSON.stringify({ url: 'https://example.com' }),
});
// Should return cached response or graceful error
expect(response.status).toBeLessThan(500);
});
test('should fallback to cached data when database is unavailable', async () => {
// Simulate database outage
mockDatabaseDown();
const response = await app.request('/my/links');
// Should serve from cache
expect(response.status).toBe(200);
expect(response.headers.get('x-served-from')).toBe('cache');
});
});
```
### 2. Load Testing
#### Performance Benchmarks
```bash
# scripts/load-test.sh
#!/bin/bash
@ -532,6 +555,7 @@ k6 run performance-tests/user-journey.js
## Implementation Roadmap
### Phase 1 (Sofort - 1 Woche)
- [x] Analyse der aktuellen Architektur
- [ ] Verbesserte Error Handling implementieren
- [ ] Health Check Endpoints erweitern
@ -539,6 +563,7 @@ k6 run performance-tests/user-journey.js
- [ ] Backup-Scripts erstellen
### Phase 2 (2-4 Wochen)
- [ ] Redis für Rate Limiting & Caching
- [ ] Load Balancer Setup
- [ ] Service Separation (PocketBase)
@ -546,6 +571,7 @@ k6 run performance-tests/user-journey.js
- [ ] Disaster Recovery Scripts
### Phase 3 (1-3 Monate)
- [ ] PostgreSQL Migration evaluieren
- [ ] CDN Integration (Cloudflare)
- [ ] Microservices Architecture
@ -555,6 +581,7 @@ k6 run performance-tests/user-journey.js
## Kosten-Nutzen-Analyse
### Zusätzliche Infrastruktur-Kosten
- **Redis Server:** €5-10/Monat
- **Backup Server:** €5-15/Monat
- **Monitoring Tools:** €0-20/Monat (Uptime Robot Free, Grafana Cloud)
@ -563,6 +590,7 @@ k6 run performance-tests/user-journey.js
**Gesamtkosten:** €10-95/Monat zusätzlich
### Nutzen
- **99.9% Uptime** (vs. aktuell ~95%)
- **Automatische Recovery** bei Ausfällen
- **Bessere Performance** durch Caching
@ -572,16 +600,19 @@ k6 run performance-tests/user-journey.js
## Metriken & KPIs
### Verfügbarkeit
- **Target:** 99.9% Uptime
- **MTTR (Mean Time To Recovery):** < 5 Minuten
- **MTBF (Mean Time Between Failures):** > 30 Tage
### Performance
- **Response Time:** < 200ms (95th percentile)
- **Redirect Time:** < 50ms
- **Error Rate:** < 0.1%
### Monitoring
- **Alert Response Time:** < 2 Minuten
- **Backup Success Rate:** 100%
- **Health Check Success:** > 99.5%
@ -591,9 +622,10 @@ k6 run performance-tests/user-journey.js
Das aktuelle Single-Point-of-Failure Setup birgt erhebliche Risiken für die Verfügbarkeit von uLoad. Mit den vorgeschlagenen Maßnahmen kann die Infrastruktur deutlich robuster und ausfallsicherer gestaltet werden.
**Empfohlene Prioritäten:**
1. **Sofort:** Verbessertes Error Handling und Monitoring
2. **Kurzfristig:** Service Separation und Backup-Strategie
2. **Kurzfristig:** Service Separation und Backup-Strategie
3. **Mittelfristig:** Load Balancing und Caching
4. **Langfristig:** Microservices und Multi-Region
Der Plan bietet einen gestuften Ansatz, um die Ausfallsicherheit schrittweise zu erhöhen, ohne die laufende Entwicklung zu blockieren.
Der Plan bietet einen gestuften Ansatz, um die Ausfallsicherheit schrittweise zu erhöhen, ohne die laufende Entwicklung zu blockieren.

View file

@ -1,11 +1,13 @@
# PocketBase Development Setup - Implementierungsplan
## 🎯 Ziel
Vollständige Trennung von Development und Production Datenbanken, ähnlich wie bei Redis.
## 📋 Aktuelle Situation
### Probleme:
- ❌ Beide Umgebungen nutzen Production DB (`https://pb.ulo.ad`)
- ❌ Gefahr von Testdaten in Production
- ❌ Keine lokale Entwicklungsumgebung
@ -13,6 +15,7 @@ Vollständige Trennung von Development und Production Datenbanken, ähnlich wie
- ❌ Inkonsistente Environment-Variable-Nutzung
### Vorhandene Ressourcen:
- ✅ PocketBase Binary bereits in `backend/` vorhanden
- ✅ `npm run backend` Script existiert
- ✅ Lokale DB-Dateien in `backend/pb_data/`
@ -23,12 +26,14 @@ Vollständige Trennung von Development und Production Datenbanken, ähnlich wie
### Phase 1: Lokale PocketBase einrichten (15 Min)
1. **PocketBase starten**
```bash
cd backend
./pocketbase serve
```
- Läuft auf http://localhost:8090
- Admin UI: http://localhost:8090/_/
- Admin UI: http://localhost:8090/\_/
2. **Admin Account erstellen**
- Beim ersten Start wird Admin-Account angelegt
@ -43,11 +48,12 @@ Vollständige Trennung von Development und Production Datenbanken, ähnlich wie
### Phase 2: Environment Variables korrigieren (10 Min)
1. **.env.development anpassen**
```env
# PocketBase Configuration (Local Development)
PUBLIC_POCKETBASE_URL=http://localhost:8090
POCKETBASE_URL=http://localhost:8090
# PocketBase Admin (for local development)
POCKETBASE_ADMIN_EMAIL=admin@localhost
POCKETBASE_ADMIN_PASSWORD=localdevpassword123
@ -63,12 +69,13 @@ Vollständige Trennung von Development und Production Datenbanken, ähnlich wie
### Phase 3: Code-Anpassungen (20 Min)
1. **src/lib/pocketbase.ts**
```typescript
import { dev } from '$app/environment';
// Automatic environment detection
const POCKETBASE_URL = import.meta.env.PUBLIC_POCKETBASE_URL ||
(dev ? 'http://localhost:8090' : 'https://pb.ulo.ad');
const POCKETBASE_URL =
import.meta.env.PUBLIC_POCKETBASE_URL || (dev ? 'http://localhost:8090' : 'https://pb.ulo.ad');
```
2. **Hardcoded URLs entfernen**
@ -82,6 +89,7 @@ Vollständige Trennung von Development und Production Datenbanken, ähnlich wie
### Phase 4: Daten-Migration (30 Min)
1. **Test-Daten erstellen**
```bash
# Script für Sample-Daten
npm run seed:dev
@ -102,15 +110,16 @@ Vollständige Trennung von Development und Production Datenbanken, ähnlich wie
### Phase 5: Development Workflow (10 Min)
1. **Start-Scripts optimieren**
```json
{
"scripts": {
"dev": "npm run dev:frontend",
"dev:frontend": "vite dev",
"dev:backend": "cd backend && ./pocketbase serve",
"dev:all": "concurrently \"npm:dev:backend\" \"npm:dev:frontend\"",
"dev:setup": "npm run dev:backend:setup && npm run dev:seed"
}
"scripts": {
"dev": "npm run dev:frontend",
"dev:frontend": "vite dev",
"dev:backend": "cd backend && ./pocketbase serve",
"dev:all": "concurrently \"npm:dev:backend\" \"npm:dev:frontend\"",
"dev:setup": "npm run dev:backend:setup && npm run dev:seed"
}
}
```
@ -121,7 +130,7 @@ Vollständige Trennung von Development und Production Datenbanken, ähnlich wie
pocketbase:
image: ghcr.io/pocketbase/pocketbase:latest
ports:
- "8090:8090"
- '8090:8090'
volumes:
- ./backend/pb_data:/pb_data
```
@ -150,7 +159,7 @@ Vollständige Trennung von Development und Production Datenbanken, ähnlich wie
# 1. Backend starten
npm run dev:backend
# 2. In neuem Terminal: Frontend starten
# 2. In neuem Terminal: Frontend starten
npm run dev:frontend
# Oder alles zusammen:
@ -160,29 +169,32 @@ npm run dev:all
## ⚠️ Wichtige Überlegungen
### Daten-Synchronisation
- **NICHT** Production-Daten lokal spiegeln
- Nur Schema/Structure synchronisieren
- Test-Daten separat verwalten
### Secrets Management
- Lokale Admin-Credentials nur für Dev
- Keine Production-Secrets in .env.development
- Stripe Test-Keys für lokale Entwicklung
### Backup Strategy
- Lokale DB regelmäßig committen? (ohne sensible Daten)
- Schema-Änderungen als Migrations tracken
- Production Backups separat
## 📊 Vergleich: Vorher vs Nachher
| Aspekt | Vorher | Nachher |
|--------|---------|---------|
| Dev Database | Production (pb.ulo.ad) | Lokal (localhost:8090) |
| Test-Daten | In Production! | Isoliert lokal |
| Performance | Netzwerk-Latenz | Instant (lokal) |
| Sicherheit | Risiko für Prod-Daten | Vollständig getrennt |
| Offline-Arbeit | Nicht möglich | Voll funktionsfähig |
| Aspekt | Vorher | Nachher |
| -------------- | ---------------------- | ---------------------- |
| Dev Database | Production (pb.ulo.ad) | Lokal (localhost:8090) |
| Test-Daten | In Production! | Isoliert lokal |
| Performance | Netzwerk-Latenz | Instant (lokal) |
| Sicherheit | Risiko für Prod-Daten | Vollständig getrennt |
| Offline-Arbeit | Nicht möglich | Voll funktionsfähig |
## 🔄 Migration Checkliste
@ -210,4 +222,4 @@ npm run dev:all
2. Team informieren über Änderungen
3. Migration durchführen
4. Dokumentation für Team erstellen
5. CI/CD anpassen für neue Struktur
5. CI/CD anpassen für neue Struktur

View file

@ -4,7 +4,7 @@ Da der Schema-Import nicht funktioniert (veraltetes Format), musst du die Collec
## 🎯 Admin Login
1. Gehe zu: http://localhost:8090/_/
1. Gehe zu: http://localhost:8090/\_/
2. Login mit:
- Email: `till.schneider@memoro.ai`
- Password: `p0ck3t-RAJ`
@ -19,21 +19,22 @@ Da der Schema-Import nicht funktioniert (veraltetes Format), musst du die Collec
**Fields hinzufügen (+ New field):**
| Field Name | Type | Required | Options |
|------------|------|----------|---------|
| `short_code` | text | ✅ | Unique: ✅, Min: 3, Max: 50 |
| `original_url` | url | ✅ | - |
| `title` | text | ❌ | Max: 200 |
| `description` | text | ❌ | Max: 500 |
| `user_id` | relation | ❌ | Collection: users, Max select: 1, Cascade delete: ✅ |
| `is_active` | bool | ❌ | - |
| `password` | text | ❌ | - |
| `max_clicks` | number | ❌ | Min: 0 |
| `expires_at` | date | ❌ | - |
| `click_count` | number | ❌ | - |
| `tags` | json | ❌ | - |
| Field Name | Type | Required | Options |
| -------------- | -------- | -------- | ---------------------------------------------------- |
| `short_code` | text | ✅ | Unique: ✅, Min: 3, Max: 50 |
| `original_url` | url | ✅ | - |
| `title` | text | ❌ | Max: 200 |
| `description` | text | ❌ | Max: 500 |
| `user_id` | relation | ❌ | Collection: users, Max select: 1, Cascade delete: ✅ |
| `is_active` | bool | ❌ | - |
| `password` | text | ❌ | - |
| `max_clicks` | number | ❌ | Min: 0 |
| `expires_at` | date | ❌ | - |
| `click_count` | number | ❌ | - |
| `tags` | json | ❌ | - |
**API Rules:**
- List/View rule: `` (leer = public)
- Create rule: `@request.auth.id != ""`
- Update rule: `@request.auth.id = user_id`
@ -47,20 +48,21 @@ Da der Schema-Import nicht funktioniert (veraltetes Format), musst du die Collec
**Fields:**
| Field Name | Type | Required | Options |
|------------|------|----------|---------|
| `link_id` | relation | ✅ | Collection: links, Max select: 1, Cascade delete: ✅ |
| `ip_hash` | text | ❌ | - |
| `user_agent` | text | ❌ | - |
| `referer` | text | ❌ | - |
| `browser` | text | ❌ | - |
| `device_type` | text | ❌ | - |
| `os` | text | ❌ | - |
| `country` | text | ❌ | - |
| `city` | text | ❌ | - |
| `clicked_at` | date | ❌ | - |
| Field Name | Type | Required | Options |
| ------------- | -------- | -------- | ---------------------------------------------------- |
| `link_id` | relation | ✅ | Collection: links, Max select: 1, Cascade delete: ✅ |
| `ip_hash` | text | ❌ | - |
| `user_agent` | text | ❌ | - |
| `referer` | text | ❌ | - |
| `browser` | text | ❌ | - |
| `device_type` | text | ❌ | - |
| `os` | text | ❌ | - |
| `country` | text | ❌ | - |
| `city` | text | ❌ | - |
| `clicked_at` | date | ❌ | - |
**API Rules:**
- List/View rule: `` (leer = public)
- Create rule: `` (leer = public)
- Update rule: `null` (keine Updates erlaubt)
@ -74,20 +76,21 @@ Da der Schema-Import nicht funktioniert (veraltetes Format), musst du die Collec
**Fields:**
| Field Name | Type | Required | Options |
|------------|------|----------|---------|
| `name` | text | ✅ | - |
| `owner` | relation | ✅ | Collection: users, Max select: 1 |
| `members` | relation | ❌ | Collection: users, Multiple: ✅ |
| `isActive` | bool | ❌ | - |
| `planType` | select | ❌ | Values: free, team, enterprise |
| `settings` | json | ❌ | - |
| Field Name | Type | Required | Options |
| ---------- | -------- | -------- | -------------------------------- |
| `name` | text | ✅ | - |
| `owner` | relation | ✅ | Collection: users, Max select: 1 |
| `members` | relation | ❌ | Collection: users, Multiple: ✅ |
| `isActive` | bool | ❌ | - |
| `planType` | select | ❌ | Values: free, team, enterprise |
| `settings` | json | ❌ | - |
## ✅ Nach dem Erstellen
1. **Speichern** nicht vergessen (Save button oben rechts)
2. **Test-Daten laden:**
```bash
node scripts/seed-local-db.js
```
@ -100,6 +103,7 @@ Da der Schema-Import nicht funktioniert (veraltetes Format), musst du die Collec
## 🎉 Fertig!
Deine lokale PocketBase ist jetzt bereit mit:
- Admin Account ✅
- Collections ✅
- Test-Daten (nach Seed-Script)
@ -107,6 +111,7 @@ Deine lokale PocketBase ist jetzt bereit mit:
## 📝 Test-URLs
Nach dem Seed-Script:
- http://localhost:5173/test1 - Normaler Link
- http://localhost:5173/test2 - Link mit Click-Limit
- http://localhost:5173/protected - Passwort: `secret123`
@ -114,9 +119,11 @@ Nach dem Seed-Script:
## 🔍 Troubleshooting
**"Collection not found" Fehler?**
- Stelle sicher, dass alle Collections erstellt und gespeichert wurden
- Name muss exakt sein (case-sensitive)
**"Invalid relation" Fehler?**
- Erst Links Collection erstellen, dann Clicks
- Users Collection existiert bereits (Standard Auth)
- Users Collection existiert bereits (Standard Auth)

View file

@ -3,13 +3,15 @@
## ✅ Was wurde implementiert
### 1. Environment Separation
- **Development**: `http://localhost:8090` (lokal)
- **Production**: `https://pb.ulo.ad` (Coolify)
- Automatische Umgebungserkennung basierend auf `dev` Flag
### 2. Code-Änderungen
- ✅ `.env.development` - Lokale PocketBase URL
- ✅ `.env.production` - Production PocketBase URL
- ✅ `.env.production` - Production PocketBase URL
- ✅ `src/lib/pocketbase.ts` - Dynamische URL-Auswahl
- ✅ `src/routes/p/[username]/+page.server.ts` - Environment-basierte URL
- ✅ `src/lib/scripts/update-links-collection.js` - Flexible URL
@ -17,6 +19,7 @@
- ✅ `src/hooks.server.ts` - CSP Headers für beide Umgebungen
### 3. Sicherheit
- Keine hardcoded Production URLs mehr
- Vollständige Trennung von Dev/Prod Daten
- Lokale Entwicklung ohne Internetverbindung möglich
@ -36,19 +39,21 @@ cd backend
### 2. Admin Account erstellen
1. Öffne http://localhost:8090/_/
1. Öffne http://localhost:8090/\_/
2. Erstelle Admin Account (nur beim ersten Mal)
3. Merke dir die Credentials
### 3. Schema importieren
**Option A: Manuell über UI**
1. Login als Admin
2. Settings → Import/Export
3. Load from JSON → `backend/pb_schema.json`
4. Review → Confirm
**Option B: Automatisch (wenn Collections existieren)**
```bash
# Schema ist bereits in backend/pb_migrations/
cd backend
@ -63,6 +68,7 @@ node scripts/seed-local-db.js
```
Erstellt:
- 2 Test-User (test@localhost, demo@localhost)
- 4 Test-Links (normal, protected, expired, limited)
- Sample Click-Daten
@ -80,21 +86,24 @@ npm run dev:all
## 📝 Test-Credentials
### Users
```
Email: test@localhost
Password: test123456
Email: demo@localhost
Email: demo@localhost
Password: demo123456
```
### Links
- `http://localhost:5173/test1` - Normaler Link
- `http://localhost:5173/test2` - Mit Click-Limit (100)
- `http://localhost:5173/protected` - Password: `secret123`
- `http://localhost:5173/expired` - Abgelaufener Link
### Admin
```
URL: http://localhost:8090/_/
Email: [deine Admin Email]
@ -104,6 +113,7 @@ Password: [dein Admin Password]
## 🔍 Verification
### 1. Environment Check
```bash
# In Browser Console sollte stehen:
🔧 PocketBase URL: http://localhost:8090
@ -112,12 +122,14 @@ Password: [dein Admin Password]
```
### 2. API Health
```bash
curl http://localhost:5173/health
# sollte zeigen: "pocketbase": "running"
```
### 3. Feature Tests
- [ ] User Registration
- [ ] Login/Logout
- [ ] Link erstellen
@ -166,15 +178,15 @@ Falls Probleme: Admin UI → Settings → API Rules
## 📊 Development vs Production
| Aspekt | Development | Production |
|--------|------------|------------|
| Aspekt | Development | Production |
| -------------- | --------------------- | ----------------- |
| PocketBase URL | http://localhost:8090 | https://pb.ulo.ad |
| Datenbank | Lokal (SQLite) | Cloud (Coolify) |
| Auth | Test-Accounts | Echte User |
| Stripe | Test-Keys | Live-Keys |
| Redis | localhost:6379 | Coolify Redis |
| SSL | Nein (HTTP) | Ja (HTTPS) |
| CSP | Relaxed | Strict |
| Datenbank | Lokal (SQLite) | Cloud (Coolify) |
| Auth | Test-Accounts | Echte User |
| Stripe | Test-Keys | Live-Keys |
| Redis | localhost:6379 | Coolify Redis |
| SSL | Nein (HTTP) | Ja (HTTPS) |
| CSP | Relaxed | Strict |
## 🔄 Daten-Synchronisation
@ -212,6 +224,7 @@ cd backend && ./pocketbase serve
## 🚢 Deployment
Beim Deployment nach Production:
1. Environment wird automatisch erkannt
2. Production URLs werden verwendet
3. Keine Code-Änderungen nötig!
@ -230,4 +243,4 @@ npm run preview
- [PocketBase Docs](https://pocketbase.io/docs/)
- [Admin UI Guide](https://pocketbase.io/docs/admin-ui/)
- [API Rules](https://pocketbase.io/docs/api-rules-and-filters/)
- [Migrations](https://pocketbase.io/docs/migrations/)
- [Migrations](https://pocketbase.io/docs/migrations/)

View file

@ -57,7 +57,6 @@ npm run dev:all
```bash
/app/pocketbase superuser create till.schneider@memoro.ai p0ck3t-RA1N
```
### Über Web-Interface

View file

@ -1,11 +1,13 @@
# Profile Background Color Feature
## Übersicht
Nutzer können jetzt die Hintergrundfarbe ihrer Profilseite anpassen.
## So funktioniert's
### 1. Einstellung ändern
1. Gehe zu **Settings** (`/settings`)
2. Scrolle zum Abschnitt **Profile Appearance**
3. Wähle eine Farbe aus:
@ -13,9 +15,11 @@ Nutzer können jetzt die Hintergrundfarbe ihrer Profilseite anpassen.
- **Presets**: Wähle aus vordefinierten Farben (Light Gray, Light Blue, Light Green, etc.)
### 2. Vorschau
Die gewählte Farbe wird sofort auf deiner Profilseite (`/p/[username]`) angezeigt.
## Verfügbare Preset-Farben
- Light Gray (Standard): `#f9fafb`
- Light Blue: `#dbeafe`
- Light Green: `#dcfce7`
@ -29,15 +33,18 @@ Die gewählte Farbe wird sofort auf deiner Profilseite (`/p/[username]`) angezei
## Technische Details
### Datenbank
- Neues Feld: `profileBackground` (String) in der `users` Collection
- Standard: `#f9fafb` (Light Gray)
### Implementation
- Settings-Seite: Color Picker + Preset-Auswahl
- Profile-Seite: Dynamisches CSS mit inline `style` Attribut
- Für dunkle Farben wird ein leichter Gradient angewendet
### Dateien geändert
- `/src/routes/(app)/settings/+page.server.ts` - Server-Action für Speicherung
- `/src/routes/(app)/settings/+page.svelte` - UI für Farbauswahl
- `/src/routes/p/[username]/+page.server.ts` - Lade Hintergrundfarbe
@ -45,11 +52,9 @@ Die gewählte Farbe wird sofort auf deiner Profilseite (`/p/[username]`) angezei
- `/src/lib/pocketbase.ts` - User Type aktualisiert
## Beispiel
```html
<div
class="min-h-screen transition-colors duration-300"
style="background: #dbeafe"
>
<!-- Profile content -->
<div class="min-h-screen transition-colors duration-300" style="background: #dbeafe">
<!-- Profile content -->
</div>
```
```

View file

@ -1,396 +1,411 @@
Redis Cache für uLoad - Was bringt es
wirklich?
Redis Cache für uLoad - Was bringt es
wirklich?
Das Hauptproblem aktuell
Das Hauptproblem aktuell
Jedes Mal wenn jemand einen deiner
Short-Links klickt, muss deine App:
1. PocketBase fragen "welche URL gehört zu
diesem Short-Code?"
2. Warten bis PocketBase in der SQLite
Datei sucht
3. Antwort zurückgeben und weiterleiten
Jedes Mal wenn jemand einen deiner
Short-Links klickt, muss deine App:
Das dauert 50-100ms pro Klick. Bei vielen
Klicks wird PocketBase zum Flaschenhals.
1. PocketBase fragen "welche URL gehört zu
diesem Short-Code?"
2. Warten bis PocketBase in der SQLite
Datei sucht
3. Antwort zurückgeben und weiterleiten
Was Redis ändern würde
Das dauert 50-100ms pro Klick. Bei vielen
Klicks wird PocketBase zum Flaschenhals.
Redis ist wie ein ultra-schneller
Zwischenspeicher. Statt jedes Mal
PocketBase zu fragen, schaust du erst in
Redis nach. Das ist 20-50x schneller!
Was Redis ändern würde
Die 3 größten Verbesserungen
Redis ist wie ein ultra-schneller
Zwischenspeicher. Statt jedes Mal
PocketBase zu fragen, schaust du erst in
Redis nach. Das ist 20-50x schneller!
1. Link-Redirects (90% deines Traffics)
Die 3 größten Verbesserungen
Problem: Jeder Klick = PocketBase Query =
langsam
Lösung: Beliebte Links werden in Redis
gespeichert
1. Link-Redirects (90% deines Traffics)
Beispiel:
- Dein Link "ulo.ad/meeting" wird 100x am
Problem: Jeder Klick = PocketBase Query =
langsam
Lösung: Beliebte Links werden in Redis
gespeichert
Beispiel:
- Dein Link "ulo.ad/meeting" wird 100x am
Tag geklickt
- Ohne Redis: 100x PocketBase Query (100x
- Ohne Redis: 100x PocketBase Query (100x
80ms = 8 Sekunden Serverzeit)
- Mit Redis: 1x PocketBase + 99x Redis (1x
- Mit Redis: 1x PocketBase + 99x Redis (1x
80ms + 99x 2ms = 0.28 Sekunden)
Ergebnis: 30x schneller, 95% weniger
PocketBase Last
Ergebnis: 30x schneller, 95% weniger
PocketBase Last
2. Dashboard Performance
2. Dashboard Performance
Problem: Dein Dashboard lädt alle deine
Links aus PocketBase
Lösung: Links werden für 5 Minuten gecacht
Problem: Dein Dashboard lädt alle deine
Links aus PocketBase
Lösung: Links werden für 5 Minuten gecacht
Beispiel:
- Du hast 50 Links, checkst Dashboard 20x
Beispiel:
- Du hast 50 Links, checkst Dashboard 20x
am Tag
- Ohne Redis: 20x komplette Liste laden =
- Ohne Redis: 20x komplette Liste laden =
langsam
- Mit Redis: 1x laden, 19x aus Cache =
- Mit Redis: 1x laden, 19x aus Cache =
blitzschnell
3. Analytics & Statistiken
3. Analytics & Statistiken
Problem: Click-Zahlen berechnen ist langsam
(viele Datenbankabfragen)
Lösung: Statistiken werden live berechnet
und gecacht
Problem: Click-Zahlen berechnen ist langsam
(viele Datenbankabfragen)
Lösung: Statistiken werden live berechnet
und gecacht
Beispiel:
- "Zeige Clicks der letzten 7 Tage" braucht
normalerweise komplizierte Queries
- Mit Redis: Zahlen werden kontinuierlich
Beispiel:
- "Zeige Clicks der letzten 7 Tage" braucht
normalerweise komplizierte Queries
- Mit Redis: Zahlen werden kontinuierlich
aktualisiert, sofort verfügbar
Rate Limiting Verbesserung
Rate Limiting Verbesserung
Aktuell: Dein Rate Limiter vergisst alles
bei Server-Restart
Mit Redis: Rate Limits bleiben bestehen,
auch über Restarts hinweg
Aktuell: Dein Rate Limiter vergisst alles
bei Server-Restart
Mit Redis: Rate Limits bleiben bestehen,
auch über Restarts hinweg
Das bedeutet: Angreifer können nicht mehr
durch Server-Restart deine Limits umgehen.
Das bedeutet: Angreifer können nicht mehr
durch Server-Restart deine Limits umgehen.
Konkrete Zahlen für dich
Konkrete Zahlen für dich
Traffic-Verteilung schätzen:
Traffic-Verteilung schätzen:
- 90% Redirects (Leute klicken deine Links)
- 8% Dashboard/API (Du managst deine Links)
- 90% Redirects (Leute klicken deine Links)
- 8% Dashboard/API (Du managst deine Links)
- 2% Sonstiges (Login, Settings, etc.)
- 2% Sonstiges (Login, Settings, etc.)
Performance ohne Redis:
Performance ohne Redis:
- Redirect: 80ms durchschnittlich
- Dashboard laden: 300ms
- Bei 1000 Klicks/Tag: PocketBase ist
- Redirect: 80ms durchschnittlich
- Dashboard laden: 300ms
- Bei 1000 Klicks/Tag: PocketBase ist
gestresst
Performance mit Redis:
Performance mit Redis:
- Redirect: 5ms (95% aus Cache)
- Dashboard laden: 20ms (meist aus Cache)
- Bei 10.000 Klicks/Tag: PocketBase
- Redirect: 5ms (95% aus Cache)
- Dashboard laden: 20ms (meist aus Cache)
- Bei 10.000 Klicks/Tag: PocketBase
entspannt
Wann merkst du den Unterschied?
Wann merkst du den Unterschied?
Sofort spürbar:
Sofort spürbar:
- Dashboard lädt viel schneller (besonders
- Dashboard lädt viel schneller (besonders
mit vielen Links)
- Redirects sind blitzschnell
- Analytics laden sofort statt zu "denken"
- Redirects sind blitzschnell
- Analytics laden sofort statt zu "denken"
Bei mehr Traffic:
Bei mehr Traffic:
- Server bleibt entspannt auch bei
- Server bleibt entspannt auch bei
Viral-Links
- Keine Timeouts mehr bei hoher Last
- PocketBase überlastet nicht
- Keine Timeouts mehr bei hoher Last
- PocketBase überlastet nicht
Skalierung:
Skalierung:
- Ohne Redis: ~500 Redirects/Minute machbar
- Mit Redis: ~5000+ Redirects/Minute
- Ohne Redis: ~500 Redirects/Minute machbar
- Mit Redis: ~5000+ Redirects/Minute
machbar
Kosten vs. Nutzen
Kosten vs. Nutzen
Option 1: Kostenlos starten
Option 1: Kostenlos starten
- Upstash Redis (10k Commands/Tag gratis)
- Perfekt zum Testen
- Upgrade wenn nötig
- Upstash Redis (10k Commands/Tag gratis)
- Perfekt zum Testen
- Upgrade wenn nötig
Option 2: Minimale Kosten
Option 2: Minimale Kosten
- €5-15/Monat für ordentlichen Redis
- Dafür 10x bessere Performance
- €5-15/Monat für ordentlichen Redis
- Dafür 10x bessere Performance
Was du sparst:
Was du sparst:
- Weniger Server-Stress = längere
- Weniger Server-Stress = längere
Hardware-Lebensdauer
- Bessere User Experience = mehr zufriedene
Nutzer
- Skalierung möglich ohne neuen Server
- Bessere User Experience = mehr zufriedene
Nutzer
- Skalierung möglich ohne neuen Server
Die Realität für uLoad
Die Realität für uLoad
Jetzt: Bei 100 Klicks gleichzeitig würde
dein Server wahrscheinlich strugglen
Mit Redis: Bei 1000 Klicks gleichzeitig
läuft alles smooth
Jetzt: Bei 100 Klicks gleichzeitig würde
dein Server wahrscheinlich strugglen
Mit Redis: Bei 1000 Klicks gleichzeitig
läuft alles smooth
Jetzt: Dashboard mit 100 Links braucht eine
Sekunde zum Laden
Mit Redis: Dashboard lädt in 0.1 Sekunden
Jetzt: Dashboard mit 100 Links braucht eine
Sekunde zum Laden
Mit Redis: Dashboard lädt in 0.1 Sekunden
Jetzt: Click-Statistiken berechnen dauert
ewig
Mit Redis: Statistiken sind immer aktuell
und sofort da
Jetzt: Click-Statistiken berechnen dauert
ewig
Mit Redis: Statistiken sind immer aktuell
und sofort da
Meine ehrliche Einschätzung
Meine ehrliche Einschätzung
Für uLoad ist Redis ein No-Brainer:
- Minimaler Aufwand (2-3 Tage Setup)
- Maximaler Nutzen (10x Performance)
- Kosten fast null (kann gratis starten)
Für uLoad ist Redis ein No-Brainer:
Der größte Gewinn ist bei Redirects - das
ist dein Kernfeature und wird damit
ultra-schnell. Plus du bist ready für
Viral-Links die plötzlich 1000x geklickt
werden.
- Minimaler Aufwand (2-3 Tage Setup)
- Maximaler Nutzen (10x Performance)
- Kosten fast null (kann gratis starten)
Bottom Line: Redis macht aus deinem "hobby
project" eine professionelle, skalierbare
Anwendung für praktisch null Zusatzkosten.
Das ist vermutlich das beste ROI was du
kriegen kannst! 🎯
Der größte Gewinn ist bei Redirects - das
ist dein Kernfeature und wird damit
ultra-schnell. Plus du bist ready für
Viral-Links die plötzlich 1000x geklickt
werden.
> super danke. wir können ja reddis auch
einfach auf unserem hetzter vps mit coolify
hochfahren oder? was brucht es an
reccourcen? was mus man beachten? sollte es
lieber auf einem adnere server liegen?
sollte es im gleichen project in coolify
liegen?
Bottom Line: Redis macht aus deinem "hobby
project" eine professionelle, skalierbare
Anwendung für praktisch null Zusatzkosten.
Das ist vermutlich das beste ROI was du
kriegen kannst! 🎯
⏺ Redis auf Hetzner VPS mit Coolify -
Perfekte Idee!
> super danke. wir können ja reddis auch
> einfach auf unserem hetzter vps mit coolify
> hochfahren oder? was brucht es an
> reccourcen? was mus man beachten? sollte es
> lieber auf einem adnere server liegen?
> sollte es im gleichen project in coolify
> liegen?
Ja, absolut machbar und sinnvoll!
⏺ Redis auf Hetzner VPS mit Coolify -
Perfekte Idee!
Dein aktueller Hetzner VPS kann Redis
problemlos mit hosten. Coolify macht das
super einfach.
Ja, absolut machbar und sinnvoll!
Resource-Bedarf von Redis
Dein aktueller Hetzner VPS kann Redis
problemlos mit hosten. Coolify macht das
super einfach.
RAM (das Wichtigste):
Resource-Bedarf von Redis
- Redis lädt alles in den RAM - das macht
RAM (das Wichtigste):
- Redis lädt alles in den RAM - das macht
es so schnell
- Für uLoad geschätzt: 50-200MB RAM
- Link Cache: ~1MB pro 10.000 Links
- Session Cache: ~10MB für 1000 aktive
User
- Analytics: ~20-50MB je nach History
- Rate Limiting: ~5-10MB
- Für uLoad geschätzt: 50-200MB RAM
- Link Cache: ~1MB pro 10.000 Links
- Session Cache: ~10MB für 1000 aktive
User
- Analytics: ~20-50MB je nach History
- Rate Limiting: ~5-10MB
CPU:
CPU:
- Minimal! Redis ist extrem CPU-effizient
- Für uLoad: <1% CPU Auslastung
- Nur bei komplexen Operationen mehr
- Minimal! Redis ist extrem CPU-effizient
- Für uLoad: <1% CPU Auslastung
- Nur bei komplexen Operationen mehr
Disk:
Disk:
- Persistence: 100-500MB für Dumps
- Logs: 10-50MB
- Total: <1GB
- Persistence: 100-500MB für Dumps
- Logs: 10-50MB
- Total: <1GB
Dein CX21 VPS Kapazität:
Dein CX21 VPS Kapazität:
- RAM: 8GB total, aktuell ~2GB verwendet →
- RAM: 8GB total, aktuell ~2GB verwendet →
6GB frei!
- CPU: 2 vCPUs, aktuell <20% Mehr als
- CPU: 2 vCPUs, aktuell <20% Mehr als
genug
- Disk: 40GB, Redis braucht <1GB Kein
- Disk: 40GB, Redis braucht <1GB Kein
Problem
Fazit: Dein Server kann Redis locker
verkraften! 🎯
Fazit: Dein Server kann Redis locker
verkraften! 🎯
Coolify Setup - zwei Optionen
Coolify Setup - zwei Optionen
Option 1: Gleiches Projekt (Empfohlen für
Start)
Option 1: Gleiches Projekt (Empfohlen für
Start)
# Einfach Redis Service zu deinem
bestehenden docker-compose hinzufügen
services:
app:
# Deine bestehende App
# Einfach Redis Service zu deinem
bestehenden docker-compose hinzufügen
services:
app: # Deine bestehende App
redis:
image: redis:7-alpine
command: redis-server --appendonly yes
--maxmemory 512mb --maxmemory-policy
allkeys-lru
volumes:
- redis-data:/data
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 30s
timeout: 10s
retries: 3
volumes:
redis-data:
--maxmemory 512mb --maxmemory-policy
allkeys-lru
volumes: - redis-data:/data
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 30s
timeout: 10s
retries: 3
Option 2: Separates Projekt in Coolify
volumes:
redis-data:
- Vorteil: Unabhängiges Deployment von
Option 2: Separates Projekt in Coolify
- Vorteil: Unabhängiges Deployment von
deiner App
- Nachteil: Etwas komplizierter zu
- Nachteil: Etwas komplizierter zu
verwalten
Ich empfehle Option 1 - Redis ist so
stabil, das macht keinen Unterschied.
Ich empfehle Option 1 - Redis ist so
stabil, das macht keinen Unterschied.
Coolify Konfiguration
Coolify Konfiguration
Redis Environment Variables:
Redis Environment Variables:
# In Coolify für Redis Service setzen:
REDIS_PASSWORD=dein-sicheres-passwort-hier
REDIS_MAXMEMORY=512mb
REDIS_SAVE_INTERVAL=900 # Auto-save alle
15 Min
# In Coolify für Redis Service setzen:
Deine App erweitern:
REDIS_PASSWORD=dein-sicheres-passwort-hier
REDIS_MAXMEMORY=512mb
REDIS_SAVE_INTERVAL=900 # Auto-save alle
15 Min
# Environment Variable für deine App
hinzufügen:
REDIS_URL=redis://:dein-passwort@redis:6379
Deine App erweitern:
Networking in Coolify:
# Environment Variable für deine App
- Redis und App sind automatisch im
hinzufügen:
REDIS_URL=redis://:dein-passwort@redis:6379
Networking in Coolify:
- Redis und App sind automatisch im
gleichen Docker Network
- Deine App kann Redis über redis:6379
- Deine App kann Redis über redis:6379
erreichen
- Kein Port nach außen öffnen nötig
- Kein Port nach außen öffnen nötig
(Sicherheit!)
Wichtige Redis Konfiguration
Wichtige Redis Konfiguration
Memory Management:
Memory Management:
# Redis Config (über Command Parameter):
--maxmemory 512mb # Begrenze RAM Nutzung
--maxmemory-policy allkeys-lru # Alte Keys
löschen wenn voll
# Redis Config (über Command Parameter):
Persistence:
--maxmemory 512mb # Begrenze RAM Nutzung
--maxmemory-policy allkeys-lru # Alte Keys
löschen wenn voll
# Zwei Optionen kombinieren:
--appendonly yes # AOF für Durability
--save 900 1 # RDB Snapshot alle 15
Min
Persistence:
Security:
# Zwei Optionen kombinieren:
# Passwort setzen:
--requirepass dein-starkes-passwort
--appendonly yes # AOF für Durability
--save 900 1 # RDB Snapshot alle 15
Min
# Gefährliche Commands deaktivieren:
--rename-command FLUSHDB ""
--rename-command FLUSHALL ""
Security:
Monitoring in Coolify
# Passwort setzen:
Redis Logs überwachen:
--requirepass dein-starkes-passwort
- Coolify zeigt Redis Logs in Echtzeit
- Achte auf Memory Warnings
- Connection Errors beobachten
# Gefährliche Commands deaktivieren:
Wichtige Redis Metriken:
--rename-command FLUSHDB ""
--rename-command FLUSHALL ""
# Commands zum Monitoring (über redis-cli):
INFO memory # RAM Nutzung
INFO stats # Operations/sec
INFO clients # Verbindungen
MONITOR # Live Commands (nur
zum Debugging!)
Monitoring in Coolify
Backup Strategy
Redis Logs überwachen:
Automatische Redis Backups:
- Coolify zeigt Redis Logs in Echtzeit
- Achte auf Memory Warnings
- Connection Errors beobachten
# Script in deinem Container:
#!/bin/bash
# redis-backup.sh
DATE=$(date +%Y%m%d_%H%M%S)
Wichtige Redis Metriken:
# Commands zum Monitoring (über redis-cli):
INFO memory # RAM Nutzung
INFO stats # Operations/sec
INFO clients # Verbindungen
MONITOR # Live Commands (nur
zum Debugging!)
Backup Strategy
Automatische Redis Backups:
# Script in deinem Container:
#!/bin/bash
# redis-backup.sh
DATE=$(date +%Y%m%d_%H%M%S)
redis-cli --rdb
/backups/redis_backup_$DATE.rdb
# In Coolify als Cron Job alle 6 Stunden
# In Coolify als Cron Job alle 6 Stunden
Oder einfacher:
Oder einfacher:
# Volume Mount für Persistence reicht:
volumes:
- redis-data:/data # Redis saved
automatisch hier
# Volume Mount für Persistence reicht:
Separater Server - wann sinnvoll?
volumes: - redis-data:/data # Redis saved
automatisch hier
Bleib bei einem Server wenn:
Separater Server - wann sinnvoll?
- <10.000 Requests/Tag (definitiv dein
Bleib bei einem Server wenn:
- <10.000 Requests/Tag (definitiv dein
Fall)
- <1000 gleichzeitige User
- Development/Early Stage (definitiv dein
- <1000 gleichzeitige User
- Development/Early Stage (definitiv dein
Fall)
Separater Redis Server nur bei:
Separater Redis Server nur bei:
- >100.000 Requests/Tag
- Multi-Region Setup
- Dedicated Redis Cluster mit Failover
- > 100.000 Requests/Tag
- Multi-Region Setup
- Dedicated Redis Cluster mit Failover
Für uLoad: Ein Server reicht völlig!
Für uLoad: Ein Server reicht völlig!
Coolify Deployment Steps
Coolify Deployment Steps
1. Docker Compose erweitern:
1. Docker Compose erweitern:
# Zu deiner bestehenden docker-compose.yml
hinzufügen:
services:
# ... deine bestehenden Services
# Zu deiner bestehenden docker-compose.yml
hinzufügen:
services: # ... deine bestehenden Services
redis:
image: redis:7-alpine
command: >
redis-server
--appendonly yes
--maxmemory 512mb
redis-server
--appendonly yes
--maxmemory 512mb
--maxmemory-policy allkeys-lru
--requirepass ${REDIS_PASSWORD}
volumes:
@ -400,92 +415,99 @@ Redis Cache für uLoad - Was bringt es
- app-network
healthcheck:
test: ["CMD", "redis-cli", "-a",
"${REDIS_PASSWORD}", "ping"]
interval: 30s
timeout: 10s
retries: 3
volumes:
redis-data:
"${REDIS_PASSWORD}", "ping"]
interval: 30s
timeout: 10s
retries: 3
networks:
app-network:
driver: bridge
volumes:
redis-data:
2. Environment Variables in Coolify:
networks:
app-network:
driver: bridge
# Für Redis:
REDIS_PASSWORD=SuperSecurePassword123!
2. Environment Variables in Coolify:
# Für deine App:
REDIS_URL=redis://:SuperSecurePassword123!@
redis:6379
# Für Redis:
3. Deploy & Test:
REDIS_PASSWORD=SuperSecurePassword123!
- Coolify deployed automatisch
- Teste Connection aus deiner App
- Check Redis Logs für Errors
# Für deine App:
Was beachten?
REDIS_URL=redis://:SuperSecurePassword123!@
redis:6379
Security:
3. Deploy & Test:
- Passwort setzen (Redis hat standardmäßig
- Coolify deployed automatisch
- Teste Connection aus deiner App
- Check Redis Logs für Errors
Was beachten?
Security:
- Passwort setzen (Redis hat standardmäßig
keins!)
- Port nicht öffentlich machen (nur intern)
- Command Renaming für gefährliche Commands
- Port nicht öffentlich machen (nur intern)
- Command Renaming für gefährliche Commands
Performance:
Performance:
- Memory Limit setzen (512MB reicht für
- Memory Limit setzen (512MB reicht für
Start)
- LRU Policy aktivieren (alte Keys werden
- LRU Policy aktivieren (alte Keys werden
gelöscht)
- Connection Pooling in deiner App
- Connection Pooling in deiner App
Monitoring:
Monitoring:
- Redis Logs in Coolify beobachten
- Memory Usage im Auge behalten
- Hit Rate messen (wie oft Cache getroffen
- Redis Logs in Coolify beobachten
- Memory Usage im Auge behalten
- Hit Rate messen (wie oft Cache getroffen
wird)
Troubleshooting
Troubleshooting
Häufige Probleme:
Häufige Probleme:
1. Connection refused: Redis Service nicht
gestartet
2. Auth failed: Passwort falsch oder nicht
gesetzt
3. Memory full: Maxmemory erreicht, LRU
Policy prüfen
1. Connection refused: Redis Service nicht
gestartet
2. Auth failed: Passwort falsch oder nicht
gesetzt
3. Memory full: Maxmemory erreicht, LRU
Policy prüfen
Debug Commands:
Debug Commands:
# In Coolify Terminal (Redis Container):
redis-cli -a dein-passwort info
redis-cli -a dein-passwort ping
redis-cli -a dein-passwort client list
# In Coolify Terminal (Redis Container):
Meine Empfehlung
redis-cli -a dein-passwort info
redis-cli -a dein-passwort ping
redis-cli -a dein-passwort client list
Setup für uLoad:
1. Gleicher Server (Hetzner VPS) ✅
2. Gleiches Coolify Projekt ✅
3. 512MB Memory Limit ✅
4. Passwort-geschützt ✅
5. Persistence aktiviert ✅
Meine Empfehlung
Vorteile:
- Einfachste Lösung
- Keine Extra-Kosten
- Latenz minimal (gleicher Server)
- Easy zu verwalten
Setup für uLoad:
Nachteile:
- Keine (für deine Use Case)
1. Gleicher Server (Hetzner VPS) ✅
2. Gleiches Coolify Projekt ✅
3. 512MB Memory Limit ✅
4. Passwort-geschützt ✅
5. Persistence aktiviert ✅
Redis auf deinem bestehenden Setup ist der
perfekte nächste Schritt! 🚀
Vorteile:
- Einfachste Lösung
- Keine Extra-Kosten
- Latenz minimal (gleicher Server)
- Easy zu verwalten
Nachteile:
- Keine (für deine Use Case)
Redis auf deinem bestehenden Setup ist der
perfekte nächste Schritt! 🚀

View file

@ -1,180 +1,185 @@
Redis Cache für uLoad - Was bringt es
wirklich?
Redis Cache für uLoad - Was bringt es
wirklich?
Das Hauptproblem aktuell
Das Hauptproblem aktuell
Jedes Mal wenn jemand einen deiner
Short-Links klickt, muss deine App:
1. PocketBase fragen "welche URL gehört zu
diesem Short-Code?"
2. Warten bis PocketBase in der SQLite
Datei sucht
3. Antwort zurückgeben und weiterleiten
Jedes Mal wenn jemand einen deiner
Short-Links klickt, muss deine App:
Das dauert 50-100ms pro Klick. Bei vielen
Klicks wird PocketBase zum Flaschenhals.
1. PocketBase fragen "welche URL gehört zu
diesem Short-Code?"
2. Warten bis PocketBase in der SQLite
Datei sucht
3. Antwort zurückgeben und weiterleiten
Was Redis ändern würde
Das dauert 50-100ms pro Klick. Bei vielen
Klicks wird PocketBase zum Flaschenhals.
Redis ist wie ein ultra-schneller
Zwischenspeicher. Statt jedes Mal
PocketBase zu fragen, schaust du erst in
Redis nach. Das ist 20-50x schneller!
Was Redis ändern würde
Die 3 größten Verbesserungen
Redis ist wie ein ultra-schneller
Zwischenspeicher. Statt jedes Mal
PocketBase zu fragen, schaust du erst in
Redis nach. Das ist 20-50x schneller!
1. Link-Redirects (90% deines Traffics)
Die 3 größten Verbesserungen
Problem: Jeder Klick = PocketBase Query =
langsam
Lösung: Beliebte Links werden in Redis
gespeichert
1. Link-Redirects (90% deines Traffics)
Beispiel:
- Dein Link "ulo.ad/meeting" wird 100x am
Problem: Jeder Klick = PocketBase Query =
langsam
Lösung: Beliebte Links werden in Redis
gespeichert
Beispiel:
- Dein Link "ulo.ad/meeting" wird 100x am
Tag geklickt
- Ohne Redis: 100x PocketBase Query (100x
- Ohne Redis: 100x PocketBase Query (100x
80ms = 8 Sekunden Serverzeit)
- Mit Redis: 1x PocketBase + 99x Redis (1x
- Mit Redis: 1x PocketBase + 99x Redis (1x
80ms + 99x 2ms = 0.28 Sekunden)
Ergebnis: 30x schneller, 95% weniger
PocketBase Last
Ergebnis: 30x schneller, 95% weniger
PocketBase Last
2. Dashboard Performance
2. Dashboard Performance
Problem: Dein Dashboard lädt alle deine
Links aus PocketBase
Lösung: Links werden für 5 Minuten gecacht
Problem: Dein Dashboard lädt alle deine
Links aus PocketBase
Lösung: Links werden für 5 Minuten gecacht
Beispiel:
- Du hast 50 Links, checkst Dashboard 20x
Beispiel:
- Du hast 50 Links, checkst Dashboard 20x
am Tag
- Ohne Redis: 20x komplette Liste laden =
- Ohne Redis: 20x komplette Liste laden =
langsam
- Mit Redis: 1x laden, 19x aus Cache =
- Mit Redis: 1x laden, 19x aus Cache =
blitzschnell
3. Analytics & Statistiken
3. Analytics & Statistiken
Problem: Click-Zahlen berechnen ist langsam
(viele Datenbankabfragen)
Lösung: Statistiken werden live berechnet
und gecacht
Problem: Click-Zahlen berechnen ist langsam
(viele Datenbankabfragen)
Lösung: Statistiken werden live berechnet
und gecacht
Beispiel:
- "Zeige Clicks der letzten 7 Tage" braucht
normalerweise komplizierte Queries
- Mit Redis: Zahlen werden kontinuierlich
Beispiel:
- "Zeige Clicks der letzten 7 Tage" braucht
normalerweise komplizierte Queries
- Mit Redis: Zahlen werden kontinuierlich
aktualisiert, sofort verfügbar
Rate Limiting Verbesserung
Rate Limiting Verbesserung
Aktuell: Dein Rate Limiter vergisst alles
bei Server-Restart
Mit Redis: Rate Limits bleiben bestehen,
auch über Restarts hinweg
Aktuell: Dein Rate Limiter vergisst alles
bei Server-Restart
Mit Redis: Rate Limits bleiben bestehen,
auch über Restarts hinweg
Das bedeutet: Angreifer können nicht mehr
durch Server-Restart deine Limits umgehen.
Das bedeutet: Angreifer können nicht mehr
durch Server-Restart deine Limits umgehen.
Konkrete Zahlen für dich
Konkrete Zahlen für dich
Traffic-Verteilung schätzen:
Traffic-Verteilung schätzen:
- 90% Redirects (Leute klicken deine Links)
- 8% Dashboard/API (Du managst deine Links)
- 90% Redirects (Leute klicken deine Links)
- 8% Dashboard/API (Du managst deine Links)
- 2% Sonstiges (Login, Settings, etc.)
- 2% Sonstiges (Login, Settings, etc.)
Performance ohne Redis:
Performance ohne Redis:
- Redirect: 80ms durchschnittlich
- Dashboard laden: 300ms
- Bei 1000 Klicks/Tag: PocketBase ist
- Redirect: 80ms durchschnittlich
- Dashboard laden: 300ms
- Bei 1000 Klicks/Tag: PocketBase ist
gestresst
Performance mit Redis:
Performance mit Redis:
- Redirect: 5ms (95% aus Cache)
- Dashboard laden: 20ms (meist aus Cache)
- Bei 10.000 Klicks/Tag: PocketBase
- Redirect: 5ms (95% aus Cache)
- Dashboard laden: 20ms (meist aus Cache)
- Bei 10.000 Klicks/Tag: PocketBase
entspannt
Wann merkst du den Unterschied?
Wann merkst du den Unterschied?
Sofort spürbar:
Sofort spürbar:
- Dashboard lädt viel schneller (besonders
- Dashboard lädt viel schneller (besonders
mit vielen Links)
- Redirects sind blitzschnell
- Analytics laden sofort statt zu "denken"
- Redirects sind blitzschnell
- Analytics laden sofort statt zu "denken"
Bei mehr Traffic:
Bei mehr Traffic:
- Server bleibt entspannt auch bei
- Server bleibt entspannt auch bei
Viral-Links
- Keine Timeouts mehr bei hoher Last
- PocketBase überlastet nicht
- Keine Timeouts mehr bei hoher Last
- PocketBase überlastet nicht
Skalierung:
Skalierung:
- Ohne Redis: ~500 Redirects/Minute machbar
- Mit Redis: ~5000+ Redirects/Minute
- Ohne Redis: ~500 Redirects/Minute machbar
- Mit Redis: ~5000+ Redirects/Minute
machbar
Kosten vs. Nutzen
Kosten vs. Nutzen
Option 1: Kostenlos starten
Option 1: Kostenlos starten
- Upstash Redis (10k Commands/Tag gratis)
- Perfekt zum Testen
- Upgrade wenn nötig
- Upstash Redis (10k Commands/Tag gratis)
- Perfekt zum Testen
- Upgrade wenn nötig
Option 2: Minimale Kosten
Option 2: Minimale Kosten
- €5-15/Monat für ordentlichen Redis
- Dafür 10x bessere Performance
- €5-15/Monat für ordentlichen Redis
- Dafür 10x bessere Performance
Was du sparst:
Was du sparst:
- Weniger Server-Stress = längere
- Weniger Server-Stress = längere
Hardware-Lebensdauer
- Bessere User Experience = mehr zufriedene
Nutzer
- Skalierung möglich ohne neuen Server
- Bessere User Experience = mehr zufriedene
Nutzer
- Skalierung möglich ohne neuen Server
Die Realität für uLoad
Die Realität für uLoad
Jetzt: Bei 100 Klicks gleichzeitig würde
dein Server wahrscheinlich strugglen
Mit Redis: Bei 1000 Klicks gleichzeitig
läuft alles smooth
Jetzt: Bei 100 Klicks gleichzeitig würde
dein Server wahrscheinlich strugglen
Mit Redis: Bei 1000 Klicks gleichzeitig
läuft alles smooth
Jetzt: Dashboard mit 100 Links braucht eine
Sekunde zum Laden
Mit Redis: Dashboard lädt in 0.1 Sekunden
Jetzt: Dashboard mit 100 Links braucht eine
Sekunde zum Laden
Mit Redis: Dashboard lädt in 0.1 Sekunden
Jetzt: Click-Statistiken berechnen dauert
ewig
Mit Redis: Statistiken sind immer aktuell
und sofort da
Jetzt: Click-Statistiken berechnen dauert
ewig
Mit Redis: Statistiken sind immer aktuell
und sofort da
Meine ehrliche Einschätzung
Meine ehrliche Einschätzung
Für uLoad ist Redis ein No-Brainer:
- Minimaler Aufwand (2-3 Tage Setup)
- Maximaler Nutzen (10x Performance)
- Kosten fast null (kann gratis starten)
Für uLoad ist Redis ein No-Brainer:
Der größte Gewinn ist bei Redirects - das
ist dein Kernfeature und wird damit
ultra-schnell. Plus du bist ready für
Viral-Links die plötzlich 1000x geklickt
werden.
- Minimaler Aufwand (2-3 Tage Setup)
- Maximaler Nutzen (10x Performance)
- Kosten fast null (kann gratis starten)
Bottom Line: Redis macht aus deinem "hobby
project" eine professionelle, skalierbare
Anwendung für praktisch null Zusatzkosten.
Das ist vermutlich das beste ROI was du
kriegen kannst! 🎯
Der größte Gewinn ist bei Redirects - das
ist dein Kernfeature und wird damit
ultra-schnell. Plus du bist ready für
Viral-Links die plötzlich 1000x geklickt
werden.
Bottom Line: Redis macht aus deinem "hobby
project" eine professionelle, skalierbare
Anwendung für praktisch null Zusatzkosten.
Das ist vermutlich das beste ROI was du
kriegen kannst! 🎯

View file

@ -9,11 +9,13 @@ Nach einigen Herausforderungen haben wir Redis erfolgreich auf Coolify zum Laufe
### Redis Service in Coolify
#### 1. Redis als Database Service hinzufügen
- **Type:** Redis Database
- **Image:** redis:7.2
- **Name:** redis-database-[generated-id]
#### 2. General Settings
```
Username: default
Password: [Sicheres Passwort generieren]
@ -23,6 +25,7 @@ Custom Docker Options: --protected-mode no --bind 0.0.0.0
**Wichtig:** Die Custom Docker Options sind KRITISCH! Ohne diese wird Redis Verbindungen ablehnen.
#### 3. Network Configuration
```
Ports Mappings: 6379:6379
Redis URL (internal): [wird automatisch generiert]
@ -33,6 +36,7 @@ Redis URL (internal): [wird automatisch generiert]
### Hauptanwendung Environment Variables
#### Funktionierende Konfiguration:
```bash
REDIS_HOST=ycsoowwsc84s0s8gc8oooosk # Der Container-Name (NICHT der Service-Name!)
REDIS_PORT=6379
@ -47,6 +51,7 @@ REDIS_PASSWORD=[Das gleiche Passwort wie im Redis Service]
**Problem:** Der Coolify Service Name funktioniert nicht für die interne Kommunikation.
**Lösung:** Verwende den tatsächlichen Container-Namen:
- ❌ FALSCH: `redis-database-ycsoowwsc84s0s8gc8oooosk`
- ❌ FALSCH: `redis-database-ycsoowwsc84s0s8gc8oooosk.coolify`
- ✅ RICHTIG: `ycsoowwsc84s0s8gc8oooosk`
@ -58,6 +63,7 @@ Der Container-Name findest du in den Redis Logs oder beim Container Start.
**Problem:** "Connection is closed" Fehler trotz korrekter Credentials.
**Lösung:** Redis Protected Mode deaktivieren:
```bash
--protected-mode no --bind 0.0.0.0
```
@ -68,7 +74,8 @@ Diese Optionen MÜSSEN in "Custom Docker Options" gesetzt werden!
**Problem:** REDIS_HOST wurde mit kompletter URL statt nur Hostname gesetzt.
**Lösung:**
**Lösung:**
- ❌ FALSCH: `REDIS_HOST=redis://default:password@host:6379`
- ✅ RICHTIG: `REDIS_HOST=ycsoowwsc84s0s8gc8oooosk`
@ -79,6 +86,7 @@ REDIS_HOST darf NUR der Hostname sein, keine URL!
**Problem:** Falscher Port (5432 statt 6379) wurde gemappt.
**Lösung:**
- Port 6379 ist Redis
- Port 5432 ist PostgreSQL
- Immer 6379:6379 für Redis verwenden
@ -104,6 +112,7 @@ REDIS_HOST darf NUR der Hostname sein, keine URL!
### Schritt 3: Hauptapp konfigurieren
Environment Variables in deiner Hauptapp:
```bash
REDIS_HOST=[Container-Name aus Schritt 2]
REDIS_PORT=6379
@ -114,36 +123,40 @@ REDIS_PASSWORD=[Passwort aus Redis Service]
### Schritt 4: Testen
Erstelle einen Test-Endpoint in deiner App:
```typescript
// src/routes/test-redis/+server.ts
import { json } from '@sveltejs/kit';
import Redis from 'ioredis';
export async function GET() {
const redis = new Redis({
host: process.env.REDIS_HOST,
port: parseInt(process.env.REDIS_PORT || '6379'),
username: process.env.REDIS_USERNAME,
password: process.env.REDIS_PASSWORD
});
const redis = new Redis({
host: process.env.REDIS_HOST,
port: parseInt(process.env.REDIS_PORT || '6379'),
username: process.env.REDIS_USERNAME,
password: process.env.REDIS_PASSWORD,
});
try {
await redis.ping();
await redis.set('test', 'Hello Redis!');
const value = await redis.get('test');
redis.disconnect();
return json({
success: true,
value,
host: process.env.REDIS_HOST
});
} catch (error) {
return json({
success: false,
error: error.message
}, { status: 500 });
}
try {
await redis.ping();
await redis.set('test', 'Hello Redis!');
const value = await redis.get('test');
redis.disconnect();
return json({
success: true,
value,
host: process.env.REDIS_HOST,
});
} catch (error) {
return json(
{
success: false,
error: error.message,
},
{ status: 500 }
);
}
}
```
@ -152,16 +165,19 @@ export async function GET() {
Nach erfolgreicher Redis-Integration:
### Link Redirects
- **Vorher:** 50-100ms (PocketBase Query)
- **Nachher:** 2-5ms (Redis Cache)
- **Verbesserung:** 20-50x schneller!
### Dashboard Loading
- **Vorher:** 200-400ms
- **Nachher:** 10-20ms
- **Verbesserung:** 10-20x schneller!
### Analytics
- **Vorher:** 500-1500ms
- **Nachher:** 20-50ms
- **Verbesserung:** 10-30x schneller!
@ -169,16 +185,19 @@ Nach erfolgreicher Redis-Integration:
## 🐛 Troubleshooting
### "Connection is closed" Error
1. Check Custom Docker Options: `--protected-mode no --bind 0.0.0.0`
2. Verify Container Name (nicht Service Name!)
3. Check Password ist korrekt
### "ECONNREFUSED" Error
1. Redis Service läuft nicht
2. Falscher Host/Port
3. Network Isolation Problem
### "NOAUTH Authentication required"
1. Password nicht gesetzt in Environment Variables
2. Falsches Password
3. Username fehlt (sollte "default" sein)
@ -186,6 +205,7 @@ Nach erfolgreicher Redis-Integration:
### Debug Commands
Im Redis Container (via Coolify Terminal):
```bash
# Test Redis läuft
redis-cli ping
@ -201,23 +221,27 @@ redis-cli -a [password] CONFIG GET protected-mode
## 💡 Best Practices
### 1. Resource Limits
```bash
--maxmemory 512mb
--maxmemory-policy allkeys-lru
```
### 2. Persistence
```bash
--appendonly yes
--save 900 1 # Save every 15 min if at least 1 key changed
```
### 3. Security
- Niemals Redis Port öffentlich exponieren
- Starkes Passwort verwenden
- Protected Mode nur intern deaktivieren
### 4. Monitoring
- Memory Usage im Auge behalten
- Hit Rate tracken
- Slow Queries monitoren
@ -225,6 +249,7 @@ redis-cli -a [password] CONFIG GET protected-mode
## 📊 Resource-Bedarf
Für uLoad auf Hetzner CX21:
- **RAM:** 50-200MB (von 8GB verfügbar)
- **CPU:** <1% (von 2 vCPUs)
- **Disk:** <1GB (von 40GB)
@ -251,4 +276,4 @@ Mit dieser Konfiguration läuft Redis stabil und performant auf dem gleichen Het
---
*Dokumentiert nach erfolgreicher Redis-Integration für uLoad auf Coolify, August 2025*
_Dokumentiert nach erfolgreicher Redis-Integration für uLoad auf Coolify, August 2025_

View file

@ -7,7 +7,7 @@ Redis is used in uload to dramatically improve link redirect performance by cach
## Prerequisites
- macOS with Homebrew installed
- Node.js 18+
- Node.js 18+
- Running uload development environment
## Installation
@ -51,6 +51,7 @@ REDIS_HOST=localhost REDIS_PORT=6379 npm run dev
```
Check the console output for:
```
✅ Redis: Connected successfully
```
@ -64,14 +65,15 @@ curl http://localhost:5173/api/redis-status | jq
```
Expected response:
```json
{
"connected": true,
"host": "localhost",
"enabled": true,
"available": true,
"cachedLinks": 0,
"error": null
"connected": true,
"host": "localhost",
"enabled": true,
"available": true,
"cachedLinks": 0,
"error": null
}
```
@ -80,16 +82,20 @@ Expected response:
### Cache Flow
1. **First Visit (Cache MISS)**
```
User → Short Link → Check Redis (miss) → Query Database → Redirect → Cache Result
```
- Takes ~100-200ms
- Stores result in Redis for future requests
2. **Subsequent Visits (Cache HIT)**
```
User → Short Link → Check Redis (hit) → Redirect
```
- Takes ~10-20ms
- Skips database query entirely
@ -139,6 +145,7 @@ redis-cli del "redirect:abc123"
### 4. Debug Cache Hits/Misses
Enable verbose logging in your browser console:
1. Visit a short link
2. Check browser console for:
- "Cache MISS - fetching from PocketBase"
@ -170,13 +177,14 @@ node test-redis-cache.mjs
Edit `src/lib/server/linkCache.ts`:
```typescript
const CACHE_TTL = 86400; // 24 hours for popular links
const SHORT_TTL = 300; // 5 minutes for normal links
const CACHE_TTL = 86400; // 24 hours for popular links
const SHORT_TTL = 300; // 5 minutes for normal links
```
### Fallback Behavior
The app automatically handles Redis unavailability:
- If Redis is down, the app continues working without cache
- No errors shown to users
- Graceful degradation to database-only mode
@ -202,11 +210,13 @@ tail -f /opt/homebrew/var/log/redis.log
### Connection Refused
1. Verify Redis is running:
```bash
redis-cli ping
```
2. Check port availability:
```bash
lsof -i :6379
```
@ -220,11 +230,13 @@ tail -f /opt/homebrew/var/log/redis.log
### Cache Not Working
1. Check Redis connection in app:
```bash
curl http://localhost:5173/api/redis-status
```
2. Verify Redis has memory available:
```bash
redis-cli info memory
```
@ -234,11 +246,13 @@ tail -f /opt/homebrew/var/log/redis.log
### Performance Issues
1. Monitor Redis latency:
```bash
redis-cli --latency
```
2. Check Redis memory usage:
```bash
redis-cli info memory | grep used_memory_human
```
@ -300,4 +314,4 @@ redis-cli slowlog get # Show slow queries
- [Production Redis Setup](./redis-production-setup.md)
- [Cache Strategy Guide](./cache-strategy.md)
- [Performance Optimization](./performance.md)
- [Performance Optimization](./performance.md)

View file

@ -59,10 +59,11 @@ redis-cli ping
Visit: http://localhost:5173/api/redis-status
Should see:
```json
{
"connected": true,
"available": true
"connected": true,
"available": true
}
```
@ -105,17 +106,20 @@ brew services stop redis
## 🆘 Help
**Redis won't start?**
```bash
brew services restart redis
```
**Connection refused?**
```bash
# Check if running
ps aux | grep redis
```
**Cache not working?**
- Check console for Redis connection message
- Verify environment variables are loaded
- Try explicit env vars: `REDIS_HOST=localhost npm run dev`
- Try explicit env vars: `REDIS_HOST=localhost npm run dev`

View file

@ -52,16 +52,19 @@ Die DSGVO und moderne Browser-Technologien schaffen zusätzliche Hürden:
Branch.io nutzt eine Kombination aus verschiedenen Matching-Technologien:
**Deterministic Matching** (100% Genauigkeit):
- Verwendet eindeutige Identifikatoren wie IDFA (iOS) oder GAID (Android)
- Click-IDs in URL-Parametern
- Login-basiertes Matching über User-Accounts
**Probabilistic Matching** (70-90% Genauigkeit):
- Kombiniert IP-Adresse, User-Agent, Zeitstempel und Geräteinformationen
- Erstellt einen "Fingerprint" für wahrscheinliche Zuordnung
- Nutzt ein Zeitfenster von typischerweise 24-72 Stunden
**Deferred Deep Linking**:
- Speichert die Ziel-Information vor der App-Installation
- Nach Installation fragt die App den Branch-Server nach pending Deep Links
- Nutzer wird automatisch zum richtigen Inhalt in der App geleitet
@ -71,16 +74,19 @@ Branch.io nutzt eine Kombination aus verschiedenen Matching-Technologien:
Diese Plattformen setzen auf:
**Server-to-Server Integration**:
- Direkte API-Kommunikation zwischen App-Backend und Attribution-Plattform
- Umgeht Browser-Restriktionen komplett
- Höhere Datenqualität durch serverseitige Validierung
**SKAdNetwork Integration** (iOS):
- Apples Privacy-freundliche Attribution-API
- Aggregierte Daten ohne Nutzer-Identifikation
- 24-48 Stunden Verzögerung in Reporting
**Multi-Touch Attribution Models**:
- First-Touch: Kredite gehen an ersten Touchpoint
- Last-Touch: Kredite gehen an letzten Touchpoint vor Conversion
- Linear: Gleichmäßige Verteilung auf alle Touchpoints
@ -101,18 +107,21 @@ Diese Plattformen setzen auf:
**Konzept**: Jeder Link-Ersteller erhält einen eindeutigen Referral-Code, der in der URL mitgegeben wird. Dieser Code wird durch die gesamte Journey hindurch weitergereicht.
**Funktionsweise**:
- Link-Ersteller teilt: `https://ulo.ad/download?ref=USER123`
- Code wird auf Download-Seite im LocalStorage gespeichert
- App liest Code beim ersten Start aus und meldet ihn zurück
- Keine personenbezogenen Daten des Endnutzers werden getrackt
**Vorteile**:
- 100% DSGVO-konform, da nur der Referrer getrackt wird
- 100% genaue Attribution möglich
- Keine Cookies oder Fingerprinting erforderlich
- Funktioniert plattformübergreifend
**Nachteile**:
- Erfordert aktive Weitergabe des Codes durch die App
- Kann bei App-Store-Downloads verloren gehen
- Keine Multi-Touch-Attribution möglich
@ -122,6 +131,7 @@ Diese Plattformen setzen auf:
**Konzept**: Temporäre Sessions mit kurzer Lebensdauer und anonymisierten Daten.
**Funktionsweise**:
- Bei Klick wird Session-ID generiert (30 Minuten Gültigkeit)
- Nur aggregierte Daten ohne Personenbezug werden gespeichert
- Täglicher Salt für Hashing verhindert langfristige Nachverfolgung
@ -145,11 +155,13 @@ Diese Plattformen setzen auf:
- Langzeit-Analyse
**Vorteile**:
- Flexibles Privacy-Modell
- Bessere Insights bei vorhandenem Consent
- Basis-Funktionalität ohne Einwilligung
**Nachteile**:
- Komplexere Implementierung
- Reduzierte Genauigkeit ohne Consent
- Consent-Banner kann Conversion beeinträchtigen
@ -159,18 +171,21 @@ Diese Plattformen setzen auf:
**Konzept**: Nutzer gibt E-Mail-Adresse ein und erhält personalisierten Download-Link.
**Funktionsweise**:
- Nutzer gibt E-Mail auf Landing Page ein
- System sendet Magic Link mit verschlüsseltem Token
- Token enthält Attribution-Informationen
- Nach Download kann Nutzer direkt zugeordnet werden
**Vorteile**:
- Explizite Einwilligung durch E-Mail-Eingabe
- 100% genaue Attribution
- Möglichkeit für Follow-up-Kommunikation
- DSGVO-konform mit Double-Opt-In
**Nachteile**:
- Zusätzlicher Schritt im Conversion-Funnel
- Nicht alle Nutzer wollen E-Mail angeben
- Verzögerung durch E-Mail-Versand
@ -180,18 +195,21 @@ Diese Plattformen setzen auf:
**Konzept**: Gamification-Ansatz mit manueller Code-Eingabe.
**Funktionsweise**:
- Jeder Link enthält prominenten Promo-Code
- Nutzer wird incentiviert, Code in App einzugeben
- Code aktiviert Vorteile (Rabatte, Features)
- Attribution erfolgt bei Code-Einlösung
**Vorteile**:
- Kein technisches Tracking erforderlich
- Nutzer hat volle Kontrolle
- Zusätzlicher Conversion-Anreiz
- 100% DSGVO-konform
**Nachteile**:
- Erfordert manuelle Aktion des Nutzers
- Nicht alle werden Code eingeben
- Zusätzliche Incentive-Kosten
@ -203,12 +221,14 @@ Diese Plattformen setzen auf:
**Ziel**: Schnelle Basis-Implementation ohne komplexe Infrastruktur
**Umsetzung**:
- Integration von Referral-Codes in bestehende Link-Struktur
- Weitergabe der Codes über URL-Parameter
- Einfaches Dashboard für Link-Ersteller
- Server-to-Server Webhook für App-Events
**Metriken**:
- Anzahl Klicks pro Referral-Code
- Conversions (Download, Registrierung, Kauf)
- Conversion-Rate pro Link-Ersteller
@ -218,12 +238,14 @@ Diese Plattformen setzen auf:
**Ziel**: Verbessertes Tracking mit Privacy-First-Ansatz
**Umsetzung**:
- Session-basiertes Tracking mit täglichem Salt
- Aggregierte Analytics ohne Personenbezug
- A/B-Testing verschiedener Attribution-Methoden
- Implementierung eines Consent-Banners für erweiterte Features
**Neue Features**:
- Conversion-Funnels
- Zeitbasierte Analysen
- Geografische Verteilung
@ -234,12 +256,14 @@ Diese Plattformen setzen auf:
**Ziel**: Vollständige Customer Journey Analyse
**Umsetzung**:
- Kombination mehrerer Tracking-Methoden
- Machine Learning für Probabilistic Matching
- Integration mit externen Analytics-Tools
- Advanced Reporting und Insights
**Erweiterte Funktionen**:
- Attribution-Modelle (First-Touch, Last-Touch, Linear)
- Kohorten-Analyse
- Lifetime-Value-Berechnung
@ -250,6 +274,7 @@ Diese Plattformen setzen auf:
**Ziel**: Professionelle Attribution-Plattform
**Umsetzung**:
- Fraud-Detection-System
- Real-Time Dashboards
- API für Drittanbieter-Integration
@ -260,16 +285,19 @@ Diese Plattformen setzen auf:
### Datenmodell
**tracking_sessions**:
- Temporäre Sessions mit kurzer Lebensdauer
- Anonymisierte Nutzer-Identifikatoren
- Verknüpfung zu Links und Referrern
**conversion_events**:
- Event-Stream aller Conversions
- Attribution zu Sessions und Referral-Codes
- Zeitstempel und Event-Typen
**attribution_analytics**:
- Aggregierte Metriken
- Keine personenbezogenen Daten
- Optimiert für schnelle Abfragen
@ -277,11 +305,13 @@ Diese Plattformen setzen auf:
### API-Design
**Tracking-Endpoints**:
- POST /api/track/click - Initiale Klick-Erfassung
- POST /api/track/event - Event-Tracking (Download, Register, Purchase)
- GET /api/analytics/attribution - Attribution-Reports
**Webhook-System**:
- Empfang von App-Events
- Validierung und Zuordnung
- Asynchrone Verarbeitung
@ -289,12 +319,14 @@ Diese Plattformen setzen auf:
### Sicherheit und Datenschutz
**Datenschutz-Maßnahmen**:
- Verschlüsselung aller Daten in Transit und at Rest
- Automatische Datenlöschung nach definierten Zeiträumen
- Pseudonymisierung von Nutzer-Daten
- Audit-Logs für alle Datenzugriffe
**Compliance**:
- DSGVO-konforme Datenverarbeitung
- Privacy-by-Design-Prinzipien
- Transparente Datenschutzerklärung
@ -328,28 +360,35 @@ Diese Plattformen setzen auf:
### Technische Risiken
**Datenverlust bei Platform-Wechsel**:
- Mitigation: Redundante Tracking-Methoden, Fallback-Mechanismen
**Ungenauigkeit bei Probabilistic Matching**:
- Mitigation: Konservative Zuordnung, manuelle Validierung bei hochwertigen Conversions
**Skalierungsprobleme**:
- Mitigation: Event-Streaming-Architektur, horizontale Skalierung
### Rechtliche Risiken
**DSGVO-Verstöße**:
- Mitigation: Privacy-by-Design, regelmäßige Audits, Datenschutzbeauftragter
**Internationale Datenschutzgesetze**:
- Mitigation: Geo-basierte Compliance-Rules, lokale Datenspeicherung
### Geschäftliche Risiken
**Nutzer-Akzeptanz**:
- Mitigation: Transparente Kommunikation, Opt-in-Incentives
**Kosten-Nutzen-Verhältnis**:
- Mitigation: Phasenweise Einführung, kontinuierliche ROI-Messung
## Zusammenfassung und Ausblick
@ -369,4 +408,4 @@ Langfristig positioniert sich uload damit nicht nur als Link-Shortener, sondern
5. **Pilotphase**: Test mit ausgewählten Nutzern
6. **Rollout**: Schrittweise Einführung für alle Nutzer
Der Erfolg des Systems wird maßgeblich davon abhängen, wie gut es gelingt, die Balance zwischen Tracking-Genauigkeit und Datenschutz zu finden. Mit dem vorgeschlagenen mehrstufigen Ansatz ist uload bestens positioniert, um diese Herausforderung zu meistern und einen neuen Standard für datenschutzfreundliches Attribution-Tracking zu setzen.
Der Erfolg des Systems wird maßgeblich davon abhängen, wie gut es gelingt, die Balance zwischen Tracking-Genauigkeit und Datenschutz zu finden. Mit dem vorgeschlagenen mehrstufigen Ansatz ist uload bestens positioniert, um diese Herausforderung zu meistern und einen neuen Standard für datenschutzfreundliches Attribution-Tracking zu setzen.

View file

@ -80,16 +80,19 @@ Nach erfolgreicher App-Installation:
#### Datenfluss und technische Architektur
**Frontend-Komponenten:**
```
Landing Page → JavaScript-Tracker → Storage APIs → Redirect Handler
```
**Backend-Komponenten:**
```
Link-Generator → Code-Database → Attribution-Engine → Analytics-Aggregator
```
**Datenstrukturen:**
- Referral-Codes: Key-Value-Store mit TTL
- Click-Events: Time-Series-Database
- Attributions: Relationale Datenbank
@ -200,6 +203,7 @@ Nach erfolgreicher Validierung:
**1. Maximale Conversion-Rate**
Das größte Plus ist die Reibungslosigkeit. Studien zeigen, dass jeder zusätzliche Schritt im Conversion-Funnel zu einem Verlust von 10-30% der Nutzer führt. Das automatische System eliminiert diese Hürde komplett. Nutzer müssen:
- Nichts merken
- Nichts eingeben
- Keine zusätzliche Entscheidung treffen
@ -207,6 +211,7 @@ Das größte Plus ist die Reibungslosigkeit. Studien zeigen, dass jeder zusätzl
**2. Universelle Anwendbarkeit**
Das System funktioniert überall:
- Bei impulsiven Käufen
- In Situationen mit wenig Zeit
- Für weniger technikaffine Nutzer
@ -237,6 +242,7 @@ Das System funktioniert überall:
**1. Fehlende Nutzer-Awareness**
Nutzer wissen nicht, dass sie getrackt werden:
- Keine bewusste Verbindung zum Referrer
- Keine Wertschätzung für den "Deal"
- Verpasste Marketing-Opportunity
@ -275,6 +281,7 @@ Nutzer wissen nicht, dass sie getrackt werden:
**1. Perfekte Attribution (nahezu 100%)**
Wenn ein Code eingegeben wird, ist die Zuordnung eindeutig:
- Keine technischen Unsicherheiten
- Keine False Positives
- Klare Kausalität
@ -283,6 +290,7 @@ Wenn ein Code eingegeben wird, ist die Zuordnung eindeutig:
**2. Marketing-Multiplikator**
Codes sind selbst Marketing-Instrumente:
- Virales Potenzial durch Weitergabe
- Gesprächsthema in Communities
- Social-Media-Content
@ -291,6 +299,7 @@ Codes sind selbst Marketing-Instrumente:
**3. Nutzer-Engagement und -Bindung**
Die aktive Eingabe schafft Commitment:
- Bewusste Entscheidung für Produkt
- Positive Assoziation durch Belohnung
- Höhere Wertschätzung
@ -322,6 +331,7 @@ Die aktive Eingabe schafft Commitment:
**1. Conversion-Friction**
Der größte Nachteil ist die zusätzliche Hürde:
- 20-40% niedrigere Conversion-Rate typisch
- Nutzer vergessen Codes
- Nutzer sind zu faul für Eingabe
@ -362,16 +372,19 @@ Der größte Nachteil ist die zusätzliche Hürde:
**Unconscious Processing**
Das automatische System nutzt das Prinzip des "Unconscious Processing":
- Nutzer treffen Entscheidungen ohne bewusste Überlegung
- Reduzierte kognitive Last führt zu schnelleren Entscheidungen
- "System 1 Thinking" nach Kahneman
**Vorteile:**
- Höhere Spontankäufe
- Weniger Entscheidungsmüdigkeit
- Natürlicher Flow
**Nachteile:**
- Keine emotionale Verbindung
- Geringere Erinnerung
- Weniger Wertschätzung
@ -400,6 +413,7 @@ Die manuelle Code-Eingabe aktiviert mehrere psychologische Mechanismen:
**1. Niedrigpreisige Impulskäufe**
Bei Apps oder Services unter 10€ ist jede Hürde fatal:
- Casual Games
- Utility-Apps
- Content-Subscriptions
@ -410,6 +424,7 @@ Bei Apps oder Services unter 10€ ist jede Hürde fatal:
**2. Zeitkritische Aktionen**
Wenn Nutzer schnell handeln müssen:
- Flash Sales
- Live-Events
- Breaking News Apps
@ -420,6 +435,7 @@ Wenn Nutzer schnell handeln müssen:
**3. Technisch weniger versierte Zielgruppen**
Für Nutzer, die mit Code-Eingabe überfordert wären:
- Senioren-Apps
- Kinder-Produkte (Eltern als Käufer)
- Mainstream-Utility-Apps
@ -427,6 +443,7 @@ Für Nutzer, die mit Code-Eingabe überfordert wären:
**4. Hochvolumige Virale Kampagnen**
Wenn Masse über Qualität geht:
- Social-Media-Challenges
- Influencer-Kampagnen mit Millionen-Reichweite
- Viral-Marketing-Stunts
@ -436,6 +453,7 @@ Wenn Masse über Qualität geht:
**1. Premium-Produkte und Services**
Bei höheren Preispunkten ist die Extra-Motivation wertvoll:
- B2B-Software
- Premium-Subscriptions (>20€/Monat)
- Online-Kurse
@ -446,6 +464,7 @@ Bei höheren Preispunkten ist die Extra-Motivation wertvoll:
**2. Community-getriebene Produkte**
Wenn Codes Teil der Community-Kultur werden:
- Gaming-Communities
- Fitness-Apps mit Gruppen
- Lern-Plattformen
@ -456,6 +475,7 @@ Wenn Codes Teil der Community-Kultur werden:
**3. Saisonale und Event-basierte Kampagnen**
Codes passen perfekt zu zeitlichen Events:
- Black Friday (BLACK2024)
- Weihnachten (XMAS-DEAL)
- Produktlaunches (LAUNCH50)
@ -464,6 +484,7 @@ Codes passen perfekt zu zeitlichen Events:
**4. Partnership und Kooperationen**
Codes sind ideal für Partnerschaften:
- Corporate Benefits
- Influencer-Kooperationen
- Cross-Promotions
@ -485,7 +506,7 @@ Codes sind ideal für Partnerschaften:
- Cookie als Fallback
- URL-Parameter als Backup
3. **Communication-Layer**:
3. **Communication-Layer**:
- Beacon-API für Analytics
- Fetch für Attribution-Events
- WebSocket für Real-Time
@ -598,12 +619,14 @@ Eine elegante Lösung kombiniert beide Systeme:
2. **Bonus-Layer**: Optionale Code-Eingabe für zusätzliche Vorteile
**Beispiel-Flow:**
- Nutzer klickt Link → Automatisches Tracking aktiv
- Landing-Page zeigt: "Bonus-Code für Extra-Vorteile: SPECIAL20"
- Nutzer kann ignorieren → Normale Conversion mit Attribution
- Oder Code eingeben → Extra-Vorteile + verstärkte Attribution
**Vorteile:**
- Basis-Attribution immer gesichert
- Zusätzliche Motivation durch Codes
- Selbst-Selektion von engaged Users
@ -625,6 +648,7 @@ Dies erlaubt organisches Wachstum der Code-Kultur.
**Konzept: Intelligente, kontextabhängige Codes**
Codes, die sich adaptiv verhalten:
- Gleicher Code, unterschiedliche Vorteile je nach Kontext
- Zeit-basierte Vorteile (morgens anders als abends)
- Geo-basierte Anpassungen
@ -632,6 +656,7 @@ Codes, die sich adaptiv verhalten:
**Beispiel:**
Code "SMART2024" gibt:
- Neue Nutzer: 50% Rabatt ersten Monat
- Bestehende Nutzer: Extra-Features
- Premium-Nutzer: Gratis-Monat für Freund
@ -690,31 +715,34 @@ Code "SMART2024" gibt:
### Vergleichende Metriken
| Metrik | Automatisches System | Voucher-System |
|--------|---------------------|----------------|
| **Setup-Komplexität** | Hoch (Technik) | Niedrig (Business) |
| **Attribution-Genauigkeit** | 60-80% | 95-100% |
| **Conversion-Rate-Impact** | 0% (neutral) | -20 bis -40% |
| **Nutzer-Engagement** | Niedrig | Hoch |
| **Viral-Potenzial** | Niedrig | Hoch |
| **Wartungsaufwand** | Niedrig | Mittel |
| **Skalierbarkeit** | Exzellent | Gut |
| **Kosten pro Conversion** | Niedrig | Mittel-Hoch |
| Metrik | Automatisches System | Voucher-System |
| --------------------------- | -------------------- | ------------------ |
| **Setup-Komplexität** | Hoch (Technik) | Niedrig (Business) |
| **Attribution-Genauigkeit** | 60-80% | 95-100% |
| **Conversion-Rate-Impact** | 0% (neutral) | -20 bis -40% |
| **Nutzer-Engagement** | Niedrig | Hoch |
| **Viral-Potenzial** | Niedrig | Hoch |
| **Wartungsaufwand** | Niedrig | Mittel |
| **Skalierbarkeit** | Exzellent | Gut |
| **Kosten pro Conversion** | Niedrig | Mittel-Hoch |
## Reale Fallstudien und Learnings
### Fallstudie 1: Gaming-App mit automatischem Tracking
**Ausgangslage:**
- Casual Mobile Game, Free-to-Play
- Zielgruppe: 18-35 Jahre
- Monetarisierung durch In-App-Käufe
**Implementation:**
- Automatisches Referral-Tracking
- Keine Promo-Codes initially
**Ergebnisse:**
- 73% Attribution-Rate
- 2.3x höhere Install-Rate vs. Promo-Codes
- Aber: 40% niedrigerer Lifetime-Value
@ -725,15 +753,18 @@ Automatisches Tracking maximiert Volume, aber Qualität leidet.
### Fallstudie 2: Fitness-App mit Voucher-System
**Ausgangslage:**
- Premium Fitness-App, 19,99€/Monat
- Zielgruppe: 25-45 Jahre, gesundheitsbewusst
- Influencer-Marketing-Fokus
**Implementation:**
- Personalisierte Influencer-Codes
- 30% Rabatt für 3 Monate
**Ergebnisse:**
- 98% Attribution-Genauigkeit
- 24% Redemption-Rate
- 3.2x höherer LTV vs. organische Nutzer
@ -745,15 +776,18 @@ Codes schaffen Commitment und Community.
### Fallstudie 3: B2B-SaaS mit Hybrid-Modell
**Ausgangslage:**
- Projektmanagement-Tool
- 50-500€/Monat je nach Plan
- Lange Sales-Cycles
**Implementation:**
- Automatisches Tracking für Trial-Signups
- Voucher-Codes für Paid-Conversions
**Ergebnisse:**
- 89% Combined Attribution
- 45% höhere Trial-to-Paid-Rate mit Codes
- Vereinfachtes Partner-Programm
@ -766,12 +800,14 @@ Hybrid-Modelle können das Beste aus beiden Welten vereinen.
### Automatisches Referral-System
**Einmalige Kosten:**
- Entwicklung: 15.000-30.000€
- Testing: 3.000-5.000€
- Integration: 5.000-10.000€
- **Gesamt: 23.000-45.000€**
**Laufende Kosten (monatlich):**
- Server/Infrastructure: 200-1.000€
- Maintenance: 500-1.000€
- Monitoring: 100-300€
@ -779,6 +815,7 @@ Hybrid-Modelle können das Beste aus beiden Welten vereinen.
**ROI-Berechnung:**
Bei 10.000 Conversions/Monat und 70% Attribution:
- 7.000 zugeordnete Conversions
- Bei 10€ Provision: 70.000€ korrekt zugeordnet
- ROI-Breakeven: 1-2 Monate
@ -786,12 +823,14 @@ Bei 10.000 Conversions/Monat und 70% Attribution:
### Voucher-System
**Einmalige Kosten:**
- Entwicklung: 8.000-15.000€
- Design/UX: 3.000-5.000€
- Integration: 2.000-5.000€
- **Gesamt: 13.000-25.000€**
**Laufende Kosten (monatlich):**
- Rabatte/Incentives: 5-30% des Umsatzes
- Code-Management: 300-500€
- Support: 500-1.500€
@ -800,6 +839,7 @@ Bei 10.000 Conversions/Monat und 70% Attribution:
**ROI-Berechnung:**
Bei 10.000 Sichtkontakten und 20% Redemption:
- 2.000 Code-Nutzer
- Bei 30% höherem LTV (+20€): 40.000€ Mehrwert
- Minus 20% Rabattkosten: 32.000€ Netto
@ -873,16 +913,16 @@ Bei 10.000 Sichtkontakten und 20% Redemption:
### Entscheidungsmatrix
| Faktor | Gewicht | Automatisch | Voucher |
|--------|---------|-------------|----------|
| **Technische Komplexität** | 15% | 3/10 | 8/10 |
| **Attribution-Genauigkeit** | 20% | 7/10 | 10/10 |
| **Conversion-Rate** | 25% | 10/10 | 6/10 |
| **Nutzer-Engagement** | 15% | 3/10 | 9/10 |
| **Skalierbarkeit** | 10% | 10/10 | 7/10 |
| **DSGVO-Compliance** | 10% | 10/10 | 10/10 |
| **Kosten** | 5% | 8/10 | 5/10 |
| **Gewichteter Score** | 100% | **7.5/10** | **7.8/10** |
| Faktor | Gewicht | Automatisch | Voucher |
| --------------------------- | ------- | ----------- | ---------- |
| **Technische Komplexität** | 15% | 3/10 | 8/10 |
| **Attribution-Genauigkeit** | 20% | 7/10 | 10/10 |
| **Conversion-Rate** | 25% | 10/10 | 6/10 |
| **Nutzer-Engagement** | 15% | 3/10 | 9/10 |
| **Skalierbarkeit** | 10% | 10/10 | 7/10 |
| **DSGVO-Compliance** | 10% | 10/10 | 10/10 |
| **Kosten** | 5% | 8/10 | 5/10 |
| **Gewichteter Score** | 100% | **7.5/10** | **7.8/10** |
### Entscheidungsbaum
@ -915,6 +955,7 @@ Start: Was ist das primäre Ziel?
**Start mit Voucher-System für schnelle Validierung:**
**Begründung:**
1. **Geringere technische Komplexität** ermöglicht schnelleren Start
2. **100% Attribution** liefert klare Daten für Investoren/Partner
3. **Marketing-Story** ("Exklusive Codes für Early Adopters")
@ -922,6 +963,7 @@ Start: Was ist das primäre Ziel?
5. **Flexibilität** für Experimente mit Incentive-Höhen
**Konkrete Umsetzung:**
- Einfache Code-Struktur: CREATOR-KAMPAGNE (z.B. PETER-LAUNCH)
- Initial hohe Incentives (30-50%) für Momentum
- Fokus auf Influencer und Early Adopters
@ -932,16 +974,19 @@ Start: Was ist das primäre Ziel?
**Migration zu Hybrid-System:**
**Phase 1:** Voucher-System optimieren
- A/B-Tests verschiedener Incentive-Höhen
- Optimierung der Code-Präsentation
- Aufbau einer Code-Kultur in der Community
**Phase 2:** Automatisches Tracking ergänzen
- Parallelbetrieb beider Systeme
- Vouchers als Premium-Option
- Automatisches Tracking als Fallback
**Phase 3:** Datengetriebene Optimierung
- Analyse welches System für welche Zielgruppe funktioniert
- Segmentierung der Ansätze nach Kampagnen-Typ
- Machine Learning für optimale System-Auswahl
@ -951,6 +996,7 @@ Start: Was ist das primäre Ziel?
**Intelligentes Attribution-Ecosystem:**
Ein System, das kontextabhängig entscheidet:
- **AI-gesteuerte Methodenwahl**: Automatische Auswahl basierend auf User-Signals
- **Progressive Enhancement**: Start simpel, wird sophistizierter mit User-Reife
- **Multi-Touch-Attribution**: Kombination beider Methoden für vollständiges Bild
@ -969,12 +1015,14 @@ Die Entscheidung zwischen automatischem Referral-Tracking und manuellem Voucher-
**Für uload spezifisch empfehle ich:**
**Start mit Voucher-System**, weil:
- Schnellere Time-to-Market
- Perfekte Attribution für Investor-Demos
- Community-Building-Potenzial
- Geringeres technisches Risiko
**Evolution zum Hybrid-System**, weil:
- Best of Both Worlds
- Maximale Flexibilität
- Datengetriebene Optimierung möglich
@ -982,4 +1030,4 @@ Die Entscheidung zwischen automatischem Referral-Tracking und manuellem Voucher-
Die Voucher-Codes sollten nicht als technische Limitation gesehen werden, sondern als Feature, das die Marke stärkt und eine engagierte Community aufbaut. Der kurzfristige Conversion-Verlust wird durch höhere Nutzer-Qualität und bessere Attribution mehr als kompensiert.
Langfristig wird die Kombination beider Ansätze uload ermöglichen, verschiedene Nutzer-Segmente optimal anzusprechen und dabei sowohl Wachstum als auch Profitabilität zu maximieren.
Langfristig wird die Kombination beider Ansätze uload ermöglichen, verschiedene Nutzer-Segmente optimal anzusprechen und dabei sowohl Wachstum als auch Profitabilität zu maximieren.

View file

@ -11,6 +11,7 @@ Voucher-Codes sind auf den ersten Blick simple Rabattcodes - doch bei genauerer
Voucher-Codes funktionieren als **soziale Währung**. Menschen teilen Codes nicht nur, um anderen einen Gefallen zu tun, sondern auch, um ihren eigenen sozialen Status zu erhöhen. "Ich habe einen exklusiven Code für dich" ist eine Form von sozialem Kapital. Der Teilende positioniert sich als Insider, als jemand mit Zugang zu besonderen Vorteilen.
Diese Dynamik ist besonders stark, wenn:
- Codes limitiert oder exklusiv sind
- Der Teilende selbst einen Vorteil erhält (Win-Win)
- Die Codes personalisiert sind (PETERS-SPECIAL vs. GENERIC123)
@ -44,16 +45,19 @@ Das Eingeben eines Codes wird zur bewussten, befriedigenden Handlung - wie das E
Voucher-Codes können zu einem integralen Teil der Community-Identität werden. Beispiele aus erfolgreichen Communities:
**Gaming-Communities:**
- Streamer haben eigene Codes, die zur "Crew" dazugehören
- Codes werden in Chat-Nachrichten wie Insider-Witze verwendet
- "Bist du Team NINJA oder Team VIKING?" (verschiedene Creator-Codes)
**Fitness-Communities:**
- Trainer-spezifische Codes schaffen Zugehörigkeit
- "Train with Team ALEX20" wird zum Motto
- Challenges verbunden mit speziellen Event-Codes
**Bildungs-Communities:**
- Dozenten-Codes für ihre Studenten
- Kurs-spezifische Codes für Lerngruppen
- Alumni-Codes als dauerhafter Benefit
@ -63,21 +67,25 @@ Voucher-Codes können zu einem integralen Teil der Community-Identität werden.
Ein gestaffeltes Code-System schafft natürliche Community-Hierarchien:
**Öffentliche Ebene:**
- Allgemeine Codes mit kleinen Rabatten (10%)
- Für jeden zugänglich
- Einstieg in die Community
**Member-Ebene:**
- Bessere Codes für registrierte Community-Mitglieder (20%)
- Erfordert Anmeldung/Engagement
- Gefühl der Zugehörigkeit
**VIP-Ebene:**
- Exklusive Codes für Top-Contributors (30-50%)
- Früher Zugang zu neuen Features
- Status-Symbol in der Community
**Inner Circle:**
- Persönliche Codes für engste Supporter
- Unlimited Benefits
- Co-Creation Möglichkeiten
@ -87,16 +95,19 @@ Ein gestaffeltes Code-System schafft natürliche Community-Hierarchien:
Voucher-Codes sind perfekte Katalysatoren für Community-Events:
**Flash-Mobs und Challenges:**
- "Die ersten 100, die Code FLASH24 nutzen..."
- Zeitlich begrenzte Community-Aktionen
- Gemeinsame Ziele ("10.000 Code-Nutzungen = Feature X für alle")
**Seasonal Campaigns:**
- Weihnachts-Codes als Geschenk-Tradition
- Geburtstags-Codes für Community-Jubiläen
- Back-to-School Codes für Bildungs-Apps
**Collaborative Unlocks:**
- Codes, die erst ab X Nutzungen aktiviert werden
- Community muss zusammenarbeiten
- Geteilter Erfolg stärkt Zusammenhalt
@ -108,6 +119,7 @@ Voucher-Codes sind perfekte Katalysatoren für Community-Events:
**Transparenz und Vertrauen:**
Affiliates sehen in Echtzeit:
- Wie oft ihr Code genutzt wurde
- Welche Conversions daraus resultierten
- Ihre exakte Provision
@ -117,6 +129,7 @@ Diese Transparenz schafft Vertrauen und motiviert zu mehr Engagement. Im Gegensa
**Einfachheit der Integration:**
Affiliates müssen:
- Keine komplexen Tracking-Links generieren
- Keine technischen Integrationen vornehmen
- Keine Cookies oder Pixel einbauen
@ -126,6 +139,7 @@ Ein einfacher Code reicht. Das senkt die Einstiegshürde dramatisch.
**Flexibilität in der Vermarktung:**
Codes funktionieren überall:
- Social Media Posts und Stories
- YouTube-Video-Beschreibungen
- Podcast-Erwähnungen
@ -136,6 +150,7 @@ Codes funktionieren überall:
### Strukturierung eines Affiliate-Programms
**Tier 1: Micro-Influencer (100-1.000 Follower)**
- Persönlicher Code: NAME20
- Benefit für Nutzer: 20% Rabatt
- Provision: 15% auf Lifetime des Kunden
@ -143,6 +158,7 @@ Codes funktionieren überall:
- Support: Self-Service Dashboard
**Tier 2: Mid-Tier Influencer (1.000-10.000 Follower)**
- Custom Code nach Wahl
- Benefit für Nutzer: 25% Rabatt
- Provision: 20% auf Lifetime
@ -150,6 +166,7 @@ Codes funktionieren überall:
- Support: Monatliche Check-ins
**Tier 3: Macro-Influencer (10.000+ Follower)**
- Multiple Codes für verschiedene Kampagnen
- Benefit für Nutzer: 30% Rabatt + Extras
- Provision: 25% auf Lifetime
@ -157,6 +174,7 @@ Codes funktionieren überall:
- Support: Dedicated Account Manager
**Tier 4: Strategic Partners**
- White-Label-Codes
- Custom Benefits
- Provision: Individual verhandelt
@ -168,6 +186,7 @@ Codes funktionieren überall:
**A/B-Testing von Codes:**
Affiliates können verschiedene Codes testen:
- SAVE20 vs. DISCOUNT20 vs. DEAL20
- Verschiedene Rabatthöhen
- Zeitlimitierte vs. dauerhafte Codes
@ -176,6 +195,7 @@ Affiliates können verschiedene Codes testen:
**Saisonale und Event-Codes:**
Spezielle Codes für maximale Conversion:
- BLACK50 für Black Friday
- SUMMER30 für Sommerferien
- NEWYEAR für Neujahrsvorsätze
@ -184,6 +204,7 @@ Spezielle Codes für maximale Conversion:
**Personalisierte Landing Pages:**
Jeder Code führt zu angepasster Experience:
- Begrüßung mit Affiliate-Namen
- Angepasste Value Proposition
- Social Proof des Affiliates
@ -198,6 +219,7 @@ Jeder Code führt zu angepasster Experience:
Die Struktur der Codes sollte sowohl menschenfreundlich als auch systemoptimiert sein:
**Format-Optionen:**
1. **Personalisiert-einfach**: PETER, SARAH, TECH
2. **Personalisiert-komplex**: PETER2024, SARAH-APP
3. **Kampagnen-basiert**: LAUNCH50, SUMMER30
@ -206,16 +228,19 @@ Die Struktur der Codes sollte sowohl menschenfreundlich als auch systemoptimiert
**Generierungs-Strategien:**
**Manuelle Generierung** für VIP-Partner:
- Persönliche Abstimmung
- Wunsch-Codes möglich
- Marken-Alignment
**Semi-automatisch** für reguläre Affiliates:
- Template-basiert: {NAME}-{CAMPAIGN}
- Validierung gegen Blacklist
- Eindeutigkeitsprüfung
**Voll-automatisch** für Massen-Kampagnen:
- Algorithmus-generiert
- Collision-Vermeidung
- Batch-Generierung
@ -234,17 +259,20 @@ Die Struktur der Codes sollte sowohl menschenfreundlich als auch systemoptimiert
**Sicherheitsmaßnahmen:**
**Rate-Limiting:**
- Max. 5 Code-Versuche pro Minute
- IP-basierte Beschränkungen
- Device-Fingerprinting
**Fraud-Prevention:**
- Velocity-Checks (zu viele Nutzungen zu schnell)
- Geo-Blocking (Codes nur in bestimmten Ländern)
- Device-Limits (ein Code pro Gerät)
- Referrer-Validation
**Abuse-Handling:**
- Automatische Code-Deaktivierung bei Missbrauch
- Blacklisting von Nutzern/IPs
- Alert-System für Anomalien
@ -254,18 +282,21 @@ Die Struktur der Codes sollte sowohl menschenfreundlich als auch systemoptimiert
**Affiliate-Dashboard Features:**
**Real-Time Metrics:**
- Live-Counter für Code-Nutzungen
- Conversion-Funnel-Visualisierung
- Earnings-Ticker
- Trend-Analysen
**Performance-Insights:**
- Beste Tageszeiten für Conversions
- Geo-Performance
- Device-Breakdown
- Vergleich mit Peer-Group
**Optimization-Tools:**
- A/B-Test-Results
- Suggested Improvements
- Best-Practice-Tipps
@ -274,18 +305,21 @@ Die Struktur der Codes sollte sowohl menschenfreundlich als auch systemoptimiert
**Admin-Dashboard Features:**
**Code-Management:**
- Bulk-Creation und -Editing
- Regelbasierte Automation
- Quick-Actions (Pause, Extend, Modify)
- Template-Library
**Affiliate-Management:**
- Performance-Rankings
- Automatic Tier-Upgrades
- Commission-Berechnungen
- Auszahlungs-Management
**Business Intelligence:**
- Customer-Lifetime-Value nach Code
- Churn-Rate nach Acquisition-Channel
- ROI-Berechnungen
@ -298,6 +332,7 @@ Die Struktur der Codes sollte sowohl menschenfreundlich als auch systemoptimiert
**Blog-Posts und Artikel:**
"Die 10 besten Promo-Codes für [Produkt] im November 2024"
- SEO-optimiert für Code-Suchen
- Regelmäßig aktualisiert
- User-Generated-Content einbinden
@ -305,6 +340,7 @@ Die Struktur der Codes sollte sowohl menschenfreundlich als auch systemoptimiert
**Video-Content:**
"Unboxing mit exklusivem Code"
- Code als Call-to-Action
- Visual einblenden
- Beschreibung und Kommentare
@ -312,6 +348,7 @@ Die Struktur der Codes sollte sowohl menschenfreundlich als auch systemoptimiert
**Podcast-Sponsoring:**
"Nutze Code PODCAST für 20% Rabatt"
- Einfach zu merken
- Mündlich kommunizierbar
- Tracking von Audio-Conversions
@ -319,24 +356,28 @@ Die Struktur der Codes sollte sowohl menschenfreundlich als auch systemoptimiert
### Social Media Strategien
**Instagram:**
- Story-Sticker mit Codes
- Swipe-Up mit vorausgefülltem Code
- IGTV-Tutorials mit Code-Reveals
- Reels mit Code-Challenges
**TikTok:**
- Viral Challenges mit Codes
- Duett-Videos mit Code-Sharing
- Live-Streams mit Flash-Codes
- Comment-Codes für Engagement
**Twitter/X:**
- Thread-Kampagnen mit Code am Ende
- Retweet-für-Code Aktionen
- Space-exklusive Codes
- Trending-Hashtags mit Codes
**LinkedIn:**
- B2B-Codes für Professionals
- Artikel mit Industry-Insights und Codes
- Company-Page-Updates
@ -345,18 +386,21 @@ Die Struktur der Codes sollte sowohl menschenfreundlich als auch systemoptimiert
### E-Mail-Marketing
**Welcome-Series:**
1. Tag 1: Welcome + WELCOME20
2. Tag 3: Feature-Highlight + TRYIT15
3. Tag 7: Success-Stories + SUCCESS25
4. Tag 14: Urgency + LASTCHANCE30
**Segmentierte Kampagnen:**
- Inaktive Nutzer: COMEBACK50
- Power-User: VIP-EXCLUSIVE
- Trial-Auslauf: STAYWITHUS40
- Geburtstage: BIRTHDAY-GIFT
**Referral-Programme:**
- "Gib deinen Freunden FRIEND30"
- "Für jede Nutzung erhältst du..."
- Leaderboards in Newsletter
@ -366,24 +410,28 @@ Die Struktur der Codes sollte sowohl menschenfreundlich als auch systemoptimiert
### Code-Naming-Psychologie
**Emotionale Trigger:**
- WINNING (Gewinner-Gefühl)
- EXCLUSIVE (Exklusivität)
- SMART (Intelligente Entscheidung)
- FREEDOM (Freiheit/Unabhängigkeit)
**Urgency-Codes:**
- LAST24 (Zeitdruck)
- ENDING (Knappheit)
- NOW50 (Sofort-Handlung)
- TODAY (Tagesaktion)
**Value-Kommunikation:**
- SAVE50 (Klarer Sparvorteil)
- HALFPRICE (Einfache Mathematik)
- BUNDLE (Mehrwert)
- PREMIUM (Upgrade-Gefühl)
**Community-Codes:**
- FAMILY (Zugehörigkeit)
- TEAM (Gemeinschaft)
- CLUB (Exklusivität)
@ -473,16 +521,19 @@ Die Struktur der Codes sollte sowohl menschenfreundlich als auch systemoptimiert
### Fallstudie 1: Fitness-App "FitLife"
**Ausgangslage:**
- Premium-Fitness-App, 29€/Monat
- Zielgruppe: 25-45 Jahre
- Problem: Hohe Akquisitionskosten
**Voucher-Strategie:**
- Personal-Trainer erhalten individuelle Codes
- 30% Rabatt für erste 3 Monate
- Trainer erhalten 20% recurring commission
**Ergebnisse nach 6 Monaten:**
- 340 aktive Trainer-Affiliates
- 12.000 neue Kunden über Codes
- 65% niedrigere CAC
@ -490,6 +541,7 @@ Die Struktur der Codes sollte sowohl menschenfreundlich als auch systemoptimiert
- Community von 50.000 aktiven Mitgliedern
**Erfolgsfaktoren:**
- Trainer als vertrauenswürdige Autoritäten
- Win-Win-Situation (Trainer verdienen mit)
- Community-Events mit Code-Challenges
@ -497,16 +549,19 @@ Die Struktur der Codes sollte sowohl menschenfreundlich als auch systemoptimiert
### Fallstudie 2: SaaS-Tool "ProjectPro"
**Ausgangslage:**
- B2B-Projektmanagement-Tool
- 50-500€/Monat je nach Teamgröße
- Problem: Lange Sales-Cycles
**Voucher-Strategie:**
- Branchen-spezifische Codes (AGENCY30, STARTUP40)
- Consultants als Affiliates
- Gestaffelte Benefits nach Teamgröße
**Ergebnisse nach 12 Monaten:**
- 89 Partner-Consultants
- 450 Enterprise-Kunden
- 3.2x höherer LTV
@ -514,6 +569,7 @@ Die Struktur der Codes sollte sowohl menschenfreundlich als auch systemoptimiert
- 15 Branchen-Communities aufgebaut
**Erfolgsfaktoren:**
- B2B-Empfehlungen extrem wertvoll
- Consultants bringen ganze Teams
- Industry-Events mit exklusiven Codes
@ -521,16 +577,19 @@ Die Struktur der Codes sollte sowohl menschenfreundlich als auch systemoptimiert
### Fallstudie 3: Gaming-Platform "PlayHub"
**Ausgangslage:**
- Gaming-Subscription-Service
- 9,99€/Monat
- Zielgruppe: 16-30 Jahre
**Voucher-Strategie:**
- Streamer-Codes mit Gamification
- Leaderboard für Code-Nutzungen
- Monatliche Preise für Top-Affiliates
**Ergebnisse nach 3 Monaten:**
- 1.200 Streamer aktiviert
- 180.000 Code-Redemptions
- Viral Coefficient: 2.3
@ -538,6 +597,7 @@ Die Struktur der Codes sollte sowohl menschenfreundlich als auch systemoptimiert
- Sehr aktive Discord-Community
**Erfolgsfaktoren:**
- Perfekter Fit zur Gaming-Kultur
- Competitive Element durch Leaderboard
- Integration in Streaming-Content
@ -550,6 +610,7 @@ Die Struktur der Codes sollte sowohl menschenfreundlich als auch systemoptimiert
Codes wie "XYZPROMO2024Q4SPECIAL50" sind unmöglich zu merken.
**Lösung:**
- Kurz und prägnant (max. 10 Zeichen)
- Merkbare Wörter statt Zeichensalat
- Konsistente Struktur
@ -560,6 +621,7 @@ Codes wie "XYZPROMO2024Q4SPECIAL50" sind unmöglich zu merken.
Nutzer warten auf bessere Codes, wenn Rabatte stark variieren.
**Lösung:**
- Klare Tier-Struktur
- Vorhersehbare Rabatt-Level
- Seltene Special-Events
@ -570,6 +632,7 @@ Nutzer warten auf bessere Codes, wenn Rabatte stark variieren.
Code-Sharing in Gutschein-Foren kannibalisiert Vollpreis-Verkäufe.
**Lösung:**
- Personalisierte Codes mit Limits
- Geo-Restrictions
- Time-based Validity
@ -580,6 +643,7 @@ Code-Sharing in Gutschein-Foren kannibalisiert Vollpreis-Verkäufe.
Affiliates fühlen sich alleingelassen und werden inaktiv.
**Lösung:**
- Regelmäßige Check-ins
- Performance-Feedback
- Success-Resources
@ -591,6 +655,7 @@ Affiliates fühlen sich alleingelassen und werden inaktiv.
Code-Eingabe auf Mobile ist frustrierend.
**Lösung:**
- Auto-Complete
- Copy-Paste-Optimierung
- QR-Code-Alternative
@ -608,6 +673,7 @@ Code-Eingabe auf Mobile ist frustrierend.
4. **Keine PII**: Keine personenbezogenen Daten des Endnutzers nötig
**Best Practices:**
- Klare Datenschutzerklärung
- Opt-in für Marketing-Kommunikation
- Sichere Speicherung von Affiliate-Daten
@ -616,12 +682,14 @@ Code-Eingabe auf Mobile ist frustrierend.
### Steuerliche Aspekte
**Für Affiliates:**
- Provisionen sind Einkommen
- Meldepflicht ab bestimmten Grenzen
- Rechnungsstellung erforderlich
- Umsatzsteuer-Thematik
**Für Unternehmen:**
- Rabatte als Marketing-Ausgaben
- Provisions-Buchhaltung
- Internationale Steuer-Compliance
@ -630,6 +698,7 @@ Code-Eingabe auf Mobile ist frustrierend.
### Verbraucherschutz
**Wichtige Regeln:**
- Keine irreführende Werbung
- Klare Bedingungen
- Tatsächliche Verfügbarkeit
@ -642,16 +711,19 @@ Code-Eingabe auf Mobile ist frustrierend.
**Kulturelle Anpassung:**
**USA:**
- Aggressive Discounts (50-70%)
- FREEDOM, AMERICA, SUPER
- Black Friday/Cyber Monday Focus
**Deutschland:**
- Moderate Rabatte (20-30%)
- SPAR, VORTEIL, EXKLUSIV
- Qualität vor Quantität
**Japan:**
- Gruppen-Codes populär
- Saisonale Codes (Sakura, NewYear)
- Höflichkeit in Kommunikation
@ -666,24 +738,28 @@ Code-Eingabe auf Mobile ist frustrierend.
### Wachstums-Strategien
**Phase 1: Launch (0-1.000 Nutzer)**
- 10-20 Hand-selektierte Affiliates
- Hohe Rabatte (40-50%)
- Persönliche Betreuung
- Quick Iterations
**Phase 2: Growth (1.000-10.000 Nutzer)**
- 100+ Affiliates
- Tiered System einführen
- Automation Tools
- Community Building
**Phase 3: Scale (10.000-100.000 Nutzer)**
- 1.000+ Affiliates
- Self-Service Platform
- API-Integration
- International Expansion
**Phase 4: Maturity (100.000+ Nutzer)**
- Enterprise Partnerships
- White-Label Solutions
- Advanced Analytics
@ -694,21 +770,25 @@ Code-Eingabe auf Mobile ist frustrierend.
### Technologische Innovationen
**AI-Powered Personalization:**
- Individuelle Codes basierend auf Nutzer-Verhalten
- Optimale Rabatthöhe per Machine Learning
- Predictive Code-Generation
**Blockchain-Integration:**
- NFT-Codes als handelbare Assets
- Transparente Attribution-Chain
- Dezentralisierte Affiliate-Netzwerke
**Voice-Commerce:**
- Alexa/Siri Code-Eingabe
- Audio-Codes in Podcasts
- Voice-activated Rewards
**AR/VR-Integration:**
- Codes in virtuellen Welten
- AR-Scavenger-Hunts
- VR-Shopping mit Codes
@ -716,16 +796,19 @@ Code-Eingabe auf Mobile ist frustrierend.
### Markt-Trends
**Subscription-Economy:**
- Codes für Subscription-Services
- Tier-Upgrades via Codes
- Retention-Codes
**Creator-Economy:**
- Jeder Creator wird Affiliate
- Micro-Influencer-Revolution
- Community-Commerce
**Sustainability-Focus:**
- Eco-Codes für nachhaltige Choices
- Carbon-Offset-Codes
- Social-Impact-Codes
@ -735,12 +818,14 @@ Code-Eingabe auf Mobile ist frustrierend.
### Monat 1: Foundation
**Woche 1-2: System-Design**
- Code-Format festlegen
- Datenbank-Schema
- API-Spezifikation
- Security-Konzept
**Woche 3-4: MVP-Entwicklung**
- Basic Code-Generation
- Validation-Engine
- Simple Dashboard
@ -749,12 +834,14 @@ Code-Eingabe auf Mobile ist frustrierend.
### Monat 2: Launch
**Woche 5-6: Testing & Refinement**
- Internal Testing
- Security Audit
- Performance Testing
- UX-Optimierung
**Woche 7-8: Soft Launch**
- 10 Beta-Affiliates
- Feedback sammeln
- Iterationen
@ -763,12 +850,14 @@ Code-Eingabe auf Mobile ist frustrierend.
### Monat 3: Growth
**Woche 9-10: Public Launch**
- Marketing-Kampagne
- Affiliate-Rekrutierung
- Press-Coverage
- Community-Events
**Woche 11-12: Optimization**
- A/B-Testing
- Performance-Analyse
- Feature-Erweiterungen
@ -820,16 +909,19 @@ Code-Eingabe auf Mobile ist frustrierend.
### Langfristige Vision
**Jahr 1: Foundation**
- 1.000 aktive Affiliates
- 100.000 Code-Redemptions
- Profitabilität erreichen
**Jahr 2: Expansion**
- 10.000 aktive Affiliates
- 1 Million Code-Redemptions
- International Launch
**Jahr 3: Leadership**
- 50.000 aktive Affiliates
- 10 Millionen Code-Redemptions
- Marktführer in Europa
@ -848,4 +940,4 @@ Der Schlüssel zum Erfolg liegt nicht in der Technologie selbst, sondern in der
Für uload bedeutet dies konkret: Start simple, think big. Beginnt mit einem einfachen Code-System, aber designed es von Anfang an für Skalierung. Baut eine Kultur um die Codes herum. Macht sie zu einem Teil der Marken-DNA. Und vor allem: Hört auf eure Community. Sie wird euch zeigen, wie die Codes optimal genutzt werden können.
Die Zukunft gehört den Plattformen, die es schaffen, Technologie menschlich zu machen. Voucher-Codes sind genau das: Eine technische Lösung, die menschliche Bedürfnisse nach Zugehörigkeit, Anerkennung und Vorteil bedient. Für uload ist dies die Chance, nicht nur ein Tool zu bauen, sondern ein Ökosystem zu schaffen, in dem jeder gewinnt.
Die Zukunft gehört den Plattformen, die es schaffen, Technologie menschlich zu machen. Voucher-Codes sind genau das: Eine technische Lösung, die menschliche Bedürfnisse nach Zugehörigkeit, Anerkennung und Vorteil bedient. Für uload ist dies die Chance, nicht nur ein Tool zu bauen, sondern ein Ökosystem zu schaffen, in dem jeder gewinnt.

View file

@ -190,7 +190,7 @@ Dieses Dokument zeigt den aktuellen Status der Toast-Integration in uLoad.
notify.promise(saveOperation(), {
loading: 'Wird gespeichert...',
success: 'Erfolgreich gespeichert!',
error: (err) => `Fehler: ${err.message}`
error: (err) => `Fehler: ${err.message}`,
});
```

View file

@ -94,7 +94,7 @@ export const notify = {
error: (message: string, details?: string) => {
if (details) {
toast.error(message, {
description: details
description: details,
});
} else {
toast.error(message);
@ -121,10 +121,10 @@ export const notify = {
toast(message, {
action: {
label: actionLabel,
onClick: onAction
}
onClick: onAction,
},
});
}
},
};
```
@ -150,7 +150,7 @@ notify.error('Username bereits vergeben');
notify.promise(createLink(data), {
loading: 'Link wird erstellt...',
success: 'Link erfolgreich erstellt!',
error: (err) => `Fehler: ${err.message}`
error: (err) => `Fehler: ${err.message}`,
});
// Link kopieren
@ -186,14 +186,14 @@ notify.success('Passwort-Reset-Link wurde gesendet');
notify.promise(updateProfile(data), {
loading: 'Profil wird aktualisiert...',
success: 'Profil erfolgreich gespeichert',
error: 'Fehler beim Speichern des Profils'
error: 'Fehler beim Speichern des Profils',
});
// Avatar hochladen
notify.promise(uploadAvatar(file), {
loading: 'Bild wird hochgeladen...',
success: 'Profilbild aktualisiert',
error: 'Fehler beim Upload'
error: 'Fehler beim Upload',
});
```

View file

@ -3,11 +3,12 @@
## Post 1: Die Psychologie kurzer URLs (Carousel)
### Slide 1 - Cover
**Die Psychologie kurzer URLs**
Warum wir eher auf kurze Links klicken
### Slide 2 - Das Problem
**42% weniger Klicks bei langen URLs**
Studien zeigen:
@ -15,9 +16,10 @@ Studien zeigen:
• Unleserliche Parameter = Spam-Verdacht
• Mobile User scrollen nicht horizontal
*Quelle: Marketing Sherpa Study 2023*
_Quelle: Marketing Sherpa Study 2023_
### Slide 3 - Die Wissenschaft
**Cognitive Load Theory**
Unser Gehirn:
@ -29,6 +31,7 @@ Lang: website.com/category/2024/01/article-title-here-very-long?utm_source=socia
Kurz: website.com/artikel
### Slide 4 - Vertrauensfaktoren
**Was macht Links vertrauenswürdig?**
1. **Erkennbare Domain** (60% Wichtigkeit)
@ -37,6 +40,7 @@ Kurz: website.com/artikel
4. **HTTPS-Verschlüsselung** (5%)
### Slide 5 - Best Practices
**So optimierst du deine Links:**
✅ Nutze sprechende URLs (/sommer-sale)
@ -46,6 +50,7 @@ Kurz: website.com/artikel
✅ Tracke die Performance
### Slide 6 - Call to Action
**Key Takeaway:**
Kurze URLs = Mehr Vertrauen = Mehr Klicks
@ -58,24 +63,30 @@ Speicher diesen Post für später! 📌
## Post 2: Der perfekte Instagram Bio-Link (Reel Script)
### Hook (0-3 Sek)
[Text Overlay: "Nur 3% nutzen ihren Bio-Link richtig!"]
"Stop! Dein Instagram Bio-Link könnte viel mehr leisten!"
### Problem (3-8 Sek)
[Zeige schlechtes Beispiel]
"Die meisten verlinken nur ihre Homepage. Fehler! Warum? Weil Instagram-User spezifische Inhalte suchen!"
### Lösung Teil 1 (8-15 Sek)
[Screen Recording]
"Trick 1: Erstelle eine Link-Sammlung! Alle wichtigen Links an einem Ort. Shop, Blog, YouTube, alles!"
### Lösung Teil 2 (15-22 Sek)
"Trick 2: Update den Link bei jedem Post! Neuer Post über Smoothies? Link zum Rezept! Story über Workout? Link zum Plan!"
### Lösung Teil 3 (22-28 Sek)
"Trick 3: Nutze Analytics! Check welche Links geklickt werden. Mehr davon = mehr Engagement!"
### Call to Action (28-30 Sek)
[Text Overlay: "Speichern & ausprobieren!"]
"Follow für mehr Instagram-Hacks! Welchen Trick probierst du zuerst?"
@ -84,6 +95,7 @@ Speicher diesen Post für später! 📌
## Post 3: QR-Codes richtig einsetzen (Infografik)
### Haupttitel
**QR-Codes: Do's and Don'ts für 2024**
### Do's ✅
@ -121,6 +133,7 @@ QR-Code braucht klaren Kontrast
Immer auch kurze URL dazu angeben
### Fun Facts
• QR steht für "Quick Response"
• Erfunden 1994 in Japan für Toyota
• Kann bis zu 4.296 Zeichen speichern
@ -139,6 +152,7 @@ Kleine Unternehmen haben oft begrenzte Marketing-Ressourcen. Umso wichtiger ist
## 1. Der Hub-Ansatz
Statt überall verschiedene Links zu streuen, erstellen Sie einen zentralen Hub. Dies kann eine einfache Landing Page sein mit:
- Kontaktinformationen
- Wichtigste Produkte/Services
- Social Media Profile
@ -149,6 +163,7 @@ Statt überall verschiedene Links zu streuen, erstellen Sie einen zentralen Hub.
## 2. Campaign-spezifische Links
Für jede Marketing-Kampagne ein eigener Kurz-Link:
- Black Friday: ihrefirma.de/blackfriday
- Newsletter: ihrefirma.de/news-mai
- Instagram: ihrefirma.de/insta
@ -158,6 +173,7 @@ Für jede Marketing-Kampagne ein eigener Kurz-Link:
## 3. QR-Codes im Ladengeschäft
Physisch zu Digital:
- Auf Visitenkarten → digitale Kontaktkarte
- An der Kasse → Google Bewertungen
- Auf Produkten → Anleitungsvideos
@ -166,6 +182,7 @@ Physisch zu Digital:
## 4. Team-Koordination
Wenn mehrere Personen Links teilen:
- Einheitliche Naming-Convention
- Zentrale Link-Verwaltung
- Regelmäßige Aufräum-Sessions
@ -174,6 +191,7 @@ Wenn mehrere Personen Links teilen:
## 5. Analyse & Optimierung
Monatlich prüfen:
- Welche Links werden geklickt?
- Zu welchen Uhrzeiten?
- Von welchen Geräten?
@ -186,13 +204,14 @@ Monatlich prüfen:
□ Team schulen
□ Monatliche Reviews planen
*Tools wie youload können bei der Umsetzung helfen, aber die Strategie ist tool-unabhängig anwendbar.*
_Tools wie youload können bei der Umsetzung helfen, aber die Strategie ist tool-unabhängig anwendbar._
---
## Post 5: UTM-Parameter einfach erklärt (Story-Serie)
### Story 1
**UTM-Parameter in 60 Sekunden erklärt**
[Grafik: URL mit markierten Parametern]
@ -205,6 +224,7 @@ Sie helfen dir zu verstehen:
Swipe für Examples →
### Story 2
**Die 5 wichtigsten Parameter**
1. utm_source = Woher? (instagram, newsletter)
@ -214,6 +234,7 @@ Swipe für Examples →
5. utm_term = Welches Keyword? (bei Ads)
### Story 3
**Real-Life Beispiel**
Normaler Link:
@ -225,6 +246,7 @@ meinshop.de/produkt?utm_source=instagram&utm_medium=social&utm_campaign=black-fr
Jetzt weißt du: Dieser Kunde kam von Instagram während der Black Friday Kampagne!
### Story 4
**Pro-Tipps**
✅ Kleinschreibung verwenden
@ -234,6 +256,7 @@ Jetzt weißt du: Dieser Kunde kam von Instagram während der Black Friday Kampag
✅ In Analytics prüfen
### Story 5
**Quick Quiz!**
Was trackt "utm_medium"?
@ -249,20 +272,25 @@ Antwort: B! Medium zeigt dir ob es Social, Email, Paid oder Organic Traffic war!
## Post 6: Mobile-First Link-Strategie (Video-Script)
### Szene 1 (0-5 Sek)
[Zeige Smartphone-Statistik]
"80% deiner Kunden sind mobil. Aber sind deine Links dafür ready?"
### Szene 2 (5-10 Sek)
[Screen-Recording: Schlechtes Beispiel]
"Problem 1: Desktop-Links auf Mobile. Horizontal scrollen? Nobody got time for that!"
### Szene 3 (10-15 Sek)
[Zeige Daumen-Reichweite]
"Problem 2: Links außerhalb der Thumb-Zone. Der Daumen erreicht nur 30% des Screens!"
### Szene 4 (15-25 Sek)
[Lösungen zeigen]
"Die Fixes:
- Kurze URLs (max 30 Zeichen sichtbar)
- Buttons statt Text-Links
- Unten platzieren (Daumen-Zone)
@ -270,6 +298,7 @@ Antwort: B! Medium zeigt dir ob es Social, Email, Paid oder Organic Traffic war!
- Test auf verschiedenen Geräten!"
### Szene 5 (25-30 Sek)
"Mobile-First ist kein Trend, es ist die Realität. Optimiere jetzt oder verliere Kunden. Check meine Bio für den kompletten Mobile-Guide!"
---
@ -277,10 +306,12 @@ Antwort: B! Medium zeigt dir ob es Social, Email, Paid oder Organic Traffic war!
## Post 7: Hashtag-Strategie Deep Dive (Carousel)
### Slide 1
**Die 30-10-10 Hashtag-Formel**
So findest du die perfekten Hashtags
### Slide 2
**Die Formel erklärt**
• 30% Große Hashtags (500k+ Posts)
• 10% Mittlere (100k-500k Posts)
@ -290,7 +321,9 @@ So findest du die perfekten Hashtags
Warum? Balance zwischen Reichweite und Sichtbarkeit!
### Slide 3
**Research-Methode**
1. Starte mit einem Haupt-Hashtag
2. Check "Ähnliche Hashtags"
3. Analysiere Top-Posts
@ -299,6 +332,7 @@ Warum? Balance zwischen Reichweite und Sichtbarkeit!
6. Erstelle deine Liste
### Slide 4
**Rotation System**
Montag: Set A (30 Hashtags)
Mittwoch: Set B (30 andere)
@ -307,6 +341,7 @@ Freitag: Set C (30 neue)
Instagram mag keine Wiederholung!
### Slide 5
**Tracking Performance**
Welche Hashtags bringen Traffic?
→ Instagram Insights
@ -316,6 +351,7 @@ Welche Hashtags bringen Traffic?
Optimiere basierend auf Daten!
### Slide 6
**Tools & Tricks**
• Later's Hashtag Suggestions
• Hashtagify.me
@ -344,6 +380,7 @@ LinkedIn | Website
**Zeile 5: Call-to-Action**
Rotierend je nach Quartal:
- Q1: "Jahresplanung? → Kostenloses Beratungsgespräch"
- Q2: "Neu: Unser Sommer-Katalog →"
- Q3: "Folgen Sie uns auf LinkedIn →"
@ -351,6 +388,7 @@ Rotierend je nach Quartal:
**Der Link-Trick:**
Nutze Kurz-URLs mit Tracking:
- firma.de/sig-jan (Januar Signatur)
- firma.de/sig-meetings (Meeting-Anfragen)
- firma.de/sig-katalog (Katalog-Download)
@ -413,13 +451,15 @@ Dort findest man alle Formate an einem Ort.
[8-15 Sek - Was kannst du testen?]
"Teste diese Dinge:
- Button-Farben
- Link-Texte ('Mehr erfahren' vs 'Jetzt entdecken')
- Link-Texte ('Mehr erfahren' vs 'Jetzt entdecken')
- Platzierung (oben vs unten)
- Mit oder ohne Emoji"
[15-22 Sek - Wie geht's?]
"So geht's:
1. Teile deine Audience 50/50
2. Version A an Gruppe 1
3. Version B an Gruppe 2
@ -442,4 +482,4 @@ Dort findest man alle Formate an einem Ort.
**Donnerstag:** Post 4 (Link-Strategie) - LinkedIn Artikel
**Freitag:** Post 5 (UTM-Parameter) - Story-Serie
**Samstag:** Community Q&A basierend auf Woche 1
**Sonntag:** Best-of Zusammenfassung der Woche
**Sonntag:** Best-of Zusammenfassung der Woche

View file

@ -1,15 +1,16 @@
# Sprecher-Script: Die Psychologie kurzer URLs
## Aussprache-Hinweise
- URLs aussprechen als "U-R-L" oder "Links"
- Zahlen unter 13 ausschreiben
- Prozentzeichen als "Prozent" sprechen
- "=" als "gleich" aussprechen
- Betonung bei *kursiven* Wörtern
- Betonung bei _kursiven_ Wörtern
---
Habt ihr euch schon mal gefragt, warum manche Links einfach *verdächtig* aussehen?
Habt ihr euch schon mal gefragt, warum manche Links einfach _verdächtig_ aussehen?
Heute erkläre ich euch die Psychologie hinter kurzen URLs und warum unser Gehirn sie einfach lieber mag.
@ -17,9 +18,9 @@ Heute erkläre ich euch die Psychologie hinter kurzen URLs und warum unser Gehir
Krasse Zahl: Zweiundvierzig Prozent weniger Klicks bei langen URLs!
Stellt euch vor - fast die Hälfte eurer potenziellen Besucher klickt nicht, nur weil der Link zu lang ist.
Stellt euch vor - fast die Hälfte eurer potenziellen Besucher klickt nicht, nur weil der Link zu lang ist.
Studien zeigen: URLs über hundert Zeichen erzeugen Misstrauen. Diese ganzen unleserlichen Parameter? Unser Gehirn schreit "Spam-Alarm!"
Studien zeigen: URLs über hundert Zeichen erzeugen Misstrauen. Diese ganzen unleserlichen Parameter? Unser Gehirn schreit "Spam-Alarm!"
Und mobile User? Die scrollen definitiv nicht horizontal für einen Link.
@ -27,23 +28,23 @@ Und mobile User? Die scrollen definitiv nicht horizontal für einen Link.
Jetzt wird's wissenschaftlich: Cognitive Load Theory.
Unser Gehirn ist faul - im positiven Sinne! Es verarbeitet kurze Informationen schneller und verbindet "einfach" automatisch mit "sicher".
Unser Gehirn ist faul - im positiven Sinne! Es verarbeitet kurze Informationen schneller und verbindet "einfach" automatisch mit "sicher".
Seht ihr den Unterschied?
Der lange Link mit all dem Kram hinten dran versus der kurze, klare Link?
Der lange Link mit all dem Kram hinten dran versus der kurze, klare Link?
Unser Gehirn liebt den kurzen. Punkt.
---
Was macht Links eigentlich vertrauenswürdig?
Was macht Links eigentlich vertrauenswürdig?
Nummer eins mit sechzig Prozent Wichtigkeit: Eine erkennbare Domain. Die Leute wollen wissen, wo sie landen!
Fünfundzwanzig Prozent: Keine kryptischen Zeichen. Diese komischen Zahlen-Buchstaben-Kombinationen? Nope!
Zehn Prozent: Kurz und prägnant.
Zehn Prozent: Kurz und prägnant.
Und die letzten fünf Prozent? HTTPS-Verschlüsselung - das kleine Schloss-Symbol.
@ -51,7 +52,7 @@ Und die letzten fünf Prozent? HTTPS-Verschlüsselung - das kleine Schloss-Symbo
Okay, wie optimiert ihr jetzt eure Links?
Erstens: Nutzt sprechende URLs! Statt irgendwelche Zahlen lieber "Schrägstrich sommer-sale".
Erstens: Nutzt sprechende URLs! Statt irgendwelche Zahlen lieber "Schrägstrich sommer-sale".
Zweitens: Keine Sonderzeichen! Die verwirren nur.
@ -68,4 +69,3 @@ Also merkt euch: Kurze URLs gleich mehr Vertrauen gleich mehr Klicks!
Pro-Tipp: Tools wie youload helfen euch, lange URLs zu kürzen UND zu personalisieren. Win-win!
Speichert diesen Post für später und dropped ein Herz, wenn ihr was gelernt habt!

View file

@ -76,7 +76,7 @@ Links kürzen, Profile erstellen, Teams managen. Die smarteste Lösung für dein
## Ausführliche Bio
**Uload - Die All-in-One Plattform für deine digitale Präsenz**
**Uload - Die All-in-One Plattform für deine digitale Präsenz**
✨ **Was wir machen:**
Wir vereinen professionelles Link-Management mit kreativer Selbstdarstellung. Egal ob Creator, Freelancer oder Team - Uload gibt dir die Tools für deinen digitalen Erfolg.
@ -157,4 +157,4 @@ Gehostet in Deutschland, DSGVO-konform, SSL-verschlüsselt mit 99.9% Uptime.
4. **Updates (10%)**
- Neue Features
- Produkt-Updates
- Behind-the-Scenes
- Behind-the-Scenes

View file

@ -36,9 +36,11 @@
## 2. Ausgearbeitete Content-Formate
### Format 1: "Link-Makeover Monday" (Wöchentliche Serie)
**Konzept:** Jeden Montag verwandeln wir das chaotische Link-Setup eines Followers in eine professionelle Uload-Lösung
**Aufbau:**
- Slide 1: Vorher - Screenshot des Problems
- Slide 2-4: Transformation-Prozess mit Uload
- Slide 5: Nachher - Fertiges Setup
@ -47,9 +49,11 @@
**Hook:** "Dein Link-Chaos nervt? Wir räumen auf! 🔗✨"
### Format 2: "60-Sekunden QR-Tutorial" (Reels/Stories)
**Konzept:** Schnelle, actionreiche Tutorials zur QR-Code Erstellung
**Struktur:**
- 0-5 Sek: Problem zeigen (lange URLs auf Flyern)
- 5-15 Sek: Uload öffnen
- 15-40 Sek: QR-Code erstellen & anpassen
@ -59,18 +63,22 @@
**Musik:** Trending Audio mit schnellem Beat
### Format 3: "Template Tuesday" (Carousel Posts)
**Konzept:** Wöchentliche Vorstellung von 3 Profilkarten-Templates
**Slides:**
1. Cover: "3 Link-in-Bio Templates für [Zielgruppe]"
2-4. Je ein Template mit Use Case
5. Vergleichstabelle der Features
6. CTA: "Welches ist dein Favorit? Kommentiere 1, 2 oder 3!"
2-4. Je ein Template mit Use Case
2. Vergleichstabelle der Features
3. CTA: "Welches ist dein Favorit? Kommentiere 1, 2 oder 3!"
### Format 4: "Analytics Insights" (Infografik-Serie)
**Konzept:** Datenvisualisierung und Tipps zur Link-Performance
**Beispiele:**
- "Die besten Zeiten für Link-Shares"
- "Mobile vs. Desktop Click-Raten"
- "Top 5 Referrer-Quellen"
@ -78,18 +86,22 @@
**Design:** Clean, minimalistisch mit Uload-Branding
### Format 5: "Creator Spotlight" (IGTV/Reels)
**Konzept:** 2-3 Minuten Interview mit erfolgreichen Uload-Nutzern
**Fragen:**
- Wie nutzt du Uload?
- Lieblings-Feature?
- ROI/Erfolge?
- Tipps für andere?
### Format 6: "Quick Tips Thursday" (Stories-Serie)
**Konzept:** 5-teilige Story-Serie mit schnellen Hacks
**Beispiel-Serie:**
- Story 1: Hook "5 Uload-Tricks in 60 Sekunden"
- Story 2-5: Je ein Trick (15 Sek)
- Story 6: Swipe-Up/Link zu Anmeldung
@ -97,9 +109,11 @@
## 3. Content-Kalender
### Posting-Frequenz
**Optimal:** 5-6 Posts pro Woche + tägliche Stories
**Wochenrhythmus:**
- **Montag:** Link-Makeover Monday (Carousel)
- **Dienstag:** Template Tuesday (Carousel)
- **Mittwoch:** User Success/Testimonial (Reel)
@ -111,36 +125,43 @@
### Woche 1: Launch-Content-Plan
**Montag - Launch-Ankündigung**
- Post: Hero-Visual "Uload ist da!"
- Stories: Countdown + Features-Preview
- Reel: "3 Gründe warum du Uload brauchst"
**Dienstag - Template Tuesday**
- Carousel: "3 Templates für Content Creators"
- Stories: Template-Customization Tutorial
- Engagement: "Welches Template passt zu dir?"
**Mittwoch - QR-Code Magic**
- Reel: "Von URL zu QR in 30 Sekunden"
- Feed-Post: QR-Use-Cases Infografik
- Stories: User zeigen ihre QR-Codes
**Donnerstag - Quick Tips**
- Carousel: "5 Link-Management Hacks"
- Stories-Serie: Jeden Hack live zeigen
- IGTV: Ausführliches Tutorial
**Freitag - Feature Friday**
- Reel: "Das kann nur Uload: Team-Kollaboration"
- Feed-Post: Team-Features erklärt
- Stories: Q&A zu Teams
**Samstag - Community Day**
- Post: "Zeig uns dein Setup!"
- Stories: User-Generated Content reposten
- Engagement: Beste Setups werden gefeatured
**Sonntag - Soft Sell**
- Carousel: "Free vs. Pro - Was ist richtig für dich?"
- Stories: Testimonials
- Link: Special Launch-Offer
@ -148,11 +169,13 @@
### Story-Strategie (Täglich)
**Fixe Story-Elemente:**
- Morgens: Tipp des Tages
- Mittags: User-Showcase oder FAQ
- Abends: Behind-the-Scenes oder Team-Update
**Interaktive Elemente:**
- Umfragen: "Welches Feature willst du als nächstes?"
- Quiz: "Welcher Link-Typ bist du?"
- Q&A-Sticker: Wöchentliche Fragerunde
@ -164,11 +187,13 @@
#UloadLinks #MoreThanJustLinks #UloadIt
**Größen-Mix pro Post:**
- 5-7 große Hashtags (>500k Posts): #LinkInBio #ContentCreator
- 8-10 mittlere (100k-500k): #LinkManagement #QRCode
- 8-10 kleine (<100k): #DigitalToolsForCreators #LinkShortener
**Zielgruppen-spezifisch:**
- Creators: #CreatorTools #InfluencerTips #ContentStrategy
- Business: #SmallBusinessTools #TeamCollaboration #B2BMarketing
- Events: #EventMarketing #QRCodeMenu #DigitalEvents
@ -176,6 +201,7 @@
### KPIs & Erfolgsmessung
**Wöchentlich tracken:**
- Follower-Wachstum
- Engagement-Rate (Ziel: >5%)
- Story-Views
@ -183,6 +209,7 @@
- Saves-to-Likes Ratio
**Monatlich analysieren:**
- Top-performing Content-Typen
- Beste Posting-Zeiten
- Conversion Rate (Follower → User)
@ -191,6 +218,7 @@
### Content-Produktion
**Batch-Creation:**
- Montags: Wochenplanung & Ideation
- Dienstags: Content-Erstellung (Grafiken, Videos)
- Mittwochs: Copywriting & Hashtag-Research
@ -198,7 +226,8 @@
- Freitags: Analytics-Review & Optimierung
**Tools:**
- Design: Canva/Figma mit Uload-Templates
- Scheduling: Later/Buffer
- Analytics: Instagram Insights + eigene Tracking
- Video: CapCut/InShot für Reels
- Video: CapCut/InShot für Reels

View file

@ -11,91 +11,121 @@
- Abkürzungen ausschreiben: "z.B." wird zu "zum Beispiel"
## Script 1: Willkommen bei youload
Hey! Kennst du das? Du hast tausend Links für Instagram, aber nur Platz für einen in der Bio? Nervt, oder? Ich zeig dir jetzt in dreißig Sekunden, wie youload dein Leben verändert. Swipe up!
## Script 2: QR-Code in zehn Sekunden
Stopp! Ich erstelle jetzt einen QR-Code in zehn Sekunden. Bereit? Los! youload öffnen, Link eingeben, QR-Code klicken, Farbe wählen, fertig! Boom! Acht Sekunden! Krass, oder?
## Script 3: Das Link-Chaos
Okay, real talk. Ich hatte früher einen Notizzettel mit all meinen Links. Dann Excel. Dann Google Docs. Totales Chaos! Jetzt? Alles in youload. Ein Klick, alle Links da. Leben gleich einfacher.
## Script 4: DSGVO minus Warum es wichtig ist
Leute, Datenschutz ist kein Spaß! Wisst ihr, dass viele US-Tools eure Daten illegal speichern? youload läuft auf deutschen Servern und ist made in germany. Eure Daten, eure Kontrolle. Safety first!
## Script 5: Template Tuesday Teaser
Morgen ist Template Tuesday! Ich zeige euch drei mega Profilkarten-Designs. Eins für Fitness-Coaches, eins für Fotografen, eins für Restaurants. Welches wollt ihr zuerst sehen? Votet jetzt!
## Script 6: Der Preis-Check
Okay, Klartext: Linktree kostet neun Dollar. Bitly zehn Dollar. youload? Vier Euro neunundneunzig und du kriegst MEHR Features. Mathe ist nicht meine Stärke, aber das ist ein No-Brainer!
## Script 7: Team-Feature Live Demo
Schaut mal! Mein Team kann jetzt alle Links bearbeiten. Sarah macht QR-Codes, Tom die Analytics, ich chill. Teamwork macht den Traum work! Swipe up für Team-Features!
## Script 8: Analytics Insights
Whoa! Gerade gecheckt: Mein Link wurde fünfhundert mal geklickt! Siebzig Prozent mobile, dreißig Prozent desktop. Die meisten aus Deutschland. Diese Insights! Besser als Instagram Analytics, no cap!
## Script 9: Link-Makeover Ankündigung
Attention! Nächsten Montag mache ich ein Link-Makeover! Schickt mir euer Link-Chaos per DM. Der krasseste Vorher-Nachher gewinnt einen Pro-Account für drei Monate!
## Script 10: Custom URL Tutorial
Quick Tip! Statt youload punkt de Schrägstrich xyz eins zwei drei kannst du youload punkt de Schrägstrich deinname machen! Einfach beim Erstellen Custom Code eingeben. Professionell, merkbar, sexy! Probier's aus!
## Script 11: Morgen-Motivation
Guten Morgen! Weißt du was geil ist? Aufwachen und sehen, dass deine Links über Nacht zweihundert mal geklickt wurden. Passive Reichweite während du schläfst. Das ist youload Power!
## Script 12: Problem-Lösung
"Ich hab keine Zeit für noch ein Tool!" Digga, youload SPART dir Zeit! Fünf Minuten Setup, dann läuft's. Keine zwanzig Tabs mehr offen. Alles an einem Ort. Zeit gespart gleich Geld gespart.
## Script 13: Feature Friday Ankündigung
Es ist Freitag! Heute zeig ich euch das krasseste Feature: Passwortgeschützte Links! Perfekt für exklusiven Content. VIP only! Tutorial kommt in einer Stunde!
## Script 14: User Success Story
OMG! Julia hat mir geschrieben: "Seit youload vierzig Prozent mehr Klicks!" Sie nutzt es für ihren Onlineshop. Alle Produkte, ein Link. Smart! Zeig mir deine Success Story!
## Script 15: Weekend Vibes
Wochenende! Aber deine Links arbeiten weiter. Während du chillst, trackt youload jeden Klick. Montag checkst du die Stats und weißt genau, was funktioniert hat. Work smart, not hard!
## Script 16: QR-Code Use Case
Gestern im Restaurant: QR-Code auf dem Tisch. Speisekarte digital! Mit youload in zwei Minuten gemacht. Kein Druck mehr, Updates instant. Die Zukunft, Leute!
## Script 17: Free vs Pro
"Brauch ich Pro?" Honest: Starte free! Fünfzig Links reichen für den Anfang. Wenn du wächst, upgrade. Kein Stress, kein Druck. youload wächst mit dir!
## Script 18: Behind the Scenes
Fun Fact: youload wurde gebaut, weil wir selbst genervt waren! Kein Tool hatte alles. Also: Selbst gemacht. Von Creators für Creators. Real recognize real!
## Script 19: Monday Motivation
Neue Woche, neue Links! Tipp: Räum montags deine Links auf. Lösche alte, update neue. Zehn Minuten Investment, ganze Woche organized. Let's go!
## Script 20: Mittwochs-Weisheit
Weisheit des Tages: Ein schlechter Link ist wie ein schlechter Händedruck. Erster Eindruck zählt! Mit youload machst du IMMER einen guten ersten Eindruck.
## Script 21: Template Showcase
Check das! Diese Profilkarte! Gradient-Header, Social Icons, Newsletter-Signup, Shop-Links. Alles drin! In fünf Minuten gebaut. Template Store macht's möglich!
## Script 22: Donnerstag Hack
Life Hack: Erstelle verschiedene Profilkarten für verschiedene Zielgruppen! Eine für Insta, eine für LinkedIn, eine für TikTok. Same person, different vibe!
## Script 23: Freitag Feature
Wisst ihr was? Ihr könnt Links zeitlich begrenzen! Black Friday Sale? Link läuft nur achtundvierzig Stunden. FOMO Marketing at its best! Swipe up für Tutorial!
## Script 24: Samstag Stories
Real Question: Wie viele Links habt ihr in eurer Bio verschwendet? Mit youload: Einer reicht. Unendliche Möglichkeiten dahinter. One Link to rule them all!
## Script 25: Sonntag Reflexion
Sonntagsgedanke: Deine Links sind dein digitales Zuhause. Würdest du Gäste in ein chaotisches Haus einladen? Eben. youload gleich digitale Ordnung. Selbstliebe beginnt bei organisierten Links!
## Script 26: Bulk Operations
Yo! Wusstet ihr: Ihr könnt fünfzig Links gleichzeitig bearbeiten? Select all, add tag, done! Was früher Stunden dauerte: zwei Klicks. Efficiency level tausend!
## Script 27: Mobile First
Achtzig Prozent eurer Follower sind mobile! youload ist mobile optimized. Jeder Link, jede Profilkarte, jeder QR-Code. Perfect auf jedem Screen. Responsive ist kein Feature, es ist ein Must!
## Script 28: Tag System
Organisation Tipp: Nutzt Tags! Hashtag kampagne zweitausendvierundzwanzig Hashtag blackfriday Hashtag newsletter. Später findest du alles sofort. Marie Kondo wäre stolz auf euch!
## Script 29: Click Limits
Geheimer Trick: Setzt Click-Limits für exklusive Angebote! "Nur die ersten hundert kriegen Rabatt!" Boom, urgency created! Marketing Psychology hundertundeins!
## Script 30: Community Love
Danke! Seriously! Ihr macht youload zu dem was es ist. Euer Feedback, eure Ideen, eure Support. Wir bauen das für EUCH! Drop ein Herz wenn ihr Teil der youload Familie seid!
Danke! Seriously! Ihr macht youload zu dem was es ist. Euer Feedback, eure Ideen, eure Support. Wir bauen das für EUCH! Drop ein Herz wenn ihr Teil der youload Familie seid!

View file

@ -3,11 +3,13 @@
## 🎯 Grundprinzipien
### 1. **Authentizität vor Übertreibung**
- ❌ **Vermeiden:** Unrealistische Nutzerzahlen ("10.000+ Nutzer" wenn man gerade startet)
- ✅ **Stattdessen:** Ehrliche Positionierung ("Beta Launch 2024", "Neu am Markt")
- ✅ **Alternative Metriken:** Response-Zeit, Uptime, Herkunft ("Made in Germany")
### 2. **Konsistente Preisgestaltung**
- Alle Preisangaben müssen identisch sein zwischen:
- Landing Page
- Pricing Page
@ -15,6 +17,7 @@
- Immer prüfen: `/src/routes/(app)/pricing/+page.svelte` als Single Source of Truth
### 3. **Realistische Social Proof**
- ❌ **Vermeiden:** Fake Testimonials mit erfundenen Erfolgsgeschichten
- ✅ **Alternativen für neue Startups:**
- Beta-Tester Feedback
@ -25,6 +28,7 @@
## 🏗️ Struktur-Empfehlungen
### Hero Section
```
1. Trust Badges (DSGVO, Sicherheit, Performance)
2. Klare Value Proposition (Headline + Subheadline)
@ -33,6 +37,7 @@
```
### Content Sections (Reihenfolge)
```
1. Hero Section
2. Zielgruppen (mit Tabs für verschiedene Personas)
@ -46,6 +51,7 @@
## 🎨 Design Patterns
### Farbcodierung
- **Primary Action:** `bg-theme-primary`
- **Secondary Action:** `border-theme-primary`
- **Success:** Grün (#10b981)
@ -53,6 +59,7 @@
- **Premium/Special:** Purple (#9333ea)
### Komponenten-Struktur
```
/src/lib/components/landing/
├── HeroSection.svelte
@ -66,18 +73,21 @@
## ✅ Checkliste vor Go-Live
### Content Check
- [ ] Alle Preise konsistent?
- [ ] Keine übertriebenen Metriken?
- [ ] Rechtschreibung geprüft?
- [ ] CTAs klar und eindeutig?
### Technical Check
- [ ] Mobile responsive?
- [ ] Ladezeiten optimiert?
- [ ] Forms funktionieren?
- [ ] Analytics tracking aktiv?
### Legal Check
- [ ] DSGVO-Hinweise vorhanden?
- [ ] Impressum verlinkt?
- [ ] Datenschutz verlinkt?
@ -86,17 +96,20 @@
## 🚀 Launch-Phasen Messaging
### Phase 1: Beta Launch (Aktuell)
- "Beta-Zugang sichern"
- "Sei einer der Ersten"
- "Early Access Features"
- "Exklusiver Beta-Preis"
### Phase 2: Public Launch
- "Jetzt verfügbar"
- "X Beta-Tester vertrauen uns"
- "Launch-Angebot"
### Phase 3: Growth
- Echte Nutzerzahlen
- Case Studies
- Erfolgsgeschichten
@ -107,18 +120,21 @@
Statt Nutzerzahlen diese Metriken verwenden:
### Technische Metriken
- Uptime (99.9%)
- Response Time (<100ms)
- Support Response (<2h)
- API Verfügbarkeit
### Qualitative Metriken
- Made in Germany/EU
- DSGVO-konform
- SSL-verschlüsselt
- Open Source (falls zutreffend)
### Zeitliche Metriken
- Gegründet/Launch Jahr
- Updates pro Monat
- Feature Releases
@ -126,6 +142,7 @@ Statt Nutzerzahlen diese Metriken verwenden:
## 🔄 A/B Testing Empfehlungen
### Test-Elemente
1. Headlines (Value Proposition)
2. CTA-Buttons (Text & Farbe)
3. Pricing-Darstellung
@ -133,6 +150,7 @@ Statt Nutzerzahlen diese Metriken verwenden:
5. Trust-Signal Platzierung
### Tracking
- Conversion Rate
- Bounce Rate
- Time on Page
@ -142,6 +160,7 @@ Statt Nutzerzahlen diese Metriken verwenden:
## 📝 Copy-Writing Tipps
### Do's
- ✅ Kurze, prägnante Headlines
- ✅ Benefits vor Features
- ✅ Aktive Sprache
@ -149,6 +168,7 @@ Statt Nutzerzahlen diese Metriken verwenden:
- ✅ Dringlichkeit ohne Fake-Scarcity
### Don'ts
- ❌ Technischer Jargon
- ❌ Zu viele Superlative
- ❌ Unklare CTAs
@ -158,18 +178,21 @@ Statt Nutzerzahlen diese Metriken verwenden:
## 🎯 Conversion-Optimierung
### Above the Fold
- Value Proposition in 5 Sekunden verstehbar
- Primärer CTA sichtbar
- Trust-Signale erkennbar
- Keine Ablenkungen
### Form-Optimierung
- Minimale Felder
- Inline-Validation
- Clear Error Messages
- Progress Indicators (bei Multi-Step)
### Mobile First
- Touch-optimierte Buttons (min. 44x44px)
- Lesbare Schriftgrößen (min. 16px)
- Vereinfachte Navigation
@ -178,12 +201,14 @@ Statt Nutzerzahlen diese Metriken verwenden:
## 🔍 Regelmäßige Reviews
### Monatlich prüfen
- Analytics-Daten
- Conversion-Raten
- User-Feedback
- Konkurrenz-Analyse
### Quarterly Updates
- Content-Refresh
- Feature-Updates
- Testimonial-Updates
@ -201,5 +226,5 @@ Statt Nutzerzahlen diese Metriken verwenden:
---
*Letztes Update: Januar 2025*
*Nächstes Review: Q2 2025*
_Letztes Update: Januar 2025_
_Nächstes Review: Q2 2025_

View file

@ -1,249 +1,268 @@
---
📊 Marktpositionierung & Zielgruppen
📊 Marktpositionierung & Zielgruppen
Primäre Zielgruppen
Primäre Zielgruppen
1. Digital Creators & Influencer
- Benötigen zentrale Link-Hubs für Social Media (Instagram, TikTok)
- Profitieren von anpassbaren Profilseiten und QR-Codes
- Schätzen detaillierte Click-Analytics
2. Kleine bis mittlere Unternehmen (KMU)
- Team-Kollaboration für Marketing-Teams
- Markenkonformes Link-Management
- Kostengünstige Alternative zu Enterprise-Lösungen
3. Freelancer & Agenturen
- Multi-Client-Management möglich
- White-Label-Potenzial durch Custom Domains (bald)
- Professionelle Analytics für Kunden-Reporting
4. Event-Veranstalter & Gastgewerbe
- QR-Code-Generation für Speisekarten, Events
- Zeitlich begrenzte Links für Aktionen
- Passwortgeschützte Links für exklusive Inhalte
1. Digital Creators & Influencer
---
🎯 Kernfunktionalitäten & USPs
1. Umfassendes Link-Management
- Benötigen zentrale Link-Hubs für Social Media (Instagram, TikTok)
- Profitieren von anpassbaren Profilseiten und QR-Codes
- Schätzen detaillierte Click-Analytics
- ✅ Kurz-URLs mit Custom Codes
- ✅ Erweiterte Metadaten (Titel, Beschreibung)
- ✅ Ablaufdatum & Click-Limits
- ✅ Passwortschutz
- ✅ Tag-System zur Organisation
- ✅ Bulk-Operationen
2. Kleine bis mittlere Unternehmen (KMU)
2. Innovative Profilkarten (Alleinstellungsmerkmal!)
- 🎨 3-Stufen-Builder: Anfänger, Fortgeschritten, Experte
- 📦 Modulares System: Header, Content, Media, Links
- 🎭 Template Store: Vorgefertigte Designs
- 🔄 Drag & Drop Sortierung
- 👁️ Live-Preview
- Team-Kollaboration für Marketing-Teams
- Markenkonformes Link-Management
- Kostengünstige Alternative zu Enterprise-Lösungen
3. Team-Kollaboration (NEU)
3. Freelancer & Agenturen
- 👥 Skalierbare Team-Größen (1-∞ Mitglieder)
- 🔐 Granulare Berechtigungen
- 📊 Geteilte Analytics
- 🏢 Workspace-Management
4. Professionelle Analytics
- Multi-Client-Management möglich
- White-Label-Potenzial durch Custom Domains (bald)
- Professionelle Analytics für Kunden-Reporting
- 📈 Click-Tracking in Echtzeit
- 🌍 Browser & Device Distribution
- 🔗 Referrer-Analyse
- 📅 Zeitbasierte Auswertungen
- 📊 Visuelle Dashboards
4. Event-Veranstalter & Gastgewerbe
5. QR-Code-Generator
- 🎨 Anpassbare Farben (Schwarz, Weiß, Gold)
- 📐 Multiple Formate (PNG, SVG, JPG)
- 📱 Mobile-optimiert
- 💾 Download-Funktion
- QR-Code-Generation für Speisekarten, Events
- Zeitlich begrenzte Links für Aktionen
- Passwortgeschützte Links für exklusive Inhalte
---
6. Internationalisierung
🎯 Kernfunktionalitäten & USPs
- 🌍 Mehrsprachige Unterstützung (Paraglide.js)
- 🔄 A/B-Testing Framework integriert
- 🎯 Lokalisierte User Experience
1. Umfassendes Link-Management
---
💰 Preismodell & Monetarisierung
- ✅ Kurz-URLs mit Custom Codes
- ✅ Erweiterte Metadaten (Titel, Beschreibung)
- ✅ Ablaufdatum & Click-Limits
- ✅ Passwortschutz
- ✅ Tag-System zur Organisation
- ✅ Bulk-Operationen
Freemium-Modell mit 4 Stufen:
2. Innovative Profilkarten (Alleinstellungsmerkmal!)
| Plan | Preis | Highlights | Zielgruppe |
|-----------|--------------|--------------------------------------|------------------------|
| Free | 0€ | 50 Links/Monat, 1 Team-Mitglied | Privatnutzer, Tester |
| Pro | 4,99€/Monat | 500 Links, 3 Team-Mitglieder | Freelancer, Creators |
| Team | 9,99€/Monat | Unbegrenzte Links, 10 Mitglieder | Kleine Teams |
| Team Plus | 19,99€/Monat | Alles unbegrenzt, API, Custom Domain | Agenturen, Unternehmen |
- 🎨 3-Stufen-Builder: Anfänger, Fortgeschritten, Experte
- 📦 Modulares System: Header, Content, Media, Links
- 🎭 Template Store: Vorgefertigte Designs
- 🔄 Drag & Drop Sortierung
- 👁️ Live-Preview
Monetarisierungs-Strategie:
3. Team-Kollaboration (NEU)
- ✅ Stripe-Integration für nahtlose Zahlungen
- ✅ Upgrade-Prompts an strategischen Punkten
- ✅ Feature-Gating für Premium-Funktionen
- ✅ Team-Lizenzen als Umsatztreiber
- 👥 Skalierbare Team-Größen (1-∞ Mitglieder)
- 🔐 Granulare Berechtigungen
- 📊 Geteilte Analytics
- 🏢 Workspace-Management
---
🏗️ Technische Exzellenz
4. Professionelle Analytics
Modern Tech Stack:
- 📈 Click-Tracking in Echtzeit
- 🌍 Browser & Device Distribution
- 🔗 Referrer-Analyse
- 📅 Zeitbasierte Auswertungen
- 📊 Visuelle Dashboards
- Frontend: SvelteKit 2.0 + Svelte 5 (Cutting-Edge)
- Backend: PocketBase (Embedded, Skalierbar)
- Styling: Tailwind CSS 4.0 (Neueste Version)
- Deployment: Docker + Coolify auf Hetzner VPS
- Database: SQLite via PocketBase (Performant)
- Payments: Stripe Integration
5. QR-Code-Generator
DevOps & Performance:
- 🎨 Anpassbare Farben (Schwarz, Weiß, Gold)
- 📐 Multiple Formate (PNG, SVG, JPG)
- 📱 Mobile-optimiert
- 💾 Download-Funktion
- 🚀 Automatische Deployments via Git Push
- 🔒 HTTPS-Enforced mit SSL
- 📊 Health-Monitoring
- 🎯 99.9% Uptime SLA
- ⚡ Optimierte Build-Pipeline
6. Internationalisierung
---
🎨 User Experience & Design
- 🌍 Mehrsprachige Unterstützung (Paraglide.js)
- 🔄 A/B-Testing Framework integriert
- 🎯 Lokalisierte User Experience
Stärken:
---
1. Intuitive Navigation: Klare Menüstruktur, konsistente UX
2. Dark Mode Support: Vollständige Theme-Unterstützung
3. Responsive Design: Mobile-First Approach
4. Visual Feedback: Toast-Nachrichten, Ladeanimationen
5. Accessibility: ARIA-Labels, Keyboard-Navigation
💰 Preismodell & Monetarisierung
Design-Philosophie:
Freemium-Modell mit 4 Stufen:
- Minimalistisch aber funktional
- Farbcodierte Statusanzeigen
- Konsistente Icon-Verwendung (Lucide Icons)
- Moderne Card-Based Layouts
| Plan | Preis | Highlights | Zielgruppe |
| --------- | ------------ | ------------------------------------ | ---------------------- |
| Free | 0€ | 50 Links/Monat, 1 Team-Mitglied | Privatnutzer, Tester |
| Pro | 4,99€/Monat | 500 Links, 3 Team-Mitglieder | Freelancer, Creators |
| Team | 9,99€/Monat | Unbegrenzte Links, 10 Mitglieder | Kleine Teams |
| Team Plus | 19,99€/Monat | Alles unbegrenzt, API, Custom Domain | Agenturen, Unternehmen |
---
📈 Wachstumspotenzial & Roadmap
Monetarisierungs-Strategie:
Kurzfristig (3 Monate):
- ✅ Stripe-Integration für nahtlose Zahlungen
- ✅ Upgrade-Prompts an strategischen Punkten
- ✅ Feature-Gating für Premium-Funktionen
- ✅ Team-Lizenzen als Umsatztreiber
- Custom Domains für Enterprise
- Erweiterte API-Dokumentation
- Mobile Apps (iOS/Android)
- Webhook-Integration
- Bulk-Import/Export
---
Mittelfristig (6-12 Monate):
🏗️ Technische Exzellenz
- KI-gestützte Link-Vorschläge
- Social Media Scheduling
- Advanced Analytics (Heatmaps, Funnels)
- White-Label-Lösung
- Marketplace für Templates
Modern Tech Stack:
Langfristig (12+ Monate):
- Frontend: SvelteKit 2.0 + Svelte 5 (Cutting-Edge)
- Backend: PocketBase (Embedded, Skalierbar)
- Styling: Tailwind CSS 4.0 (Neueste Version)
- Deployment: Docker + Coolify auf Hetzner VPS
- Database: SQLite via PocketBase (Performant)
- Payments: Stripe Integration
- Enterprise SSO (SAML, OAuth)
- Compliance-Features (GDPR-Tools)
- Multi-Region Deployment
- B2B-Partnerschaften
- API-First Platform
DevOps & Performance:
---
🏆 Wettbewerbsvorteile
- 🚀 Automatische Deployments via Git Push
- 🔒 HTTPS-Enforced mit SSL
- 📊 Health-Monitoring
- 🎯 99.9% Uptime SLA
- ⚡ Optimierte Build-Pipeline
Vs. Bitly:
---
✅ Günstigerer Einstiegspreis
✅ Profilkarten-Feature
✅ Bessere Team-Funktionen im niedrigen Preissegment
🎨 User Experience & Design
Vs. Linktree:
Stärken:
✅ Vollwertiges Link-Management zusätzlich zu Profilseiten
✅ Detailliertere Analytics
✅ Flexibleres Pricing
1. Intuitive Navigation: Klare Menüstruktur, konsistente UX
2. Dark Mode Support: Vollständige Theme-Unterstützung
3. Responsive Design: Mobile-First Approach
4. Visual Feedback: Toast-Nachrichten, Ladeanimationen
5. Accessibility: ARIA-Labels, Keyboard-Navigation
Vs. Rebrandly:
Design-Philosophie:
✅ Einfachere Bedienung
✅ Integrierte Profilkarten
✅ Transparenteres Pricing
- Minimalistisch aber funktional
- Farbcodierte Statusanzeigen
- Konsistente Icon-Verwendung (Lucide Icons)
- Moderne Card-Based Layouts
---
🎯 Marketing-Empfehlungen
---
1. Content Marketing
📈 Wachstumspotenzial & Roadmap
- SEO-optimierte Blogbeiträge zu Link-Management
- Video-Tutorials für Card-Builder
- Case Studies erfolgreicher Nutzer
Kurzfristig (3 Monate):
2. Social Media Strategie
- Custom Domains für Enterprise
- Erweiterte API-Dokumentation
- Mobile Apps (iOS/Android)
- Webhook-Integration
- Bulk-Import/Export
- Instagram/TikTok: Creator-fokussierte Kampagnen
- LinkedIn: B2B-Content für Teams
- Twitter/X: Feature-Updates, Tips & Tricks
Mittelfristig (6-12 Monate):
3. Partnerschaften
- KI-gestützte Link-Vorschläge
- Social Media Scheduling
- Advanced Analytics (Heatmaps, Funnels)
- White-Label-Lösung
- Marketplace für Templates
- Influencer-Kooperationen (kostenlose Pro-Accounts)
- Agentur-Partnerprogramm
- Integration mit populären Tools (Zapier, Make)
Langfristig (12+ Monate):
4. Growth Hacking
- Enterprise SSO (SAML, OAuth)
- Compliance-Features (GDPR-Tools)
- Multi-Region Deployment
- B2B-Partnerschaften
- API-First Platform
- Referral-Programm mit Belohnungen
- Freemium-to-Pro Conversion-Optimierung
- Limited-Time Offers für Team-Pläne
---
5. Brand Positioning
🏆 Wettbewerbsvorteile
- Tagline-Vorschlag: "More than links. Your digital identity."
- Kern-Message: Professionelles Link-Management trifft kreative Selbstdarstellung
- Differenzierung: Der einzige Link-Shortener mit integriertem Profile-Builder
Vs. Bitly:
---
💡 Kritische Erfolgsfaktoren
✅ Günstigerer Einstiegspreis
✅ Profilkarten-Feature
✅ Bessere Team-Funktionen im niedrigen Preissegment
Stärken:
Vs. Linktree:
- ✅ Technisch ausgereift und modern
- ✅ Klares, attraktives Pricing
- ✅ Unique Features (Cards, Templates)
- ✅ Starke Team-Funktionalität
- ✅ Skalierbare Architektur
✅ Vollwertiges Link-Management zusätzlich zu Profilseiten
✅ Detailliertere Analytics
✅ Flexibleres Pricing
Verbesserungspotenziale:
Vs. Rebrandly:
- ⚠️ Markenbekanntheit aufbauen
- ⚠️ Mobile Apps fehlen noch
- ⚠️ API-Dokumentation ausbaufähig
- ⚠️ Enterprise-Features limitiert
✅ Einfachere Bedienung
✅ Integrierte Profilkarten
✅ Transparenteres Pricing
---
🎬 Fazit
---
Uload ist eine beeindruckend durchdachte Plattform, die den gesättigten Markt der Link-Shortener durch
innovative Features wie Profilkarten und starke Team-Funktionalität neu definiert. Die technische Exzellenz,
kombiniert mit einem attraktiven Preismodell und klarer Zielgruppenansprache, positioniert Uload ideal für
nachhaltiges Wachstum.
🎯 Marketing-Empfehlungen
Kernbotschaft für Marketing:
"Uload ist nicht nur ein Link-Shortener es ist die All-in-One-Plattform für digitale Präsenz, die Links,
Profile und Teams intelligent vereint."
1. Content Marketing
Mit der richtigen Marketing-Strategie und kontinuierlicher Feature-Entwicklung hat Uload das Potenzial, sich
als führende Alternative zu etablierten Playern wie Bitly und Linktree zu etablieren, besonders im
europäischen Markt.
- SEO-optimierte Blogbeiträge zu Link-Management
- Video-Tutorials für Card-Builder
- Case Studies erfolgreicher Nutzer
2. Social Media Strategie
- Instagram/TikTok: Creator-fokussierte Kampagnen
- LinkedIn: B2B-Content für Teams
- Twitter/X: Feature-Updates, Tips & Tricks
3. Partnerschaften
- Influencer-Kooperationen (kostenlose Pro-Accounts)
- Agentur-Partnerprogramm
- Integration mit populären Tools (Zapier, Make)
4. Growth Hacking
- Referral-Programm mit Belohnungen
- Freemium-to-Pro Conversion-Optimierung
- Limited-Time Offers für Team-Pläne
5. Brand Positioning
- Tagline-Vorschlag: "More than links. Your digital identity."
- Kern-Message: Professionelles Link-Management trifft kreative Selbstdarstellung
- Differenzierung: Der einzige Link-Shortener mit integriertem Profile-Builder
---
💡 Kritische Erfolgsfaktoren
Stärken:
- ✅ Technisch ausgereift und modern
- ✅ Klares, attraktives Pricing
- ✅ Unique Features (Cards, Templates)
- ✅ Starke Team-Funktionalität
- ✅ Skalierbare Architektur
Verbesserungspotenziale:
- ⚠️ Markenbekanntheit aufbauen
- ⚠️ Mobile Apps fehlen noch
- ⚠️ API-Dokumentation ausbaufähig
- ⚠️ Enterprise-Features limitiert
---
🎬 Fazit
Uload ist eine beeindruckend durchdachte Plattform, die den gesättigten Markt der Link-Shortener durch
innovative Features wie Profilkarten und starke Team-Funktionalität neu definiert. Die technische Exzellenz,
kombiniert mit einem attraktiven Preismodell und klarer Zielgruppenansprache, positioniert Uload ideal für
nachhaltiges Wachstum.
Kernbotschaft für Marketing:
"Uload ist nicht nur ein Link-Shortener es ist die All-in-One-Plattform für digitale Präsenz, die Links,
Profile und Teams intelligent vereint."
Mit der richtigen Marketing-Strategie und kontinuierlicher Feature-Entwicklung hat Uload das Potenzial, sich
als führende Alternative zu etablierten Playern wie Bitly und Linktree zu etablieren, besonders im
europäischen Markt.
⏺ Update Todos
⎿ ☒ Analyze project structure and tech stack
☒ Examine core features and functionality
☒ Review user interface and design
☒ Analyze database schema and data models
☒ Identify unique selling points
☒ Write comprehensive marketing report
☒ Analyze project structure and tech stack
☒ Examine core features and functionality
☒ Review user interface and design
☒ Analyze database schema and data models
☒ Identify unique selling points
☒ Write comprehensive marketing report

View file

@ -1,6 +1,7 @@
# Uload - Talking Head Video Scripts
## Video Script 1: "Der Link-Shortener, der mehr kann"
**Dauer: 30-45 Sekunden**
---
@ -23,6 +24,7 @@
---
## Video Script 2: "Warum ich von Linktree zu Uload gewechselt bin"
**Dauer: 40-50 Sekunden**
---
@ -48,6 +50,7 @@
---
## Video Script 3: "Das perfekte Tool für Creator und Teams"
**Dauer: 35-45 Sekunden**
---
@ -73,6 +76,7 @@
---
## Video Script 4: "QR-Codes, die richtig was können"
**Dauer: 30-40 Sekunden**
---
@ -84,7 +88,7 @@
"Uload macht aus jedem Link einen QR-Code - in Gold, mit Passwortschutz, und Ablaufdatum!"
**[Use Cases - 12 Sek]**
"Event nur für VIPs? Passwortgeschützter QR-Code.
"Event nur für VIPs? Passwortgeschützter QR-Code.
Flash-Sale? QR-Code der nach 24 Stunden abläuft.
Limitierte Aktion? QR-Code für nur 100 Klicks!"
@ -100,6 +104,7 @@ Limitierte Aktion? QR-Code für nur 100 Klicks!"
---
## Video Script 5: "Von 0 auf Profi-Profil in 5 Minuten"
**Dauer: 35-45 Sekunden**
---
@ -131,27 +136,31 @@ QR-Code generieren - AUTOMATISCH!"
## Produktions-Tipps:
### Visuelle Elemente:
- **Screen-Recordings** der Features während du sprichst
- **Grafische Overlays** für Preisvergleiche
- **Animierte Zahlen** für Statistiken
- **Split-Screens** für Vorher-Nachher
### Ton & Musik:
- **Upbeat Background Music** (leise)
- **Sound Effects** bei Übergängen
- **Klare, enthusiastische Stimme**
### Call-to-Actions:
- **URL einblenden**: ulo.ad
- **QR-Code** zum Scannen
- **"Link in Bio"** Reference
### Hashtag-Strategie:
- #LinkInBio
- #DigitalMarketing
- #DigitalMarketing
- #CreatorTools
- #TeamWork
- #QRCode
- #Uload
- #LinkShortener
- #MarketingHacks
- #MarketingHacks

View file

@ -74,4 +74,4 @@ Fertig! Eine professionelle Profilseite mit Analytics, kurzen Links und QR-Codes
Bei Linktree hätte ich nur Links. Bei Uload hab ich ein komplettes Digital-Marketing-Tool!
Bau deine in unter 5 Minuten: ulo.ad - kostenlos starten!
Bau deine in unter 5 Minuten: ulo.ad - kostenlos starten!

View file

@ -61,7 +61,7 @@ describe('Tags Integration', () => {
testUser = await pb.collection('users').create({
email: `test-${Date.now()}@example.com`,
password: 'testpassword123',
passwordConfirm: 'testpassword123'
passwordConfirm: 'testpassword123',
});
await pb.collection('users').authWithPassword(testUser.email, 'testpassword123');
@ -73,7 +73,7 @@ describe('Tags Integration', () => {
slug: 'test-tag',
user_id: testUser.id,
color: '#3B82F6',
is_public: false
is_public: false,
});
expect(tag.name).toBe('Test Tag');
@ -84,14 +84,14 @@ describe('Tags Integration', () => {
const tag = await pb.collection('tags').create({
name: 'My Tag',
slug: 'my-tag',
user_id: testUser.id
user_id: testUser.id,
});
// Try to update with different user
const otherUser = await pb.collection('users').create({
email: `other-${Date.now()}@example.com`,
password: 'testpassword123',
passwordConfirm: 'testpassword123'
passwordConfirm: 'testpassword123',
});
await pb.collection('users').authWithPassword(otherUser.email, 'testpassword123');
@ -309,7 +309,7 @@ Sentry.init({
integrations: [Sentry.browserTracingIntegration(), Sentry.replayIntegration()],
tracesSampleRate: 1.0,
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0
replaysOnErrorSampleRate: 1.0,
});
export const handleError = Sentry.handleErrorWithSentry();
@ -326,7 +326,7 @@ export async function GET() {
const checks = {
app: 'ok',
database: 'unknown',
timestamp: new Date().toISOString()
timestamp: new Date().toISOString(),
};
try {
@ -423,7 +423,7 @@ const migrations = [
await pb.collections.update('tags', tagsCollection);
console.log('✓ Fixed tags collection rules');
}
},
},
{
version: 2,
@ -431,8 +431,8 @@ const migrations = [
up: async (pb) => {
// Add indexes for performance
// Implementation here
}
}
},
},
];
async function runMigrations() {

View file

@ -108,7 +108,7 @@ import { writable, derived } from 'svelte/store';
export const cardStore = createCardStore({
cards: new Map(),
activeCard: null,
editMode: false
editMode: false,
});
```
@ -174,8 +174,8 @@ const CardSchema = z.object({
constraints: z.object({
maxModules: z.number().max(20),
maxHTMLSize: z.number().max(100000),
maxCSSSize: z.number().max(50000)
})
maxCSSSize: z.number().max(50000),
}),
});
```
@ -194,7 +194,7 @@ const CardSchema = z.object({
const cardEventBus = {
emit: (event: string, data: any) => {},
on: (event: string, handler: Function) => {},
off: (event: string, handler: Function) => {}
off: (event: string, handler: Function) => {},
};
```
@ -222,7 +222,7 @@ export const cardsStore = {
createCard: async (config: CardConfig) => {},
updateCard: async (id: string, updates: Partial<Card>) => {},
deleteCard: async (id: string) => {},
convertCard: async (id: string, targetMode: RenderMode) => {}
convertCard: async (id: string, targetMode: RenderMode) => {},
};
```
@ -272,7 +272,7 @@ if (import.meta.env.DEV) {
inspect: (cardId: string) => {},
export: (cardId: string) => {},
import: (config: any) => {},
benchmark: () => {}
benchmark: () => {},
};
}
```

View file

@ -10,12 +10,14 @@ Successfully implemented cards display on user profile pages by creating a simpl
## Changes Made
### 1. Server-Side Data Loading (`/src/routes/p/[username]/+page.server.ts`)
- Removed complex Card type import that was causing 500 errors
- Implemented direct PocketBase query without type dependencies
- Added safe JSON parsing for card config/metadata fields
- Filter cards by `page="profile"` and `visibility="public"`
### 2. Client-Side Rendering (`/src/routes/p/[username]/+page.svelte`)
- Removed dependency on complex CardRenderer component
- Created inline simplified card rendering for three modes:
- **Beginner Mode**: Renders modules (header, content, media, links)
@ -24,6 +26,7 @@ Successfully implemented cards display on user profile pages by creating a simpl
- Added "Featured Cards" section above links
### 3. Card Management (`/src/routes/(app)/my/cards/+page.svelte`)
- Updated to load ALL user cards, not just profile ones
- Existing profile toggle functionality allows users to control visibility
- Added stats showing total cards vs cards on profile
@ -32,12 +35,14 @@ Successfully implemented cards display on user profile pages by creating a simpl
## Key Features
### Profile Page
- Cards appear in a responsive grid (1-3 columns)
- Simple module-based rendering for beginner cards
- Fallback display for advanced/expert cards
- Only shows public cards marked for profile display
### Management Page
- Checkbox to toggle "Show on Profile"
- Warning if card is not public but set for profile
- Drag-and-drop reordering still functional
@ -46,12 +51,14 @@ Successfully implemented cards display on user profile pages by creating a simpl
## Technical Approach
### Why This Works
1. **No Type Imports**: Server-side code doesn't import complex Card types
2. **Direct Database Access**: Uses PocketBase directly with JSON parsing
3. **Simplified Rendering**: Inline rendering without complex components
4. **Progressive Enhancement**: Basic display with room for improvements
### Trade-offs
- Less feature-rich display than full CardRenderer
- Advanced/Expert cards show metadata only (not full rendering)
- No template variable replacement
@ -60,11 +67,13 @@ Successfully implemented cards display on user profile pages by creating a simpl
## Future Improvements
### Short Term
1. Add client-side CardRenderer for better display
2. Implement template variable replacement
3. Add preview mode in card management
### Long Term
1. Refactor Card type system for SSR compatibility
2. Create server-safe card components
3. Implement full rendering for all card modes
@ -80,4 +89,4 @@ Successfully implemented cards display on user profile pages by creating a simpl
## Conclusion
The simplified approach successfully displays cards on profile pages while avoiding the complex type system issues. This provides a working foundation that can be enhanced incrementally without breaking production.
The simplified approach successfully displays cards on profile pages while avoiding the complex type system issues. This provides a working foundation that can be enhanced incrementally without breaking production.

View file

@ -11,6 +11,7 @@ Nach eingehender Analyse des aktuellen Projekts und dem erhaltenen Feedback bewe
## Aktuelle Situation
### Technologie-Stack
- **Frontend:** SvelteKit v2.22 mit Svelte 5.0
- **Styling:** Tailwind CSS v4.0
- **Backend:** PocketBase (aktuell)
@ -18,6 +19,7 @@ Nach eingehender Analyse des aktuellen Projekts und dem erhaltenen Feedback bewe
- **Hosting:** Separate Instanzen für Dev (localhost:8090) und Prod (pb.ulo.ad)
### Implementierte Features
- URL-Shortening mit custom Short-Codes
- User Authentication & Profile Management
- Link-Management mit Tags
@ -89,6 +91,7 @@ Nach eingehender Analyse des aktuellen Projekts und dem erhaltenen Feedback bewe
## Migrations-Strategie (falls notwendig)
### Wann der Wechsel sinnvoll wäre:
- ✅ Mehr als 10.000 aktive User
- ✅ Mehr als 1 Million Clicks/Tag
- ✅ Komplexe Business Intelligence Anforderungen
@ -96,6 +99,7 @@ Nach eingehender Analyse des aktuellen Projekts und dem erhaltenen Feedback bewe
- ✅ Geografisch verteilte Deployments
### Empfohlener Migrations-Pfad:
1. **Phase 1:** Weiter mit PocketBase (JETZT)
2. **Phase 2:** Redis-Cache für Hot-Links hinzufügen
3. **Phase 3:** Analytics in separaten Service auslagern
@ -104,6 +108,7 @@ Nach eingehender Analyse des aktuellen Projekts und dem erhaltenen Feedback bewe
## Konkrete Empfehlungen
### Kurzfristig (0-3 Monate)
1. **Bei PocketBase bleiben**
- Fokus auf Feature-Entwicklung
- User-Feedback sammeln
@ -115,6 +120,7 @@ Nach eingehender Analyse des aktuellen Projekts und dem erhaltenen Feedback bewe
- Lazy-Loading für Analytics
### Mittelfristig (3-6 Monate)
1. **Hybrid-Ansatz evaluieren**
- PocketBase für Auth & Core-Data
- Redis für Link-Resolution Cache
@ -126,6 +132,7 @@ Nach eingehender Analyse des aktuellen Projekts und dem erhaltenen Feedback bewe
- Datenbasierte Entscheidungen treffen
### Langfristig (6+ Monate)
1. **Bei nachgewiesenem Bedarf**
- Migration zu PostgreSQL planen
- Schrittweise Migration
@ -143,17 +150,17 @@ Die Kritik an PocketBase ist **teilweise berechtigt**, aber für die aktuelle Pr
## Anhang: Feature-Vergleich
| Feature | PocketBase | PostgreSQL + Stack | Bewertung |
|---------|------------|-------------------|-----------|
| Setup-Zeit | 5 Minuten | 2-3 Stunden | PocketBase ✅ |
| Auth System | Eingebaut | Lucia/Auth.js nötig | PocketBase ✅ |
| REST API | Automatisch | Prisma + tRPC/REST | PocketBase ✅ |
| Realtime | WebSockets eingebaut | Separate Lösung | PocketBase ✅ |
| File Storage | Eingebaut | S3/Cloudinary | PocketBase ✅ |
| Admin UI | Eingebaut | Eigenbau/Forest Admin | PocketBase ✅ |
| Complex Queries | Limitiert | Vollständig | PostgreSQL ✅ |
| Skalierung | Vertikal | Horizontal | PostgreSQL ✅ |
| Performance | Gut bis 100k Users | Exzellent | PostgreSQL ✅ |
| Kosten | Niedrig | Mittel-Hoch | PocketBase ✅ |
| Feature | PocketBase | PostgreSQL + Stack | Bewertung |
| --------------- | -------------------- | --------------------- | ------------- |
| Setup-Zeit | 5 Minuten | 2-3 Stunden | PocketBase ✅ |
| Auth System | Eingebaut | Lucia/Auth.js nötig | PocketBase ✅ |
| REST API | Automatisch | Prisma + tRPC/REST | PocketBase ✅ |
| Realtime | WebSockets eingebaut | Separate Lösung | PocketBase ✅ |
| File Storage | Eingebaut | S3/Cloudinary | PocketBase ✅ |
| Admin UI | Eingebaut | Eigenbau/Forest Admin | PocketBase ✅ |
| Complex Queries | Limitiert | Vollständig | PostgreSQL ✅ |
| Skalierung | Vertikal | Horizontal | PostgreSQL ✅ |
| Performance | Gut bis 100k Users | Exzellent | PostgreSQL ✅ |
| Kosten | Niedrig | Mittel-Hoch | PocketBase ✅ |
**Gesamtbewertung für aktuelles Projekt:** PocketBase 7:3 PostgreSQL
**Gesamtbewertung für aktuelles Projekt:** PocketBase 7:3 PostgreSQL

View file

@ -209,7 +209,7 @@ export function getVariant(testId: string, cookies: Cookies) {
variant = Math.random() > 0.5 ? 'A' : 'B';
cookies.set(cookieName, variant, {
path: '/',
maxAge: 60 * 60 * 24 * 30 // 30 Tage
maxAge: 60 * 60 * 24 * 30, // 30 Tage
});
}
@ -219,7 +219,7 @@ export function getVariant(testId: string, cookies: Cookies) {
// Usage in +layout.server.ts
export async function load({ cookies }) {
return {
heroVariant: getVariant('homepage-hero', cookies)
heroVariant: getVariant('homepage-hero', cookies),
};
}
```

View file

@ -68,8 +68,8 @@ export class ABTestingService {
variants: [
{ id: 'control', name: 'Original', weight: 34 },
{ id: 'value-focused', name: 'Value Proposition', weight: 33 },
{ id: 'social-proof', name: 'Social Proof First', weight: 33 }
]
{ id: 'social-proof', name: 'Social Proof First', weight: 33 },
],
});
this.tests.set('homepage-cta', {
@ -80,8 +80,8 @@ export class ABTestingService {
{ id: 'start-free', name: 'Start Free', weight: 25 },
{ id: 'try-now', name: 'Try Now', weight: 25 },
{ id: 'get-started', name: 'Get Started', weight: 25 },
{ id: 'create-link', name: 'Create Your First Link', weight: 25 }
]
{ id: 'create-link', name: 'Create Your First Link', weight: 25 },
],
});
}
@ -107,7 +107,7 @@ export class ABTestingService {
maxAge: 60 * 60 * 24 * 30,
httpOnly: true,
secure: true,
sameSite: 'lax'
sameSite: 'lax',
});
return variant;
@ -151,8 +151,8 @@ export const load: PageServerLoad = async ({ locals, cookies }) => {
// Existing data...
abTests: {
hero: heroVariant,
cta: ctaVariant
}
cta: ctaVariant,
},
};
};
```
@ -173,7 +173,7 @@ export const load: PageServerLoad = async ({ locals, cookies }) => {
const heroComponents = {
control: HeroOriginal,
'value-focused': HeroValue,
'social-proof': HeroSocial
'social-proof': HeroSocial,
};
const HeroComponent = heroComponents[data.abTests?.hero?.id || 'control'];
@ -183,7 +183,7 @@ export const load: PageServerLoad = async ({ locals, cookies }) => {
'start-free': 'Start Free - No Credit Card',
'try-now': 'Try Now',
'get-started': 'Get Started Free',
'create-link': 'Create Your First Link'
'create-link': 'Create Your First Link',
};
const ctaText = ctaTexts[data.abTests?.cta?.id || 'start-free'];
@ -204,7 +204,7 @@ export const load: PageServerLoad = async ({ locals, cookies }) => {
export const featureFlags = {
newHero: import.meta.env.PUBLIC_FEATURE_NEW_HERO === 'true',
interactiveDemo: import.meta.env.PUBLIC_FEATURE_DEMO === 'true',
pricingCalculator: import.meta.env.PUBLIC_FEATURE_CALCULATOR === 'true'
pricingCalculator: import.meta.env.PUBLIC_FEATURE_CALCULATOR === 'true',
};
// .env.local
@ -225,7 +225,7 @@ export const load: PageServerLoad = async ({ url, cookies }) => {
// Override cookie for testing
cookies.set('ab_homepage-hero', variant, {
path: '/',
maxAge: 60 * 60 * 24
maxAge: 60 * 60 * 24,
});
}
@ -259,16 +259,16 @@ export function getTimeBasedVariant(): string {
export const heroHeadlines = {
control: {
headline: 'Short Links That Work Harder',
subheadline: 'Professional URL management with real-time analytics'
subheadline: 'Professional URL management with real-time analytics',
},
benefit: {
headline: 'Save 3 Hours Per Week on Link Management',
subheadline: 'Automate your URL workflow with smart analytics'
subheadline: 'Automate your URL workflow with smart analytics',
},
social: {
headline: 'Join 10,000+ Marketers Using uLoad',
subheadline: 'The trusted URL shortener for growing brands'
}
subheadline: 'The trusted URL shortener for growing brands',
},
};
```
@ -288,23 +288,23 @@ export const heroHeadlines = {
'start-free': {
text: 'Start Free - No Credit Card',
color: 'bg-purple-600 hover:bg-purple-700',
size: 'px-8 py-4 text-lg'
size: 'px-8 py-4 text-lg',
},
'try-now': {
text: 'Try Now →',
color: 'bg-blue-600 hover:bg-blue-700',
size: 'px-6 py-3 text-base'
size: 'px-6 py-3 text-base',
},
'get-started': {
text: 'Get Started Free',
color: 'bg-gradient-to-r from-purple-600 to-blue-600',
size: 'px-8 py-4 text-lg'
size: 'px-8 py-4 text-lg',
},
'create-link': {
text: '🔗 Create Your First Link',
color: 'bg-black hover:bg-gray-800',
size: 'px-6 py-4 text-lg'
}
size: 'px-6 py-4 text-lg',
},
};
const config = configs[variant];
@ -363,7 +363,7 @@ export function trackABEvent(
window.gtag('event', 'ab_test', {
test_id: testId,
variant_id: variantId,
action: action
action: action,
});
}
@ -375,8 +375,8 @@ export function trackABEvent(
testId,
variantId,
action,
timestamp: new Date().toISOString()
})
timestamp: new Date().toISOString(),
}),
});
}
```
@ -520,7 +520,7 @@ export const load: PageServerLoad = async ({ cookies, locals }) => {
heroVariant = Math.random() > 0.5 ? 'a' : 'b';
cookies.set('ab_hero', heroVariant, {
path: '/',
maxAge: 60 * 60 * 24 * 30
maxAge: 60 * 60 * 24 * 30,
});
}
@ -528,7 +528,7 @@ export const load: PageServerLoad = async ({ cookies, locals }) => {
return {
// Existing data...
heroVariant
heroVariant,
};
};
```

View file

@ -37,7 +37,7 @@ async function getLocationFromIP(ipAddress) {
const data = await response.json();
return {
country: data.country_name || 'Unknown',
city: data.city || 'Unknown'
city: data.city || 'Unknown',
};
} catch (error) {
return { country: 'Unknown', city: 'Unknown' };

View file

@ -139,7 +139,7 @@ migrate(
{ name: 'folders', oldField: 'user', newField: 'user_id' },
{ name: 'folders', oldField: 'public', newField: 'is_public' },
{ name: 'tags', oldField: 'user', newField: 'user_id' },
{ name: 'tags', oldField: 'public', newField: 'is_public' }
{ name: 'tags', oldField: 'public', newField: 'is_public' },
];
collections.forEach(({ name, oldField, newField }) => {
@ -169,7 +169,7 @@ migrate(
{ name: 'links', fields: ['user_id', 'is_active', 'folder_id'] },
{ name: 'analytics', fields: ['link_id', 'ip_address'] },
{ name: 'folders', fields: ['user_id', 'is_public'] },
{ name: 'tags', fields: ['user_id', 'is_public'] }
{ name: 'tags', fields: ['user_id', 'is_public'] },
];
collections.forEach(({ name, fields }) => {
@ -206,7 +206,7 @@ export class DBCompatibility {
folder: 'folder_id',
active: 'is_active',
public: 'is_public',
ip: 'ip_address'
ip: 'ip_address',
};
return mapping[oldName] || oldName;
@ -236,7 +236,7 @@ export class DBCompatibility {
folder_id: 'folder',
is_active: 'active',
is_public: 'public',
ip_address: 'ip'
ip_address: 'ip',
};
const transformed = { ...data };
@ -346,7 +346,7 @@ migrate((db) => {
const updates = [
'UPDATE links SET user = user_id WHERE user_id IS NOT NULL',
'UPDATE links SET active = is_active WHERE is_active IS NOT NULL',
'UPDATE analytics SET link = link_id WHERE link_id IS NOT NULL'
'UPDATE analytics SET link = link_id WHERE link_id IS NOT NULL',
// ... weitere Updates
];
@ -390,7 +390,7 @@ export async function validateMigration() {
linksIntegrity: await checkLinksIntegrity(),
analyticsConsistency: await checkAnalyticsConsistency(),
relationsValid: await checkRelationsValid(),
performanceMetrics: await checkPerformance()
performanceMetrics: await checkPerformance(),
};
return checks;

View file

@ -8,7 +8,7 @@ PocketBase ist bereits gestartet auf: http://localhost:8090
### 1. Admin Account erstellen
Öffne im Browser: http://localhost:8090/_/
Öffne im Browser: http://localhost:8090/\_/
- Beim ersten Besuch wirst du aufgefordert einen Admin-Account zu erstellen
- Verwende sichere Credentials (nur für lokale Entwicklung)
@ -27,10 +27,12 @@ Nach dem Login als Admin:
Erstelle ein paar Test-Einträge:
**Test User:**
- Email: test@localhost
- Password: test123456
**Test Links:**
- Kurz-Code: test1
- Original URL: https://example.com
- Aktiv: Ja
@ -54,4 +56,4 @@ Die App sollte jetzt http://localhost:8090 als Backend nutzen!
## 🎉 Fertig!
Deine lokale Entwicklungsumgebung ist jetzt vollständig von Production getrennt!
Deine lokale Entwicklungsumgebung ist jetzt vollständig von Production getrennt!

View file

@ -13,7 +13,7 @@ import { PUBLIC_APP_URL } from '$env/static/public';
import type { RequestHandler } from './$types';
const stripe = new Stripe(STRIPE_SECRET_KEY, {
apiVersion: '2024-11-20'
apiVersion: '2024-11-20',
});
export const POST: RequestHandler = async ({ request, locals }) => {
@ -40,15 +40,15 @@ export const POST: RequestHandler = async ({ request, locals }) => {
name: user.name || undefined,
metadata: {
pocketbase_id: user.id,
username: user.username || ''
}
username: user.username || '',
},
});
stripeCustomerId = customer.id;
// Save customer ID for future use
await locals.pb.collection('users').update(user.id, {
stripe_customer_id: stripeCustomerId
stripe_customer_id: stripeCustomerId,
});
}
@ -63,34 +63,34 @@ export const POST: RequestHandler = async ({ request, locals }) => {
line_items: [
{
price: priceId,
quantity: 1
}
quantity: 1,
},
],
mode: 'subscription',
allow_promotion_codes: true,
subscription_data: {
metadata: {
pocketbase_user_id: user.id
}
pocketbase_user_id: user.id,
},
},
success_url: `${PUBLIC_APP_URL}/checkout/success?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${PUBLIC_APP_URL}/pricing?cancelled=true`,
locale: 'de',
metadata: {
user_id: user.id,
user_email: user.email
}
user_email: user.email,
},
});
return json({
sessionId: session.id,
url: session.url
url: session.url,
});
} catch (error) {
console.error('Stripe checkout error:', error);
return json(
{
error: 'Fehler beim Erstellen der Checkout-Session'
error: 'Fehler beim Erstellen der Checkout-Session',
},
{ status: 500 }
);
@ -107,7 +107,7 @@ import { STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET } from '$env/static/private';
import type { RequestHandler } from './$types';
const stripe = new Stripe(STRIPE_SECRET_KEY, {
apiVersion: '2024-11-20'
apiVersion: '2024-11-20',
});
export const POST: RequestHandler = async ({ request, locals }) => {
@ -149,7 +149,7 @@ export const POST: RequestHandler = async ({ request, locals }) => {
stripe_customer_id: session.customer,
stripe_subscription_id: subscription.id,
current_period_end: new Date(subscription.current_period_end * 1000).toISOString(),
subscription_interval: subscription.items.data[0].price.recurring?.interval || 'month'
subscription_interval: subscription.items.data[0].price.recurring?.interval || 'month',
});
// Reset usage counter for new subscribers
@ -159,7 +159,7 @@ export const POST: RequestHandler = async ({ request, locals }) => {
new Date().getFullYear(),
new Date().getMonth() + 1,
1
).toISOString()
).toISOString(),
});
console.log(`User ${userId} upgraded to Pro`);
@ -199,7 +199,7 @@ export const POST: RequestHandler = async ({ request, locals }) => {
// Update user subscription status
await locals.pb.collection('users').update(userId, {
subscription_status: status,
current_period_end: new Date(subscription.current_period_end * 1000).toISOString()
current_period_end: new Date(subscription.current_period_end * 1000).toISOString(),
});
console.log(`User ${userId} subscription status: ${status}`);
@ -224,7 +224,7 @@ export const POST: RequestHandler = async ({ request, locals }) => {
await locals.pb.collection('users').update(userId, {
subscription_status: 'free',
stripe_subscription_id: null,
current_period_end: null
current_period_end: null,
});
console.log(`User ${userId} downgraded to Free`);
@ -244,7 +244,7 @@ export const POST: RequestHandler = async ({ request, locals }) => {
// Mark as past_due
await locals.pb.collection('users').update(userId, {
subscription_status: 'past_due'
subscription_status: 'past_due',
});
// TODO: Send email notification to user
@ -267,7 +267,7 @@ export const POST: RequestHandler = async ({ request, locals }) => {
const user = await locals.pb.collection('users').getOne(userId);
if (user.subscription_status === 'past_due') {
await locals.pb.collection('users').update(userId, {
subscription_status: 'pro'
subscription_status: 'pro',
});
console.log(`User ${userId} reactivated after payment`);
}
@ -297,7 +297,7 @@ import { PUBLIC_APP_URL } from '$env/static/public';
import type { RequestHandler } from './$types';
const stripe = new Stripe(STRIPE_SECRET_KEY, {
apiVersion: '2024-11-20'
apiVersion: '2024-11-20',
});
export const POST: RequestHandler = async ({ locals }) => {
@ -316,7 +316,7 @@ export const POST: RequestHandler = async ({ locals }) => {
const session = await stripe.billingPortal.sessions.create({
customer: user.stripe_customer_id,
return_url: `${PUBLIC_APP_URL}/account`,
locale: 'de'
locale: 'de',
});
return json({ url: session.url });
@ -348,7 +348,7 @@ export class SubscriptionService {
if (user.subscription_status === 'past_due') {
return {
allowed: false,
reason: 'Bitte aktualisiere deine Zahlungsmethode'
reason: 'Bitte aktualisiere deine Zahlungsmethode',
};
}
@ -361,7 +361,7 @@ export class SubscriptionService {
if (linksUsed >= 10) {
return {
allowed: false,
reason: `Du hast bereits ${linksUsed} von 10 kostenlosen Links diesen Monat erstellt`
reason: `Du hast bereits ${linksUsed} von 10 kostenlosen Links diesen Monat erstellt`,
};
}
@ -377,14 +377,14 @@ export class SubscriptionService {
const currentCount = user.links_created_this_month || 0;
await this.pb.collection('users').update(userId, {
links_created_this_month: currentCount + 1
links_created_this_month: currentCount + 1,
});
// Log usage
await this.pb.collection('usage_logs').create({
user: userId,
action: 'link_created',
timestamp: new Date().toISOString()
timestamp: new Date().toISOString(),
});
}
@ -398,7 +398,7 @@ export class SubscriptionService {
await this.pb.collection('users').update(userId, {
links_created_this_month: 0,
monthly_reset_date: nextReset.toISOString()
monthly_reset_date: nextReset.toISOString(),
});
}
}
@ -416,7 +416,7 @@ export class SubscriptionService {
used: 0,
limit: 0,
unlimited: true,
daysUntilReset: 0
daysUntilReset: 0,
};
}
@ -431,7 +431,7 @@ export class SubscriptionService {
used: updatedUser.links_created_this_month || 0,
limit: 10,
unlimited: false,
daysUntilReset
daysUntilReset,
};
}
}
@ -463,7 +463,7 @@ export class SubscriptionService {
const response = await fetch('/api/stripe/checkout', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ interval })
body: JSON.stringify({ interval }),
});
if (!response.ok) {
@ -556,11 +556,11 @@ export const handle: Handle = async ({ event, resolve }) => {
return new Response(
JSON.stringify({
error: reason,
requiresUpgrade: true
requiresUpgrade: true,
}),
{
status: 403,
headers: { 'Content-Type': 'application/json' }
headers: { 'Content-Type': 'application/json' },
}
);
}
@ -587,7 +587,7 @@ export const handle: Handle = async ({ event, resolve }) => {
try {
const response = await fetch('/api/stripe/portal', {
method: 'POST'
method: 'POST',
});
const { url } = await response.json();
@ -665,7 +665,7 @@ export async function createTestSubscription(pb: PocketBase, userId: string) {
subscription_status: 'pro',
stripe_customer_id: 'cus_test_' + Date.now(),
stripe_subscription_id: 'sub_test_' + Date.now(),
current_period_end: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString()
current_period_end: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(),
});
}
@ -674,12 +674,12 @@ export async function simulateWebhook(eventType: string, data: any) {
method: 'POST',
headers: {
'stripe-signature': 'test_signature',
'Content-Type': 'application/json'
'Content-Type': 'application/json',
},
body: JSON.stringify({
type: eventType,
data: { object: data }
})
data: { object: data },
}),
});
return response;
@ -705,41 +705,41 @@ async function addStripeFields() {
name: 'subscription_status',
type: 'select',
options: {
values: ['free', 'pro', 'cancelled', 'past_due']
values: ['free', 'pro', 'cancelled', 'past_due'],
},
required: true
required: true,
},
{
name: 'stripe_customer_id',
type: 'text',
required: false
required: false,
},
{
name: 'stripe_subscription_id',
type: 'text',
required: false
required: false,
},
{
name: 'current_period_end',
type: 'date',
required: false
required: false,
},
{
name: 'links_created_this_month',
type: 'number',
min: 0,
required: true
required: true,
},
{
name: 'monthly_reset_date',
type: 'date',
required: false
}
required: false,
},
];
// Update collection
await pb.collections.update('users', {
schema: updatedSchema
schema: updatedSchema,
});
console.log('✅ Migration completed');

View file

@ -42,9 +42,7 @@ Prozess:
(pocketbase_id, username)
4. Erstellt Checkout
Session mit:
- Lokalisation
- Lokalisation
(DE/EN/IT/FR/ES) - Zahlungsmethoden
(Karte + SEPA) - Promotion Codes

View file

@ -295,7 +295,7 @@ trackLinkClick({
shortCode: 'abc123',
username: 'john',
hasPassword: false,
isExpiring: true
isExpiring: true,
});
// Track link creation
@ -304,7 +304,7 @@ trackLinkCreated({
hasPassword: true,
hasExpiry: false,
hasClickLimit: true,
isCustomCode: false
isCustomCode: false,
});
// Track authentication
@ -316,7 +316,7 @@ trackAuth('logout');
trackError({
type: 'validation',
message: 'Invalid URL format',
code: 'INVALID_URL'
code: 'INVALID_URL',
});
```
@ -351,7 +351,7 @@ Example:
// In analytics.ts
export const EVENTS = {
// ... existing events
NEW_EVENT_NAME: 'new-event-name'
NEW_EVENT_NAME: 'new-event-name',
};
// In your component
@ -359,6 +359,6 @@ import { trackEvent, EVENTS } from '$lib/analytics';
trackEvent(EVENTS.NEW_EVENT_NAME, {
property1: 'value1',
property2: 'value2'
property2: 'value2',
});
```