--- name: clerk-multi-env-setup description: 'Configure Clerk for multiple environments (dev, staging, production). Use when setting up environment-specific configurations, managing multiple Clerk instances, or implementing environment promotion. Trigger with phrases like "clerk environments", "clerk staging", "clerk dev prod", "clerk multi-environment". ' allowed-tools: Read, Write, Edit, Bash(npm:*), Grep version: 1.13.0 license: MIT author: Jeremy Longshore tags: - saas - clerk - clerk-multi compatibility: Designed for Claude Code, also compatible with Codex and OpenClaw --- # Clerk Multi-Environment Setup ## Overview Configure Clerk across development, staging, and production environments with separate instances, environment-aware configuration, and safe promotion workflows. ## Prerequisites - Clerk account (one instance per environment recommended) - CI/CD pipeline (GitHub Actions, Vercel, etc.) - Environment variable management in place ## Instructions ### Step 1: Create Clerk Instances Create separate Clerk instances in the Dashboard for each environment: | Environment | Instance | Key Prefix | Domain | |-------------|----------|------------|--------| | Development | my-app-dev | `pk_test_` / `sk_test_` | localhost:3000 | | Staging | my-app-staging | `pk_test_` / `sk_test_` | staging.myapp.com | | Production | my-app-prod | `pk_live_` / `sk_live_` | myapp.com | ### Step 2: Environment Configuration Files ```bash # .env.local (development - git-ignored) NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_dev... CLERK_SECRET_KEY=sk_test_dev... CLERK_WEBHOOK_SECRET=whsec_dev... # .env.staging (staging) NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_staging... CLERK_SECRET_KEY=sk_test_staging... CLERK_WEBHOOK_SECRET=whsec_staging... # .env.production (production) NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_live_prod... CLERK_SECRET_KEY=sk_live_prod... CLERK_WEBHOOK_SECRET=whsec_prod... ``` ### Step 3: Environment-Aware Configuration ```typescript // lib/clerk-config.ts type ClerkEnv = 'development' | 'staging' | 'production' function getClerkEnv(): ClerkEnv { const key = process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY || '' if (key.startsWith('pk_live_')) return 'production' if (process.env.VERCEL_ENV === 'preview') return 'staging' return 'development' } export const clerkConfig = { env: getClerkEnv(), get isDev() { return this.env === 'development' }, get isProd() { return this.env === 'production' }, signInUrl: '/sign-in', signUpUrl: '/sign-up', afterSignInUrl: '/dashboard', get allowedRedirectOrigins() { switch (this.env) { case 'production': return ['https://myapp.com'] case 'staging': return ['https://staging.myapp.com'] default: return ['http://localhost:3000'] } }, } ``` ### Step 4: Startup Validation ```typescript // lib/validate-env.ts export function validateClerkEnv() { const required = [ 'NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY', 'CLERK_SECRET_KEY', ] const missing = required.filter((key) => !process.env[key]) if (missing.length > 0) { throw new Error(`Missing Clerk env vars: ${missing.join(', ')}`) } const pk = process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY! const sk = process.env.CLERK_SECRET_KEY! // Prevent key mismatch (test keys with live keys) const pkIsLive = pk.startsWith('pk_live_') const skIsLive = sk.startsWith('sk_live_') if (pkIsLive !== skIsLive) { throw new Error('Clerk key mismatch: publishable and secret keys must be from same environment') } // Warn if using live keys in development if (pkIsLive && process.env.NODE_ENV === 'development') { console.warn('WARNING: Using production Clerk keys in development mode') } } ``` Call at app startup: ```typescript // app/layout.tsx import { validateClerkEnv } from '@/lib/validate-env' validateClerkEnv() ``` ### Step 5: Webhook Configuration Per Environment ```typescript // app/api/webhooks/clerk/route.ts import { headers } from 'next/headers' import { Webhook } from 'svix' export async function POST(req: Request) { // Each environment uses its own webhook secret const secret = process.env.CLERK_WEBHOOK_SECRET if (!secret) { console.error('CLERK_WEBHOOK_SECRET not configured for this environment') return new Response('Server error', { status: 500 }) } // Verification logic (same across environments) const headerPayload = await headers() const wh = new Webhook(secret) const body = await req.text() try { const evt = wh.verify(body, { 'svix-id': headerPayload.get('svix-id')!, 'svix-timestamp': headerPayload.get('svix-timestamp')!, 'svix-signature': headerPayload.get('svix-signature')!, }) // Handle event... return new Response('OK', { status: 200 }) } catch { return new Response('Invalid signature', { status: 400 }) } } ``` ### Step 6: CI/CD Environment Promotion ```yaml # .github/workflows/deploy.yml name: Deploy on: push: branches: [main, staging] jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set environment run: | if [[ "${{ github.ref }}" == "refs/heads/main" ]]; then echo "DEPLOY_ENV=production" >> $GITHUB_ENV else echo "DEPLOY_ENV=staging" >> $GITHUB_ENV fi - name: Deploy to Vercel env: NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: ${{ secrets[format('CLERK_PK_{0}', env.DEPLOY_ENV)] }} CLERK_SECRET_KEY: ${{ secrets[format('CLERK_SK_{0}', env.DEPLOY_ENV)] }} run: vercel deploy --prod ``` ## Output - Separate Clerk instances per environment (dev, staging, production) - Environment-aware configuration with key validation - Startup checks preventing key mismatches - Per-environment webhook secrets - CI/CD pipeline deploying correct keys per branch ## Error Handling | Error | Cause | Solution | |-------|-------|----------| | Key mismatch error | `pk_test_` with `sk_live_` | Ensure both keys from same Clerk instance | | Webhook signature fails | Wrong secret for environment | Verify `CLERK_WEBHOOK_SECRET` matches instance | | User not found | Querying wrong environment | Check you are hitting correct Clerk instance | | OAuth redirect fails | Domain not configured | Add environment domain in Clerk Dashboard | ## Examples ### Vercel Preview Environment Setup ```bash # Set env vars for Vercel preview deployments vercel env add NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY preview vercel env add CLERK_SECRET_KEY preview ``` ## Resources - [Clerk Deployment Environments](https://clerk.com/docs/deployments/overview) - [Preview Environment Setup](https://clerk.com/docs/deployments/set-up-preview-environment) - [Vercel Environment Variables](https://vercel.com/docs/environment-variables) ## Next Steps Proceed to `clerk-observability` for monitoring and logging.