clerk-cost-tuning

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Clerk 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年)

TierMAU IncludedPriceFeatures
Free10,000$0Basic auth, 5 social providers
Pro10,000$25/moCustom domain, priority support
EnterpriseCustomCustomSSO, 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-authentications
typescript
// 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)) // $1825
typescript
// 计算月度成本
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)) // $1825

Output

输出成果

  • Pricing model understood
  • Cost optimization strategies implemented
  • Usage monitoring configured
  • Budget alerts set up
  • 理解Clerk定价模型
  • 实施成本优化策略
  • 配置使用情况监控
  • 设置预算告警

Error Handling

错误处理

IssueCauseSolution
Unexpected billMAU spikeImplement usage monitoring
Feature limitationsFree tier limitsUpgrade to Pro
API limitsHeavy usageImplement caching
问题原因解决方案
账单超出预期MAU激增实施使用情况监控
功能受限免费版限制升级至专业版
API调用受限使用量过大实现缓存机制

Resources

参考资源

Next Steps

下一步

Proceed to
clerk-reference-architecture
for architecture patterns.
继续查看
clerk-reference-architecture
获取架构模式相关内容。