Initial commit: Portfolio Website

Vollständige Next.js 15 Portfolio-Website mit:
- Blog-System mit 100+ Artikeln
- Supabase-Integration
- Responsive Design mit Tailwind CSS
- TypeScript-Konfiguration
- Testing-Setup mit Vitest und Playwright

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-01 15:07:20 +01:00
co-authored by Claude Opus 4.5
commit e1bbe5455d
315 changed files with 94124 additions and 0 deletions
@@ -0,0 +1,690 @@
# Conversion Rate Optimization (CRO)
**Meta-Description:** Conversion Rate Optimization für SaaS. Landing Pages, User Funnels, Micro-Conversions und datengetriebene Optimierung.
**Keywords:** CRO, Conversion Rate, Landing Page, User Funnel, Micro-Conversions, UX Optimization, Growth Hacking
---
## Einführung
**Conversion Rate Optimization (CRO)** maximiert den Wert bestehenden Traffics. Statt mehr Besucher zu kaufen, werden bestehende Nutzer besser konvertiert. Dieser Guide zeigt systematische Ansätze für **Landing Pages**, **Funnels** und **Micro-Conversions**.
---
## CRO Overview
```
┌─────────────────────────────────────────────────────────────┐
│ CONVERSION OPTIMIZATION FUNNEL │
├─────────────────────────────────────────────────────────────┤
│ │
│ TOFU (Top of Funnel): │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Awareness → Interest │ │
│ │ ├── Landing Page Views │ │
│ │ ├── Blog Post Reads │ │
│ │ ├── Social Media Clicks │ │
│ │ └── Target: Reduce Bounce Rate │ │
│ └─────────────────────────────────────────────────────┘ │
│ ↓ │
│ MOFU (Middle of Funnel): │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Consideration → Intent │ │
│ │ ├── Email Signups │ │
│ │ ├── Free Trial Starts │ │
│ │ ├── Demo Requests │ │
│ │ └── Target: Increase Lead Quality │ │
│ └─────────────────────────────────────────────────────┘ │
│ ↓ │
│ BOFU (Bottom of Funnel): │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Purchase → Retention │ │
│ │ ├── Trial to Paid Conversion │ │
│ │ ├── Checkout Completion │ │
│ │ ├── Upsells / Cross-sells │ │
│ │ └── Target: Maximize Revenue │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Key Metrics: │
│ ├── Conversion Rate = Conversions / Visitors × 100 │
│ ├── Micro-Conversion Rate (Email, Trial, etc.) │
│ ├── Revenue per Visitor (RPV) │
│ └── Customer Acquisition Cost (CAC) │
│ │
└─────────────────────────────────────────────────────────────┘
```
---
## Landing Page Optimization
```tsx
// components/landing/OptimizedHero.tsx
'use client';
import { useState } from 'react';
import { track } from '@/lib/analytics';
interface HeroProps {
headline: string;
subheadline: string;
ctaText: string;
ctaUrl: string;
trustBadges: string[];
videoUrl?: string;
}
export function OptimizedHero({
headline,
subheadline,
ctaText,
ctaUrl,
trustBadges,
videoUrl
}: HeroProps) {
const [videoPlaying, setVideoPlaying] = useState(false);
const handleCTAClick = () => {
track('hero_cta_clicked', {
headline,
ctaText
});
};
const handleVideoPlay = () => {
setVideoPlaying(true);
track('hero_video_played');
};
return (
<section className="relative py-20 lg:py-32">
<div className="container mx-auto px-4">
<div className="grid lg:grid-cols-2 gap-12 items-center">
{/* Copy Side */}
<div className="space-y-6">
{/* Social Proof Mini */}
<div className="flex items-center gap-2 text-sm text-gray-600">
<span className="flex -space-x-2">
{[1, 2, 3, 4, 5].map(i => (
<img
key={i}
src={`/avatars/${i}.jpg`}
alt=""
className="w-8 h-8 rounded-full border-2 border-white"
/>
))}
</span>
<span>Join 10,000+ happy customers</span>
</div>
{/* Headline - Max 6 words */}
<h1 className="text-4xl lg:text-6xl font-bold leading-tight">
{headline}
</h1>
{/* Subheadline - Benefits focused */}
<p className="text-xl text-gray-600 max-w-lg">
{subheadline}
</p>
{/* CTA Group */}
<div className="flex flex-col sm:flex-row gap-4">
<a
href={ctaUrl}
onClick={handleCTAClick}
className="btn-primary text-lg px-8 py-4 text-center"
>
{ctaText}
</a>
<span className="text-sm text-gray-500 self-center">
No credit card required
</span>
</div>
{/* Trust Badges */}
<div className="flex flex-wrap gap-4 pt-4">
{trustBadges.map((badge, i) => (
<div
key={i}
className="flex items-center gap-2 text-sm text-gray-600"
>
<CheckIcon className="w-5 h-5 text-green-500" />
{badge}
</div>
))}
</div>
</div>
{/* Visual Side */}
<div className="relative">
{videoUrl ? (
<VideoPlayer
url={videoUrl}
onPlay={handleVideoPlay}
playing={videoPlaying}
/>
) : (
<img
src="/hero-image.webp"
alt="Product screenshot"
className="rounded-lg shadow-2xl"
/>
)}
</div>
</div>
</div>
</section>
);
}
```
```tsx
// components/landing/SocialProof.tsx
interface Testimonial {
quote: string;
author: string;
role: string;
company: string;
avatar: string;
logo: string;
metric?: string;
}
export function SocialProofSection({
testimonials
}: {
testimonials: Testimonial[]
}) {
return (
<section className="py-20 bg-gray-50">
<div className="container mx-auto px-4">
{/* Logos Bar */}
<div className="text-center mb-12">
<p className="text-sm text-gray-500 mb-6">
Trusted by leading companies
</p>
<div className="flex flex-wrap justify-center items-center gap-8 opacity-60">
{testimonials.map((t, i) => (
<img
key={i}
src={t.logo}
alt={t.company}
className="h-8 grayscale hover:grayscale-0 transition"
/>
))}
</div>
</div>
{/* Testimonials Grid */}
<div className="grid md:grid-cols-3 gap-8">
{testimonials.map((testimonial, i) => (
<TestimonialCard key={i} {...testimonial} />
))}
</div>
{/* Aggregate Stats */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-8 mt-16 text-center">
<Stat value="10,000+" label="Active Users" />
<Stat value="99.9%" label="Uptime" />
<Stat value="4.9/5" label="Customer Rating" />
<Stat value="24/7" label="Support" />
</div>
</div>
</section>
);
}
function TestimonialCard({
quote,
author,
role,
company,
avatar,
metric
}: Testimonial) {
return (
<div className="bg-white p-6 rounded-xl shadow-sm">
{metric && (
<div className="text-3xl font-bold text-blue-600 mb-4">
{metric}
</div>
)}
<blockquote className="text-gray-700 mb-4">
"{quote}"
</blockquote>
<div className="flex items-center gap-3">
<img
src={avatar}
alt={author}
className="w-12 h-12 rounded-full"
/>
<div>
<div className="font-semibold">{author}</div>
<div className="text-sm text-gray-500">{role}, {company}</div>
</div>
</div>
</div>
);
}
```
---
## Funnel Tracking
```typescript
// lib/cro/funnel.ts
export interface FunnelStep {
id: string;
name: string;
pagePattern: string | RegExp;
requiredEvents?: string[];
}
export interface FunnelConfig {
id: string;
name: string;
steps: FunnelStep[];
}
export const signupFunnel: FunnelConfig = {
id: 'signup',
name: 'Signup Funnel',
steps: [
{ id: 'landing', name: 'Landing Page', pagePattern: '/' },
{ id: 'pricing', name: 'Pricing Page', pagePattern: '/pricing' },
{ id: 'signup-start', name: 'Signup Started', pagePattern: '/signup', requiredEvents: ['signup_form_viewed'] },
{ id: 'signup-complete', name: 'Signup Complete', pagePattern: '/welcome', requiredEvents: ['signup_completed'] },
{ id: 'onboarding', name: 'Onboarding', pagePattern: '/onboarding' },
{ id: 'activated', name: 'Activated', pagePattern: '/dashboard', requiredEvents: ['first_action_completed'] }
]
};
// Funnel Analytics
export async function getFunnelAnalytics(
funnelId: string,
startDate: Date,
endDate: Date
): Promise<FunnelAnalytics> {
const funnel = funnels.get(funnelId);
if (!funnel) throw new Error(`Funnel ${funnelId} not found`);
const stepData = await Promise.all(
funnel.steps.map(async (step, index) => {
const count = await getStepCount(step, startDate, endDate);
const previousCount = index > 0
? await getStepCount(funnel.steps[index - 1], startDate, endDate)
: count;
return {
step: step.name,
count,
conversionRate: previousCount > 0 ? (count / previousCount) * 100 : 100,
dropoffRate: previousCount > 0 ? ((previousCount - count) / previousCount) * 100 : 0
};
})
);
const overallConversion = stepData[0].count > 0
? (stepData[stepData.length - 1].count / stepData[0].count) * 100
: 0;
return {
funnel: funnel.name,
period: { start: startDate, end: endDate },
steps: stepData,
overallConversion
};
}
```
```tsx
// components/analytics/FunnelVisualization.tsx
'use client';
interface FunnelStep {
step: string;
count: number;
conversionRate: number;
dropoffRate: number;
}
export function FunnelVisualization({ steps }: { steps: FunnelStep[] }) {
const maxCount = Math.max(...steps.map(s => s.count));
return (
<div className="space-y-4">
{steps.map((step, index) => {
const width = (step.count / maxCount) * 100;
const isLastStep = index === steps.length - 1;
return (
<div key={step.step} className="relative">
{/* Step Bar */}
<div className="flex items-center gap-4">
<div className="w-32 text-sm font-medium">{step.step}</div>
<div className="flex-1 h-12 bg-gray-100 rounded-lg overflow-hidden">
<div
className="h-full bg-blue-500 flex items-center justify-end px-4 transition-all"
style={{ width: `${width}%` }}
>
<span className="text-white font-semibold">
{step.count.toLocaleString()}
</span>
</div>
</div>
<div className="w-20 text-right">
<span className={`text-sm font-medium ${
step.conversionRate >= 50 ? 'text-green-600' : 'text-red-600'
}`}>
{step.conversionRate.toFixed(1)}%
</span>
</div>
</div>
{/* Dropoff Indicator */}
{!isLastStep && step.dropoffRate > 0 && (
<div className="ml-32 pl-4 py-2 text-sm text-red-500">
{step.dropoffRate.toFixed(1)}% dropoff ({Math.round(step.count * step.dropoffRate / 100).toLocaleString()} users)
</div>
)}
</div>
);
})}
</div>
);
}
```
---
## Micro-Conversions
```typescript
// lib/cro/micro-conversions.ts
export const microConversions = {
engagement: [
{ id: 'scroll_depth_50', name: 'Scrolled 50%', weight: 1 },
{ id: 'scroll_depth_90', name: 'Scrolled 90%', weight: 2 },
{ id: 'time_on_page_60s', name: '60s on page', weight: 2 },
{ id: 'video_played', name: 'Video Played', weight: 3 },
{ id: 'video_completed', name: 'Video Completed', weight: 5 }
],
intent: [
{ id: 'pricing_viewed', name: 'Viewed Pricing', weight: 5 },
{ id: 'feature_comparison', name: 'Compared Features', weight: 4 },
{ id: 'faq_expanded', name: 'Expanded FAQ', weight: 2 },
{ id: 'live_chat_started', name: 'Started Live Chat', weight: 8 }
],
lead: [
{ id: 'email_captured', name: 'Email Signup', weight: 10 },
{ id: 'demo_requested', name: 'Demo Request', weight: 15 },
{ id: 'trial_started', name: 'Trial Started', weight: 20 }
]
};
// Calculate Lead Score
export function calculateLeadScore(events: string[]): number {
let score = 0;
Object.values(microConversions).flat().forEach(conversion => {
if (events.includes(conversion.id)) {
score += conversion.weight;
}
});
return score;
}
// hooks/useMicroConversions.ts
'use client';
import { useEffect, useCallback } from 'react';
import { track } from '@/lib/analytics';
export function useMicroConversions() {
// Scroll Depth Tracking
useEffect(() => {
let maxScroll = 0;
const milestones = [25, 50, 75, 90];
const triggered = new Set<number>();
const handleScroll = () => {
const scrollHeight = document.documentElement.scrollHeight - window.innerHeight;
const scrollPercent = Math.round((window.scrollY / scrollHeight) * 100);
if (scrollPercent > maxScroll) {
maxScroll = scrollPercent;
milestones.forEach(milestone => {
if (scrollPercent >= milestone && !triggered.has(milestone)) {
triggered.add(milestone);
track(`scroll_depth_${milestone}`);
}
});
}
};
window.addEventListener('scroll', handleScroll, { passive: true });
return () => window.removeEventListener('scroll', handleScroll);
}, []);
// Time on Page Tracking
useEffect(() => {
const milestones = [30, 60, 120, 300];
let elapsed = 0;
const interval = setInterval(() => {
elapsed += 1;
if (milestones.includes(elapsed)) {
track(`time_on_page_${elapsed}s`);
}
}, 1000);
return () => clearInterval(interval);
}, []);
// Track specific actions
const trackMicro = useCallback((eventId: string, metadata?: Record<string, unknown>) => {
track(eventId, metadata);
}, []);
return { trackMicro };
}
```
---
## Form Optimization
```tsx
// components/forms/OptimizedSignupForm.tsx
'use client';
import { useState, useEffect } from 'react';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { track } from '@/lib/analytics';
const signupSchema = z.object({
email: z.string().email('Please enter a valid email'),
password: z.string().min(8, 'Password must be at least 8 characters'),
name: z.string().min(2, 'Name is required')
});
type SignupData = z.infer<typeof signupSchema>;
export function OptimizedSignupForm() {
const [step, setStep] = useState(1);
const [startTime] = useState(Date.now());
const {
register,
handleSubmit,
formState: { errors, isSubmitting },
watch
} = useForm<SignupData>({
resolver: zodResolver(signupSchema),
mode: 'onBlur'
});
// Track form interactions
useEffect(() => {
track('signup_form_viewed');
}, []);
const email = watch('email');
useEffect(() => {
if (email && email.includes('@')) {
track('signup_email_entered');
}
}, [email]);
const onSubmit = async (data: SignupData) => {
const timeToComplete = Date.now() - startTime;
track('signup_form_submitted', {
timeToComplete,
step
});
try {
await createAccount(data);
track('signup_completed', { timeToComplete });
} catch (error) {
track('signup_error', { error: (error as Error).message });
}
};
// Field focus tracking
const trackFieldFocus = (field: string) => {
track('form_field_focused', { field });
};
return (
<form onSubmit={handleSubmit(onSubmit)} className="space-y-6">
{/* Progress Indicator */}
<div className="flex gap-2 mb-6">
{[1, 2, 3].map(s => (
<div
key={s}
className={`h-1 flex-1 rounded ${
s <= step ? 'bg-blue-600' : 'bg-gray-200'
}`}
/>
))}
</div>
{/* Step 1: Email */}
{step >= 1 && (
<div>
<label className="block text-sm font-medium mb-2">
Work Email
</label>
<input
type="email"
{...register('email')}
onFocus={() => trackFieldFocus('email')}
className="w-full px-4 py-3 border rounded-lg focus:ring-2 focus:ring-blue-500"
placeholder="you@company.com"
autoComplete="email"
/>
{errors.email && (
<p className="text-red-500 text-sm mt-1">{errors.email.message}</p>
)}
</div>
)}
{/* Step 2: Password */}
{step >= 2 && (
<div>
<label className="block text-sm font-medium mb-2">
Create Password
</label>
<input
type="password"
{...register('password')}
onFocus={() => trackFieldFocus('password')}
className="w-full px-4 py-3 border rounded-lg"
placeholder="8+ characters"
/>
{errors.password && (
<p className="text-red-500 text-sm mt-1">{errors.password.message}</p>
)}
{/* Password Strength */}
<PasswordStrengthMeter password={watch('password') || ''} />
</div>
)}
{/* CTA Button */}
<button
type={step < 2 ? 'button' : 'submit'}
onClick={() => step < 2 && setStep(step + 1)}
disabled={isSubmitting}
className="w-full py-4 bg-blue-600 text-white rounded-lg font-semibold hover:bg-blue-700 disabled:opacity-50"
>
{isSubmitting ? 'Creating account...' : step < 2 ? 'Continue' : 'Create Free Account'}
</button>
{/* Trust Signals */}
<div className="flex items-center justify-center gap-4 text-sm text-gray-500">
<span className="flex items-center gap-1">
<LockIcon className="w-4 h-4" />
Secure
</span>
<span>No credit card required</span>
<span>Cancel anytime</span>
</div>
</form>
);
}
```
---
## CRO Checklist
| Element | Best Practice |
|---------|--------------|
| **Headline** | Clear value proposition, max 6 words |
| **CTA** | Action-oriented, contrasting color |
| **Social Proof** | Numbers, logos, testimonials |
| **Trust Signals** | Security badges, guarantees |
| **Forms** | Progressive, minimal fields |
| **Mobile** | Touch-friendly, fast loading |
| **Urgency** | Scarcity, limited time (honest!) |
| **Exit Intent** | Offer value, not desperation |
---
## Fazit
Conversion Rate Optimization erfordert:
1. **Datenanalyse**: Funnels und Micro-Conversions tracken
2. **User Research**: Verstehen warum Nutzer konvertieren
3. **Testing**: Systematisches A/B Testing
4. **Iteration**: Kontinuierliche Verbesserung
Kleine Verbesserungen summieren sich zu großen Ergebnissen.
---
## Bildprompts
1. "Conversion funnel visualization, colorful stages with dropoff points"
2. "Landing page wireframe with CRO annotations, best practices highlighted"
3. "A/B test results dashboard, winner variant celebration"
---
## Quellen
- [CXL Conversion Optimization Guide](https://cxl.com/blog/conversion-optimization-guide/)
- [Unbounce Landing Page Best Practices](https://unbounce.com/landing-page-articles/)
- [Hotjar CRO Resources](https://www.hotjar.com/conversion-rate-optimization/)
- [Neil Patel CRO Guide](https://neilpatel.com/blog/conversion-rate-optimization/)