clerk-cost-tuning
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseClerk Cost Tuning
Clerk成本优化
Overview
概述
Understand Clerk pricing and optimize costs for your application.
了解Clerk定价并优化您应用的成本。
Prerequisites
前提条件
- Clerk account active
- Understanding of MAU (Monthly Active Users)
- Application usage patterns known
- 拥有活跃的Clerk账户
- 了解MAU(月活跃用户)概念
- 知晓应用的使用模式
Clerk Pricing Model
Clerk定价模型
Pricing Tiers (as of 2024)
定价层级(截至2024年)
| Tier | MAU Included | Price | Features |
|---|---|---|---|
| Free | 10,000 | $0 | Basic auth, 5 social providers |
| Pro | 10,000 | $25/mo | Custom domain, priority support |
| Enterprise | Custom | Custom | SSO, SLA, dedicated support |
| 层级 | 包含MAU数量 | 价格 | 功能 |
|---|---|---|---|
| 免费版 | 10,000 | $0 | 基础认证、5种社交登录提供商 |
| 专业版 | 10,000 | $25/月 | 自定义域名、优先支持 |
| 企业版 | 定制 | 定制 | SSO、服务等级协议(SLA)、专属支持 |
Per-User Pricing (after included MAU)
超出包含MAU后的按用户定价
- Pro: ~$0.02 per MAU above 10,000
- 专业版:超出10,000 MAU后,约$0.02/MAU
What Counts as MAU?
什么是MAU?
- Any user who signs in during the month
- Active session = counted
- Multiple sign-ins = counted once
- 当月内任何登录过的用户
- 存在活跃会话即被统计
- 多次登录仅统计一次
Cost Optimization Strategies
成本优化策略
Strategy 1: Reduce Unnecessary Sessions
策略1:减少不必要的会话
typescript
// lib/session-optimization.ts
import { auth } from '@clerk/nextjs/server'
// Use session efficiently - avoid creating multiple sessions
export async function getOrCreateSession() {
const { userId, sessionId } = await auth()
// Prefer existing session over creating new ones
if (sessionId) {
return { userId, sessionId, isNew: false }
}
// Only create session when absolutely needed
return { userId, sessionId: null, isNew: true }
}
// Configure session lifetime appropriately
// Clerk Dashboard > Configure > Sessions
// Longer sessions = fewer re-authenticationstypescript
// lib/session-optimization.ts
import { auth } from '@clerk/nextjs/server'
// 高效使用会话 - 避免创建多个会话
export async function getOrCreateSession() {
const { userId, sessionId } = await auth()
// 优先使用现有会话而非创建新会话
if (sessionId) {
return { userId, sessionId, isNew: false }
}
// 仅在绝对必要时创建会话
return { userId, sessionId: null, isNew: true }
}
// 合理配置会话有效期
// Clerk控制台 > 配置 > 会话
// 会话有效期越长 = 重新认证次数越少Strategy 2: Implement Guest Users
策略2:实现访客用户功能
typescript
// lib/guest-users.ts
// Use guest mode for non-essential features to reduce MAU
export function useGuestOrAuth() {
const { userId, isLoaded, isSignedIn } = useUser()
// Allow limited functionality without sign-in
const guestId = useMemo(() => {
if (typeof window === 'undefined') return null
let id = localStorage.getItem('guest_id')
if (!id) {
id = crypto.randomUUID()
localStorage.setItem('guest_id', id)
}
return id
}, [])
return {
userId: isSignedIn ? userId : null,
guestId: !isSignedIn ? guestId : null,
isGuest: !isSignedIn && !!guestId,
isLoaded
}
}
// Use guest ID for features that don't require auth
export async function savePreference(key: string, value: any) {
const { userId, guestId } = useGuestOrAuth()
if (userId) {
// Authenticated - save to user profile
await saveToUserProfile(userId, key, value)
} else if (guestId) {
// Guest - save to localStorage (no Clerk MAU cost)
localStorage.setItem(`pref_${key}`, JSON.stringify(value))
}
}typescript
// lib/guest-users.ts
// 对非核心功能使用访客模式以减少MAU统计
export function useGuestOrAuth() {
const { userId, isLoaded, isSignedIn } = useUser()
// 允许无需登录即可使用有限功能
const guestId = useMemo(() => {
if (typeof window === 'undefined') return null
let id = localStorage.getItem('guest_id')
if (!id) {
id = crypto.randomUUID()
localStorage.setItem('guest_id', id)
}
return id
}, [])
return {
userId: isSignedIn ? userId : null,
guestId: !isSignedIn ? guestId : null,
isGuest: !isSignedIn && !!guestId,
isLoaded
}
}
// 对无需认证的功能使用访客ID
export async function savePreference(key: string, value: any) {
const { userId, guestId } = useGuestOrAuth()
if (userId) {
// 已认证用户 - 保存到用户档案
await saveToUserProfile(userId, key, value)
} else if (guestId) {
// 访客用户 - 保存到localStorage(不会产生Clerk MAU成本)
localStorage.setItem(`pref_${key}`, JSON.stringify(value))
}
}Strategy 3: Defer Authentication
策略3:延迟认证时机
typescript
// Delay requiring sign-in until necessary
'use client'
import { useUser, SignInButton } from '@clerk/nextjs'
export function FeatureGate({ children, requiresAuth = false }) {
const { isSignedIn, isLoaded } = useUser()
// If feature doesn't require auth, show it
if (!requiresAuth) {
return children
}
if (!isLoaded) {
return <Skeleton />
}
if (!isSignedIn) {
return (
<div className="p-4 border rounded">
<p>Sign in to access this feature</p>
<SignInButton mode="modal">
<button className="btn">Sign In</button>
</SignInButton>
</div>
)
}
return children
}
// Usage - only count MAU when user accesses premium features
function App() {
return (
<div>
{/* Free features - no sign-in required */}
<PublicContent />
{/* Premium features - sign-in required */}
<FeatureGate requiresAuth>
<PremiumContent />
</FeatureGate>
</div>
)
}typescript
// 延迟要求用户登录的时机,直到必要时
'use client'
import { useUser, SignInButton } from '@clerk/nextjs'
export function FeatureGate({ children, requiresAuth = false }) {
const { isSignedIn, isLoaded } = useUser()
// 如果功能无需认证,直接展示
if (!requiresAuth) {
return children
}
if (!isLoaded) {
return <Skeleton />
}
if (!isSignedIn) {
return (
<div className="p-4 border rounded">
<p>请登录以访问此功能</p>
<SignInButton mode="modal">
<button className="btn">登录</button>
</SignInButton>
</div>
)
}
return children
}
// 使用方式 - 仅当用户访问付费功能时才统计MAU
function App() {
return (
<div>
{/* 免费功能 - 无需登录 */}
<PublicContent />
{/* 付费功能 - 需要登录 */}
<FeatureGate requiresAuth>
<PremiumContent />
</FeatureGate>
</div>
)
}Strategy 4: Reduce API Calls
策略4:减少API调用次数
typescript
// lib/batched-clerk.ts
import { clerkClient } from '@clerk/nextjs/server'
// Batch user lookups to reduce API calls
export async function batchGetUsers(userIds: string[]) {
if (userIds.length === 0) return []
const client = await clerkClient()
// Single API call instead of multiple getUser calls
const { data: users } = await client.users.getUserList({
userId: userIds,
limit: 100
})
return users
}
// Cache organization data
const orgCache = new Map<string, any>()
export async function getOrganization(orgId: string) {
if (orgCache.has(orgId)) {
return orgCache.get(orgId)
}
const client = await clerkClient()
const org = await client.organizations.getOrganization({ organizationId: orgId })
orgCache.set(orgId, org)
return org
}typescript
// lib/batched-clerk.ts
import { clerkClient } from '@clerk/nextjs/server'
// 批量查询用户以减少API调用次数
export async function batchGetUsers(userIds: string[]) {
if (userIds.length === 0) return []
const client = await clerkClient()
// 单次API调用替代多次getUser调用
const { data: users } = await client.users.getUserList({
userId: userIds,
limit: 100
})
return users
}
// 缓存组织数据
const orgCache = new Map<string, any>()
export async function getOrganization(orgId: string) {
if (orgCache.has(orgId)) {
return orgCache.get(orgId)
}
const client = await clerkClient()
const org = await client.organizations.getOrganization({ organizationId: orgId })
orgCache.set(orgId, org)
return org
}Strategy 5: Monitor and Alert
策略5:监控与告警
typescript
// lib/cost-monitoring.ts
import { clerkClient } from '@clerk/nextjs/server'
export async function getMonthlyUsageEstimate() {
const client = await clerkClient()
// Get unique users this month
const startOfMonth = new Date()
startOfMonth.setDate(1)
startOfMonth.setHours(0, 0, 0, 0)
const { totalCount } = await client.users.getUserList({
limit: 1,
// Note: You may need to track this yourself
})
// Estimate cost
const includedMAU = 10000 // Pro tier
const extraUsers = Math.max(0, totalCount - includedMAU)
const estimatedCost = 25 + (extraUsers * 0.02)
return {
totalUsers: totalCount,
includedMAU,
extraUsers,
estimatedCost,
percentageUsed: (totalCount / includedMAU) * 100
}
}
// Alert when approaching limits
export async function checkUsageAlerts() {
const usage = await getMonthlyUsageEstimate()
if (usage.percentageUsed > 80) {
await sendAlert(`Clerk usage at ${usage.percentageUsed}% of included MAU`)
}
}typescript
// lib/cost-monitoring.ts
import { clerkClient } from '@clerk/nextjs/server'
export async function getMonthlyUsageEstimate() {
const client = await clerkClient()
// 获取当月的独立用户数
const startOfMonth = new Date()
startOfMonth.setDate(1)
startOfMonth.setHours(0, 0, 0, 0)
const { totalCount } = await client.users.getUserList({
limit: 1,
// 注意:您可能需要自行跟踪此数据
})
// 估算成本
const includedMAU = 10000 // 专业版包含的MAU数量
const extraUsers = Math.max(0, totalCount - includedMAU)
const estimatedCost = 25 + (extraUsers * 0.02)
return {
totalUsers: totalCount,
includedMAU,
extraUsers,
estimatedCost,
percentageUsed: (totalCount / includedMAU) * 100
}
}
// 当接近限额时触发告警
export async function checkUsageAlerts() {
const usage = await getMonthlyUsageEstimate()
if (usage.percentageUsed > 80) {
await sendAlert(`Clerk使用量已达包含MAU的${usage.percentageUsed}%`)
}
}Cost Reduction Checklist
成本削减检查清单
- Review session lifetime settings (longer = fewer re-auths)
- Implement guest mode for non-essential features
- Defer authentication until necessary
- Batch API calls
- Cache user/org data aggressively
- Monitor MAU usage regularly
- Remove inactive users periodically
- Use webhooks instead of polling
- 检查会话有效期设置(有效期越长 = 重新认证次数越少)
- 为非核心功能实现访客模式
- 延迟认证时机至必要时
- 批量处理API调用
- 积极缓存用户/组织数据
- 定期监控MAU使用情况
- 定期清理不活跃用户
- 使用Webhooks替代轮询
Pricing Calculator
定价计算器
typescript
// Calculate monthly cost
function estimateMonthlyCost(
tier: 'free' | 'pro' | 'enterprise',
expectedMAU: number
): number {
switch (tier) {
case 'free':
return expectedMAU <= 10000 ? 0 : Infinity // Upgrade required
case 'pro':
const includedMAU = 10000
const basePrice = 25
const extraUsers = Math.max(0, expectedMAU - includedMAU)
return basePrice + (extraUsers * 0.02)
case 'enterprise':
return -1 // Contact sales
}
}
// Examples
console.log(estimateMonthlyCost('pro', 5000)) // $25
console.log(estimateMonthlyCost('pro', 20000)) // $225
console.log(estimateMonthlyCost('pro', 100000)) // $1825typescript
// 计算月度成本
function estimateMonthlyCost(
tier: 'free' | 'pro' | 'enterprise',
expectedMAU: number
): number {
switch (tier) {
case 'free':
return expectedMAU <= 10000 ? 0 : Infinity // 需要升级
case 'pro':
const includedMAU = 10000
const basePrice = 25
const extraUsers = Math.max(0, expectedMAU - includedMAU)
return basePrice + (extraUsers * 0.02)
case 'enterprise':
return -1 // 联系销售团队
}
}
// 示例
console.log(estimateMonthlyCost('pro', 5000)) // $25
console.log(estimateMonthlyCost('pro', 20000)) // $225
console.log(estimateMonthlyCost('pro', 100000)) // $1825Output
输出成果
- Pricing model understood
- Cost optimization strategies implemented
- Usage monitoring configured
- Budget alerts set up
- 理解Clerk定价模型
- 实施成本优化策略
- 配置使用情况监控
- 设置预算告警
Error Handling
错误处理
| Issue | Cause | Solution |
|---|---|---|
| Unexpected bill | MAU spike | Implement usage monitoring |
| Feature limitations | Free tier limits | Upgrade to Pro |
| API limits | Heavy usage | Implement caching |
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 账单超出预期 | MAU激增 | 实施使用情况监控 |
| 功能受限 | 免费版限制 | 升级至专业版 |
| API调用受限 | 使用量过大 | 实现缓存机制 |
Resources
参考资源
Next Steps
下一步
Proceed to for architecture patterns.
clerk-reference-architecture继续查看获取架构模式相关内容。
clerk-reference-architecture