Agent Skills: Clerk Cost Tuning

|

UncategorizedID: jeremylongshore/claude-code-plugins-plus-skills/clerk-cost-tuning

Install this agent skill to your local

pnpm dlx add-skill https://github.com/jeremylongshore/claude-code-plugins-plus-skills/tree/HEAD/plugins/saas-packs/clerk-pack/skills/clerk-cost-tuning

Skill Files

Browse the full folder contents for clerk-cost-tuning.

Download Skill

Loading file tree…

plugins/saas-packs/clerk-pack/skills/clerk-cost-tuning/SKILL.md

Skill Metadata

Name
clerk-cost-tuning
Description
'Optimize Clerk costs and understand pricing.

Clerk Cost Tuning

Overview

Understand Clerk pricing and optimize costs. Clerk charges by Monthly Retained Users (MRU) — a user is counted as retained only when they return 24+ hours after signing up. Covers pricing tiers, MRU reduction strategies, caching to reduce API calls, and usage monitoring.

Prerequisites

  • Clerk account active
  • Understanding of MRU (Monthly Retained Users)
  • Application usage patterns known

Instructions

Step 1: Understand Clerk Pricing Model

| Plan | Price | MRU Included | Extra MRU | |------|-------|-------------|-----------| | Free (Hobby) | $0/mo | 50,000 MRU | N/A | | Pro | $25/mo ($20/mo billed annually) | 50,000 MRU | $0.02/MRU | | Business | $300/mo ($250/mo billed annually) | 50,000 MRU | $0.02/MRU | | Enterprise | Custom | Custom | Custom |

Key pricing concepts:

  • MRU = unique user who returns to your app 24+ hours after signing up ("first day free" — sign-up-day activity never counts)
  • Users who sign up and never return are not billed
  • Users who only visit public pages are not counted
  • Bot/crawler sessions are not counted
  • Test/development instances are free and unlimited

Step 2: Reduce MRU Count

// Strategy 1: Defer authentication — don't force sign-in until necessary
// middleware.ts
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'

const requiresAuth = createRouteMatcher([
  '/dashboard(.*)',
  '/settings(.*)',
  '/api/protected(.*)',
])

export default clerkMiddleware(async (auth, req) => {
  // Only require auth for specific routes (not entire site)
  if (requiresAuth(req)) {
    await auth.protect()
  }
})
// Strategy 2: Use anonymous access for read-only features
// app/blog/[slug]/page.tsx
import { auth } from '@clerk/nextjs/server'

export default async function BlogPost({ params }: { params: { slug: string } }) {
  const { userId } = await auth() // Check but don't require
  const post = await db.post.findUnique({ where: { slug: params.slug } })

  return (
    <article>
      <h1>{post?.title}</h1>
      <div>{post?.content}</div>
      {userId ? <CommentForm /> : <p>Sign in to comment</p>}
    </article>
  )
}

Step 3: Cache to Reduce API Calls

// lib/user-cache.ts
import { cache } from 'react'
import { currentUser } from '@clerk/nextjs/server'

// Deduplicate within single request (free)
export const getUser = cache(async () => {
  return currentUser()
})

// Cross-request caching reduces Backend API calls
import { unstable_cache } from 'next/cache'
import { clerkClient } from '@clerk/nextjs/server'

export const getUserMetadata = unstable_cache(
  async (userId: string) => {
    const client = await clerkClient()
    const user = await client.users.getUser(userId)
    return user.publicMetadata
  },
  ['user-metadata'],
  { revalidate: 600 } // 10-minute cache
)

Step 4: Monitor Usage

// app/api/admin/clerk-usage/route.ts
import { auth, clerkClient } from '@clerk/nextjs/server'

export async function GET() {
  const { has } = await auth()
  if (!has({ role: 'org:admin' })) {
    return Response.json({ error: 'Admin only' }, { status: 403 })
  }

  const client = await clerkClient()
  const users = await client.users.getUserList({ limit: 1 })

  return Response.json({
    totalUsers: users.totalCount,
    // Estimate MRU based on recent sign-ins
    estimatedMRU: 'Check Clerk Dashboard > Billing for actual MRU',
    dashboardUrl: 'last-active?after=30d',
  })
}

Step 5: Clean Up Inactive Users

// scripts/cleanup-inactive-users.ts
import { createClerkClient } from '@clerk/backend'

const clerk = createClerkClient({ secretKey: process.env.CLERK_SECRET_KEY! })

async function findInactiveUsers(daysInactive = 90) {
  const cutoff = Date.now() - daysInactive * 24 * 60 * 60 * 1000
  const allUsers = await clerk.users.getUserList({ limit: 500 })

  const inactive = allUsers.data.filter(
    (user) => (user.lastSignInAt || 0) < cutoff
  )

  console.log(`Found ${inactive.length} users inactive for ${daysInactive}+ days`)
  console.log('Consider: notification campaign, data export, or account cleanup')

  return inactive
}

findInactiveUsers()

Output

  • Pricing model understood with MRU thresholds
  • Route-level auth to minimize unnecessary MRU counts
  • Request-level and cross-request caching reducing API calls
  • Usage monitoring endpoint for admins
  • Inactive user identification script

Error Handling

| Issue | Cause | Solution | |-------|-------|----------| | Unexpected bill increase | MRU spike from bot traffic | Add bot detection, restrict auth to needed routes | | Feature limitations | Free tier limits (no SSO, etc.) | Upgrade to Pro ($25/mo, or $20/mo billed annually) | | High API call volume | No caching | Add React cache() + unstable_cache() | | MRU count mismatch | Counting test users | Use separate dev instance (free, unlimited) |

Examples

Cost Estimation Script

function estimateMonthlyCost(mru: number): string {
  if (mru <= 50_000) return 'Free tier ($0/mo)'
  const overage = mru - 50_000
  const cost = 25 + overage * 0.02
  return `Pro tier: $${cost.toFixed(2)}/mo (${overage.toLocaleString()} extra MRU at $0.02 each)`
}

console.log(estimateMonthlyCost(15_000))  // "Free tier ($0/mo)"
console.log(estimateMonthlyCost(60_000))  // "Pro tier: $225.00/mo (10,000 extra MRU at $0.02 each)"

Resources

Next Steps

Proceed to clerk-reference-architecture for architecture patterns.