Files
Portfolio/blog-posts/83-saas-pricing-strategies.md
damjan_savicandClaude Opus 4.5 e1bbe5455d 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>
2026-02-01 15:07:20 +01:00

612 lines
20 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# SaaS Pricing Strategies
**Meta-Description:** SaaS Pricing Modelle implementieren. Freemium, Tiered Pricing, Usage-Based und Value-Based Pricing mit Stripe Integration.
**Keywords:** SaaS Pricing, Freemium, Tiered Pricing, Usage-Based, Value Pricing, Stripe, Subscription, Revenue Model
---
## Einführung
**Pricing** ist einer der wichtigsten Hebel für SaaS-Wachstum. Das richtige **Pricing-Modell** kann Revenue verdoppeln ohne zusätzliche Kunden. Dieser Guide zeigt verschiedene Strategien und deren technische Implementation.
---
## Pricing Models Overview
```
┌─────────────────────────────────────────────────────────────┐
│ SAAS PRICING MODELS │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. FLAT-RATE PRICING: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ $49/month - All features included │ │
│ │ ✓ Simple to understand │ │
│ │ ✗ Limited upsell potential │ │
│ │ Example: Basecamp │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 2. TIERED PRICING: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Starter Pro Enterprise │ │
│ │ $19/mo $49/mo $199/mo │ │
│ │ 5 users 25 users Unlimited │ │
│ │ ✓ Clear upgrade path │ │
│ │ ✓ Captures different segments │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 3. USAGE-BASED (Pay-as-you-go): │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ $0.01 per API call / $0.10 per GB │ │
│ │ ✓ Low barrier to entry │ │
│ │ ✓ Revenue scales with customer success │ │
│ │ ✗ Unpredictable revenue │ │
│ │ Example: AWS, Twilio │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 4. FREEMIUM: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Free tier + Premium tiers │ │
│ │ ✓ Viral growth potential │ │
│ │ ✓ Low friction acquisition │ │
│ │ ✗ High support costs │ │
│ │ Example: Slack, Dropbox │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 5. PER-SEAT PRICING: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ $10/user/month │ │
│ │ ✓ Predictable, easy to understand │ │
│ │ ✓ Grows with customer's team │ │
│ │ Example: Notion, Linear │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
```
---
## Pricing Configuration
```typescript
// lib/pricing/config.ts
export interface PricingTier {
id: string;
name: string;
description: string;
monthlyPrice: number;
yearlyPrice: number;
features: Feature[];
limits: TierLimits;
highlighted?: boolean;
ctaText: string;
stripeMonthlyPriceId: string;
stripeYearlyPriceId: string;
}
export interface Feature {
name: string;
included: boolean;
limit?: string;
tooltip?: string;
}
export interface TierLimits {
users: number;
projects: number;
storage: number; // in GB
apiCalls: number;
customDomains: number;
support: 'community' | 'email' | 'priority' | 'dedicated';
}
export const pricingTiers: PricingTier[] = [
{
id: 'free',
name: 'Free',
description: 'For individuals getting started',
monthlyPrice: 0,
yearlyPrice: 0,
features: [
{ name: 'Up to 3 projects', included: true },
{ name: 'Basic analytics', included: true },
{ name: 'Community support', included: true },
{ name: 'API access', included: false },
{ name: 'Custom domains', included: false },
{ name: 'Team collaboration', included: false }
],
limits: {
users: 1,
projects: 3,
storage: 1,
apiCalls: 1000,
customDomains: 0,
support: 'community'
},
ctaText: 'Get Started Free',
stripeMonthlyPriceId: '',
stripeYearlyPriceId: ''
},
{
id: 'pro',
name: 'Pro',
description: 'For professionals and small teams',
monthlyPrice: 29,
yearlyPrice: 290, // 2 months free
features: [
{ name: 'Unlimited projects', included: true },
{ name: 'Advanced analytics', included: true },
{ name: 'Email support', included: true },
{ name: 'API access', included: true, limit: '10k calls/mo' },
{ name: 'Custom domains', included: true, limit: '3 domains' },
{ name: 'Team collaboration', included: true, limit: '5 members' }
],
limits: {
users: 5,
projects: -1, // unlimited
storage: 50,
apiCalls: 10000,
customDomains: 3,
support: 'email'
},
highlighted: true,
ctaText: 'Start Free Trial',
stripeMonthlyPriceId: 'price_pro_monthly',
stripeYearlyPriceId: 'price_pro_yearly'
},
{
id: 'enterprise',
name: 'Enterprise',
description: 'For large teams with advanced needs',
monthlyPrice: 99,
yearlyPrice: 990,
features: [
{ name: 'Unlimited everything', included: true },
{ name: 'Custom analytics', included: true },
{ name: 'Dedicated support', included: true },
{ name: 'API access', included: true, limit: 'Unlimited' },
{ name: 'Custom domains', included: true, limit: 'Unlimited' },
{ name: 'Team collaboration', included: true, limit: 'Unlimited' },
{ name: 'SSO/SAML', included: true },
{ name: 'SLA guarantee', included: true }
],
limits: {
users: -1,
projects: -1,
storage: -1,
apiCalls: -1,
customDomains: -1,
support: 'dedicated'
},
ctaText: 'Contact Sales',
stripeMonthlyPriceId: 'price_enterprise_monthly',
stripeYearlyPriceId: 'price_enterprise_yearly'
}
];
// Check if feature is available
export function hasFeature(
tierId: string,
featureName: string
): boolean {
const tier = pricingTiers.find(t => t.id === tierId);
if (!tier) return false;
const feature = tier.features.find(f => f.name === featureName);
return feature?.included ?? false;
}
// Check limits
export function checkLimit(
tierId: string,
limitKey: keyof TierLimits,
currentUsage: number
): { allowed: boolean; limit: number; usage: number } {
const tier = pricingTiers.find(t => t.id === tierId);
if (!tier) return { allowed: false, limit: 0, usage: currentUsage };
const limit = tier.limits[limitKey];
return {
allowed: limit === -1 || currentUsage < limit,
limit,
usage: currentUsage
};
}
```
---
## Pricing Component
```tsx
// components/pricing/PricingTable.tsx
'use client';
import { useState } from 'react';
import { pricingTiers, PricingTier } from '@/lib/pricing/config';
import { CheckIcon, XIcon } from 'lucide-react';
import { track } from '@/lib/analytics';
export function PricingTable() {
const [billingPeriod, setBillingPeriod] = useState<'monthly' | 'yearly'>('monthly');
const handleToggle = () => {
const newPeriod = billingPeriod === 'monthly' ? 'yearly' : 'monthly';
setBillingPeriod(newPeriod);
track('pricing_period_changed', { period: newPeriod });
};
return (
<section className="py-20">
<div className="container mx-auto px-4">
{/* Header */}
<div className="text-center mb-12">
<h2 className="text-4xl font-bold mb-4">
Simple, transparent pricing
</h2>
<p className="text-xl text-gray-600">
No hidden fees. Cancel anytime.
</p>
{/* Billing Toggle */}
<div className="flex items-center justify-center gap-4 mt-8">
<span className={billingPeriod === 'monthly' ? 'font-semibold' : 'text-gray-500'}>
Monthly
</span>
<button
onClick={handleToggle}
className="relative w-14 h-7 bg-gray-200 rounded-full transition"
aria-label="Toggle billing period"
>
<span
className={`absolute w-5 h-5 bg-blue-600 rounded-full top-1 transition-all ${
billingPeriod === 'yearly' ? 'left-8' : 'left-1'
}`}
/>
</button>
<span className={billingPeriod === 'yearly' ? 'font-semibold' : 'text-gray-500'}>
Yearly
<span className="ml-2 text-green-600 text-sm">Save 17%</span>
</span>
</div>
</div>
{/* Pricing Cards */}
<div className="grid md:grid-cols-3 gap-8 max-w-5xl mx-auto">
{pricingTiers.map((tier) => (
<PricingCard
key={tier.id}
tier={tier}
billingPeriod={billingPeriod}
/>
))}
</div>
{/* FAQ Link */}
<div className="text-center mt-12">
<p className="text-gray-600">
Have questions?{' '}
<a href="/faq" className="text-blue-600 hover:underline">
Check our FAQ
</a>{' '}
or{' '}
<a href="/contact" className="text-blue-600 hover:underline">
contact us
</a>
</p>
</div>
</div>
</section>
);
}
function PricingCard({
tier,
billingPeriod
}: {
tier: PricingTier;
billingPeriod: 'monthly' | 'yearly';
}) {
const price = billingPeriod === 'monthly' ? tier.monthlyPrice : tier.yearlyPrice;
const pricePerMonth = billingPeriod === 'yearly' ? Math.round(price / 12) : price;
const handleCTAClick = () => {
track('pricing_cta_clicked', {
tier: tier.id,
billingPeriod,
price
});
};
return (
<div
className={`relative rounded-2xl p-8 ${
tier.highlighted
? 'bg-blue-600 text-white ring-4 ring-blue-600 ring-offset-2'
: 'bg-white border border-gray-200'
}`}
>
{tier.highlighted && (
<span className="absolute -top-4 left-1/2 -translate-x-1/2 bg-yellow-400 text-yellow-900 px-4 py-1 rounded-full text-sm font-semibold">
Most Popular
</span>
)}
<h3 className="text-2xl font-bold">{tier.name}</h3>
<p className={`mt-2 ${tier.highlighted ? 'text-blue-100' : 'text-gray-600'}`}>
{tier.description}
</p>
{/* Price */}
<div className="mt-6">
<span className="text-5xl font-bold">
${pricePerMonth}
</span>
<span className={tier.highlighted ? 'text-blue-100' : 'text-gray-500'}>
/month
</span>
{billingPeriod === 'yearly' && price > 0 && (
<p className={`text-sm mt-1 ${tier.highlighted ? 'text-blue-100' : 'text-gray-500'}`}>
Billed ${price} annually
</p>
)}
</div>
{/* CTA */}
<button
onClick={handleCTAClick}
className={`w-full mt-8 py-3 rounded-lg font-semibold transition ${
tier.highlighted
? 'bg-white text-blue-600 hover:bg-blue-50'
: 'bg-blue-600 text-white hover:bg-blue-700'
}`}
>
{tier.ctaText}
</button>
{/* Features */}
<ul className="mt-8 space-y-4">
{tier.features.map((feature, index) => (
<li key={index} className="flex items-start gap-3">
{feature.included ? (
<CheckIcon className={`w-5 h-5 flex-shrink-0 ${
tier.highlighted ? 'text-blue-200' : 'text-green-500'
}`} />
) : (
<XIcon className="w-5 h-5 flex-shrink-0 text-gray-400" />
)}
<span className={!feature.included ? 'text-gray-400' : ''}>
{feature.name}
{feature.limit && (
<span className={`text-sm ${tier.highlighted ? 'text-blue-200' : 'text-gray-500'}`}>
{' '}({feature.limit})
</span>
)}
</span>
</li>
))}
</ul>
</div>
);
}
```
---
## Usage-Based Pricing
```typescript
// lib/pricing/usage.ts
export interface UsageMetric {
id: string;
name: string;
unit: string;
pricePerUnit: number;
freeAllowance: number;
tiers?: UsageTier[];
}
export interface UsageTier {
upTo: number;
pricePerUnit: number;
}
export const usageMetrics: UsageMetric[] = [
{
id: 'api_calls',
name: 'API Calls',
unit: 'request',
pricePerUnit: 0.0001, // $0.0001 per call
freeAllowance: 10000,
tiers: [
{ upTo: 100000, pricePerUnit: 0.0001 },
{ upTo: 1000000, pricePerUnit: 0.00008 },
{ upTo: Infinity, pricePerUnit: 0.00005 }
]
},
{
id: 'storage',
name: 'Storage',
unit: 'GB',
pricePerUnit: 0.10,
freeAllowance: 5,
tiers: [
{ upTo: 100, pricePerUnit: 0.10 },
{ upTo: 1000, pricePerUnit: 0.08 },
{ upTo: Infinity, pricePerUnit: 0.05 }
]
},
{
id: 'bandwidth',
name: 'Bandwidth',
unit: 'GB',
pricePerUnit: 0.05,
freeAllowance: 10
}
];
export function calculateUsageCost(
metricId: string,
usage: number
): number {
const metric = usageMetrics.find(m => m.id === metricId);
if (!metric) return 0;
// Subtract free allowance
const billableUsage = Math.max(0, usage - metric.freeAllowance);
if (billableUsage === 0) return 0;
// Calculate with tiers if available
if (metric.tiers) {
let remaining = billableUsage;
let cost = 0;
let previousLimit = 0;
for (const tier of metric.tiers) {
const tierUsage = Math.min(remaining, tier.upTo - previousLimit);
cost += tierUsage * tier.pricePerUnit;
remaining -= tierUsage;
previousLimit = tier.upTo;
if (remaining <= 0) break;
}
return cost;
}
return billableUsage * metric.pricePerUnit;
}
export function calculateTotalUsageCost(
usage: Record<string, number>
): { breakdown: Record<string, number>; total: number } {
const breakdown: Record<string, number> = {};
let total = 0;
for (const [metricId, amount] of Object.entries(usage)) {
const cost = calculateUsageCost(metricId, amount);
breakdown[metricId] = cost;
total += cost;
}
return { breakdown, total };
}
```
---
## Stripe Integration
```typescript
// lib/stripe/checkout.ts
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: '2024-06-20'
});
export async function createCheckoutSession(
customerId: string,
priceId: string,
options?: {
trialDays?: number;
successUrl?: string;
cancelUrl?: string;
couponId?: string;
}
): Promise<string> {
const session = await stripe.checkout.sessions.create({
customer: customerId,
mode: 'subscription',
payment_method_types: ['card'],
line_items: [
{
price: priceId,
quantity: 1
}
],
subscription_data: {
trial_period_days: options?.trialDays || 14,
...(options?.couponId && {
coupon: options.couponId
})
},
success_url: options?.successUrl || `${process.env.NEXT_PUBLIC_APP_URL}/checkout/success?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: options?.cancelUrl || `${process.env.NEXT_PUBLIC_APP_URL}/pricing`,
allow_promotion_codes: true
});
return session.url!;
}
export async function createBillingPortalSession(
customerId: string
): Promise<string> {
const session = await stripe.billingPortal.sessions.create({
customer: customerId,
return_url: `${process.env.NEXT_PUBLIC_APP_URL}/settings/billing`
});
return session.url;
}
// Usage-based billing
export async function reportUsage(
subscriptionItemId: string,
quantity: number,
timestamp?: number
): Promise<void> {
await stripe.subscriptionItems.createUsageRecord(subscriptionItemId, {
quantity,
timestamp: timestamp || Math.floor(Date.now() / 1000),
action: 'increment'
});
}
```
---
## Pricing Psychology
| Tactic | Implementation |
|--------|---------------|
| **Anchoring** | Show highest tier first or middle highlighted |
| **Decoy Effect** | Add a tier that makes others look better |
| **Charm Pricing** | $29 instead of $30 |
| **Annual Discount** | 2 months free for yearly |
| **Social Proof** | "Most Popular" badge |
| **Loss Aversion** | "Limited time offer" |
| **Value Framing** | Compare to alternatives |
---
## Fazit
SaaS Pricing Strategien erfordern:
1. **Know Your Customers**: Segmente verstehen
2. **Value-Based**: Preis an Kundennutzen koppeln
3. **Test & Iterate**: A/B Testing für Pricing
4. **Clear Communication**: Transparente Preisgestaltung
Pricing ist nie "fertig" kontinuierlich optimieren.
---
## Bildprompts
1. "SaaS pricing page design, three-tier comparison with highlighted option"
2. "Usage-based pricing calculator interface, slider and cost preview"
3. "Pricing psychology infographic, anchoring and decoy effects"
---
## Quellen
- [Stripe Pricing Guide](https://stripe.com/guides/atlas/saas-pricing)
- [ProfitWell Pricing Strategy](https://www.profitwell.com/recur/all/pricing-strategy)
- [OpenView Pricing](https://openviewpartners.com/blog/saas-pricing-models/)
- [Price Intelligently](https://www.priceintelligently.com/)