Clerk Production Checklist
Overview
Complete checklist to ensure your Clerk integration is production-ready. Covers environment config, security hardening, monitoring, error handling, and compliance.
Prerequisites
- Clerk integration working in development
- Production environment and domain configured
- CI/CD pipeline ready
Instructions
Step 1: Environment Configuration Checklist
| Check |
Status |
Action |
Using pk_live_ keys |
[ ] |
Switch from test to live keys |
CLERK_SECRET_KEY is sk_live_ |
[ ] |
Never use test keys in production |
.env.local in .gitignore |
[ ] |
Prevent accidental secret commits |
CLERK_WEBHOOK_SECRET set |
[ ] |
Required for webhook verification |
| Production domain in Clerk Dashboard |
[ ] |
Dashboard > Domains |
| Sign-in/sign-up URLs configured |
[ ] |
Set NEXT_PUBLIC_CLERK_SIGN_IN_URL etc. |
Step 2: Validation Script
// scripts/prod-readiness.ts
import { createClerkClient } from '@clerk/backend'
async function validateProduction() {
const checks: { name: string; pass: boolean; detail: string }[] = []
// 1. Live keys check
const pk = process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY || ''
const sk = process.env.CLERK_SECRET_KEY || ''
checks.push({
name: 'Live publishable key',
pass: pk.startsWith('pk_live_'),
detail: pk.startsWith('pk_live_') ? 'Using live key' : `Using ${pk.slice(0, 8)}... (should be pk_live_)`,
})
checks.push({
name: 'Live secret key',
pass: sk.startsWith('sk_live_'),
detail: sk.startsWith('sk_live_') ? 'Using live key' : 'Should be sk_live_ for production',
})
// 2. API connectivity
try {
const clerk = createClerkClient({ secretKey: sk })
await clerk.users.getUserList({ limit: 1 })
checks.push({ name: 'API connectivity', pass: true, detail: 'Backend API reachable' })
} catch (err: any) {
checks.push({ name: 'API connectivity', pass: false, detail: err.message })
}
// 3. Webhook secret
checks.push({
name: 'Webhook secret configured',
pass: !!process.env.CLERK_WEBHOOK_SECRET,
detail: process.env.CLERK_WEBHOOK_SECRET ? 'Set' : 'CLERK_WEBHOOK_SECRET missing',
})
// 4. Middleware exists
const fs = await import('fs')
const hasMiddleware = fs.existsSync('middleware.ts') || fs.existsSync('src/middleware.ts')
checks.push({
name: 'Middleware present',
pass: hasMiddleware,
detail: hasMiddleware ? 'Found' : 'middleware.ts not found at project root',
})
// Print results
console.log('\n=== Clerk Production Readiness ===\n')
for (const check of checks) {
const icon = check.pass ? 'PASS' : 'FAIL'
console.log(`[${icon}] ${check.name}: ${check.detail}`)
}
const allPass = checks.every((c) => c.pass)
console.log(`\nResult: ${allPass ? 'READY for production' : 'NOT READY — fix failing checks'}`)
process.exit(allPass ? 0 : 1)
}
validateProduction()
Run with:
npx tsx scripts/prod-readiness.ts
Step 3: Security Checklist
| Check |
Status |
Action |
| Middleware protects all routes |
[ ] |
Verify non-public routes require auth |
API routes check userId |
[ ] |
Return 401 if userId is null |
| Webhook signatures verified |
[ ] |
Use svix library for verification |
| CORS configured correctly |
[ ] |
Only allow production domain |
| Rate limiting on sensitive endpoints |
[ ] |
Use @upstash/ratelimit or similar |
| CSP headers set |
[ ] |
Add Clerk domains to Content-Security-Policy |
| No secret keys in client code |
[ ] |
CLERK_SECRET_KEY never exposed |
Step 4: Monitoring Checklist
| Check |
Status |
Action |
| Health check endpoint |
[ ] |
/api/health monitoring Clerk API |
| Error tracking (Sentry) |
[ ] |
Clerk user context in error reports |
| Auth event logging |
[ ] |
Log sign-in, sign-out, permission denied |
| Webhook monitoring |
[ ] |
Alert on failed webhook deliveries |
| Uptime monitoring |
[ ] |
External monitor hitting health endpoint |
Step 5: Error Handling Checklist
| Check |
Status |
Action |
| Custom error pages |
[ ] |
/not-found, /error pages handle auth errors |
| Graceful auth failures |
[ ] |
Redirect to sign-in, don't show stack traces |
| Webhook retry handling |
[ ] |
Idempotency keys prevent duplicate processing |
| Session expiry UX |
[ ] |
Show "session expired" prompt, not blank page |
// app/error.tsx — global error boundary with auth context
'use client'
import { useAuth } from '@clerk/nextjs'
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
const { isSignedIn } = useAuth()
return (
<div>
<h2>Something went wrong</h2>
<p>{error.message}</p>
<button again</button>
{!isSignedIn && <a href="/sign-in">Sign in</a>}
</div>
)
}
Step 6: Performance Checklist
| Check |
Status |
Action |
| Middleware matcher excludes static files |
[ ] |
Don't auth-check images, fonts, CSS |
User data cached (React.cache()) |
[ ] |
Deduplicate within request |
| Auth components lazy loaded |
[ ] |
dynamic() for UserButton, SignInButton |
| Edge Runtime for middleware |
[ ] |
Faster cold starts on Vercel |
Output
- Environment configuration verified (live keys, webhook secret, domain)
- Automated validation script (run in CI or before deploy)
- Security, monitoring, error handling, and performance checklists
- Global error boundary component with auth context
Error Handling
| Error |
Cause |
Solution |
| Validation script fails |
Test keys in production |
Switch to pk_live_ / sk_live_ keys |
| API connectivity check fails |
Wrong secret key |
Verify key in Clerk Dashboard > API Keys |
| Middleware not found |
File in wrong location |
Place middleware.ts at project root (not inside app/) |
| Health check returns 503 |
Clerk API unreachable |
Check network, verify key, check status.clerk.com |
Examples
CI Production Gate
# .github/workflows/deploy.yml — add as pre-deploy step
- name: Clerk production readiness
run: npx tsx scripts/prod-readiness.ts
env:
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: ${{ secrets.CLERK_PK_PROD }}
CLERK_SECRET_KEY: ${{ secrets.CLERK_SK_PROD }}
CLERK_WEBHOOK_SECRET: ${{ secrets.CLERK_WEBHOOK_SECRET_PROD }}
Resources
Next Steps
Proceed to clerk-upgrade-migration for SDK version upgrades.
1---2name: clerk-prod-checklist3description: Production readiness checklist for Clerk deployment. Use when preparing to deploy, reviewing production configuration, or auditing Clerk implementation before launch. Trigger with phrases like "clerk production", "clerk deploy checklist", "clerk go-live", "clerk launch ready".4license: MIT5---6# Clerk Production Checklist
7
8## Overview
9
10Complete checklist to ensure your Clerk integration is production-ready. Covers environment config, security hardening, monitoring, error handling, and compliance.
11
12## Prerequisites
13
14- Clerk integration working in development
15- Production environment and domain configured
16- CI/CD pipeline ready
17
18## Instructions
19
20### Step 1: Environment Configuration Checklist
21
22| Check | Status | Action |
23|-------|--------|--------|
24| Using `pk_live_` keys | [ ] | Switch from test to live keys |
25| `CLERK_SECRET_KEY` is `sk_live_` | [ ] | Never use test keys in production |
26| `.env.local` in `.gitignore` | [ ] | Prevent accidental secret commits |
27| `CLERK_WEBHOOK_SECRET` set | [ ] | Required for webhook verification |
28| Production domain in Clerk Dashboard | [ ] | Dashboard > Domains |
29| Sign-in/sign-up URLs configured | [ ] | Set `NEXT_PUBLIC_CLERK_SIGN_IN_URL` etc. |
30
31### Step 2: Validation Script
32
33```typescript
34// scripts/prod-readiness.ts
35import { createClerkClient } from '@clerk/backend'
36
37async function validateProduction() {
38 const checks: { name: string; pass: boolean; detail: string }[] = []
39
40 // 1. Live keys check
41 const pk = process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY || ''
42 const sk = process.env.CLERK_SECRET_KEY || ''
43 checks.push({
44 name: 'Live publishable key',
45 pass: pk.startsWith('pk_live_'),
46 detail: pk.startsWith('pk_live_') ? 'Using live key' : `Using ${pk.slice(0, 8)}... (should be pk_live_)`,
47 })
48 checks.push({
49 name: 'Live secret key',
50 pass: sk.startsWith('sk_live_'),
51 detail: sk.startsWith('sk_live_') ? 'Using live key' : 'Should be sk_live_ for production',
52 })
53
54 // 2. API connectivity
55 try {
56 const clerk = createClerkClient({ secretKey: sk })
57 await clerk.users.getUserList({ limit: 1 })
58 checks.push({ name: 'API connectivity', pass: true, detail: 'Backend API reachable' })
59 } catch (err: any) {
60 checks.push({ name: 'API connectivity', pass: false, detail: err.message })
61 }
62
63 // 3. Webhook secret
64 checks.push({
65 name: 'Webhook secret configured',
66 pass: !!process.env.CLERK_WEBHOOK_SECRET,
67 detail: process.env.CLERK_WEBHOOK_SECRET ? 'Set' : 'CLERK_WEBHOOK_SECRET missing',
68 })
69
70 // 4. Middleware exists
71 const fs = await import('fs')
72 const hasMiddleware = fs.existsSync('middleware.ts') || fs.existsSync('src/middleware.ts')
73 checks.push({
74 name: 'Middleware present',
75 pass: hasMiddleware,
76 detail: hasMiddleware ? 'Found' : 'middleware.ts not found at project root',
77 })
78
79 // Print results
80 console.log('\n=== Clerk Production Readiness ===\n')
81 for (const check of checks) {
82 const icon = check.pass ? 'PASS' : 'FAIL'
83 console.log(`[${icon}] ${check.name}: ${check.detail}`)
84 }
85
86 const allPass = checks.every((c) => c.pass)
87 console.log(`\nResult: ${allPass ? 'READY for production' : 'NOT READY — fix failing checks'}`)
88 process.exit(allPass ? 0 : 1)
89}
90
91validateProduction()
92```
93
94Run with:
95
96```bash
97npx tsx scripts/prod-readiness.ts
98```
99
100### Step 3: Security Checklist
101
102| Check | Status | Action |
103|-------|--------|--------|
104| Middleware protects all routes | [ ] | Verify non-public routes require auth |
105| API routes check `userId` | [ ] | Return 401 if `userId` is null |
106| Webhook signatures verified | [ ] | Use `svix` library for verification |
107| CORS configured correctly | [ ] | Only allow production domain |
108| Rate limiting on sensitive endpoints | [ ] | Use `@upstash/ratelimit` or similar |
109| CSP headers set | [ ] | Add Clerk domains to Content-Security-Policy |
110| No secret keys in client code | [ ] | `CLERK_SECRET_KEY` never exposed |
111
112### Step 4: Monitoring Checklist
113
114| Check | Status | Action |
115|-------|--------|--------|
116| Health check endpoint | [ ] | `/api/health` monitoring Clerk API |
117| Error tracking (Sentry) | [ ] | Clerk user context in error reports |
118| Auth event logging | [ ] | Log sign-in, sign-out, permission denied |
119| Webhook monitoring | [ ] | Alert on failed webhook deliveries |
120| Uptime monitoring | [ ] | External monitor hitting health endpoint |
121
122### Step 5: Error Handling Checklist
123
124| Check | Status | Action |
125|-------|--------|--------|
126| Custom error pages | [ ] | `/not-found`, `/error` pages handle auth errors |
127| Graceful auth failures | [ ] | Redirect to sign-in, don't show stack traces |
128| Webhook retry handling | [ ] | Idempotency keys prevent duplicate processing |
129| Session expiry UX | [ ] | Show "session expired" prompt, not blank page |
130
131```typescript
132// app/error.tsx — global error boundary with auth context
133'use client'
134import { useAuth } from '@clerk/nextjs'
135
136export default function Error({ error, reset }: { error: Error; reset: () => void }) {
137 const { isSignedIn } = useAuth()
138
139 return (
140 <div>
141 <h2>Something went wrong</h2>
142 <p>{error.message}</p>
143 <button onClick={reset}>Try again</button>
144 {!isSignedIn && <a href="/sign-in">Sign in</a>}
145 </div>
146 )
147}
148```
149
150### Step 6: Performance Checklist
151
152| Check | Status | Action |
153|-------|--------|--------|
154| Middleware matcher excludes static files | [ ] | Don't auth-check images, fonts, CSS |
155| User data cached (`React.cache()`) | [ ] | Deduplicate within request |
156| Auth components lazy loaded | [ ] | `dynamic()` for `UserButton`, `SignInButton` |
157| Edge Runtime for middleware | [ ] | Faster cold starts on Vercel |
158
159## Output
160
161- Environment configuration verified (live keys, webhook secret, domain)
162- Automated validation script (run in CI or before deploy)
163- Security, monitoring, error handling, and performance checklists
164- Global error boundary component with auth context
165
166## Error Handling
167
168| Error | Cause | Solution |
169|-------|-------|----------|
170| Validation script fails | Test keys in production | Switch to `pk_live_` / `sk_live_` keys |
171| API connectivity check fails | Wrong secret key | Verify key in Clerk Dashboard > API Keys |
172| Middleware not found | File in wrong location | Place `middleware.ts` at project root (not inside `app/`) |
173| Health check returns 503 | Clerk API unreachable | Check network, verify key, check status.clerk.com |
174
175## Examples
176
177### CI Production Gate
178
179```yaml
180# .github/workflows/deploy.yml — add as pre-deploy step
181- name: Clerk production readiness
182 run: npx tsx scripts/prod-readiness.ts
183 env:
184 NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: ${{ secrets.CLERK_PK_PROD }}
185 CLERK_SECRET_KEY: ${{ secrets.CLERK_SK_PROD }}
186 CLERK_WEBHOOK_SECRET: ${{ secrets.CLERK_WEBHOOK_SECRET_PROD }}
187```
188
189## Resources
190
191- [Clerk Production Checklist](https://clerk.com/docs/deployments/overview)
192- [Clerk Security Best Practices](https://clerk.com/docs/security/overview)
193- Clerk Domain Setup
194
195## Next Steps
196
197Proceed to `clerk-upgrade-migration` for SDK version upgrades.