clerk-multi-env-setup
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseClerk Multi-Environment Setup
Clerk 多环境配置
Overview
概述
Configure Clerk across development, staging, and production environments.
在开发、预发布和生产环境中配置Clerk。
Prerequisites
前置条件
- Clerk account with multiple instances
- Understanding of environment management
- CI/CD pipeline configured
- 拥有多实例的Clerk账号
- 了解环境管理知识
- 已配置CI/CD流水线
Instructions
操作步骤
Step 1: Create Clerk Instances
步骤1:创建Clerk实例
Create separate Clerk instances for each environment in the Clerk Dashboard:
- - Development
myapp-dev - - Staging
myapp-staging - - Production
myapp-prod
在Clerk控制台为每个环境创建独立的Clerk实例:
- - 开发环境
myapp-dev - - 预发布环境
myapp-staging - - 生产环境
myapp-prod
Step 2: Environment Configuration
步骤2:环境变量配置
bash
undefinedbash
undefined.env.development.local
.env.development.local
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_dev_...
CLERK_SECRET_KEY=sk_test_dev_...
NEXT_PUBLIC_APP_ENV=development
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_dev_...
CLERK_SECRET_KEY=sk_test_dev_...
NEXT_PUBLIC_APP_ENV=development
.env.staging.local
.env.staging.local
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_staging_...
CLERK_SECRET_KEY=sk_test_staging_...
NEXT_PUBLIC_APP_ENV=staging
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_staging_...
CLERK_SECRET_KEY=sk_test_staging_...
NEXT_PUBLIC_APP_ENV=staging
.env.production.local
.env.production.local
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_live_...
CLERK_SECRET_KEY=sk_live_...
NEXT_PUBLIC_APP_ENV=production
undefinedNEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_live_...
CLERK_SECRET_KEY=sk_live_...
NEXT_PUBLIC_APP_ENV=production
undefinedStep 3: Environment-Aware Configuration
步骤3:环境感知配置
typescript
// lib/clerk-config.ts
type Environment = 'development' | 'staging' | 'production'
interface ClerkConfig {
signInUrl: string
signUpUrl: string
afterSignInUrl: string
afterSignUpUrl: string
debug: boolean
}
const configs: Record<Environment, ClerkConfig> = {
development: {
signInUrl: '/sign-in',
signUpUrl: '/sign-up',
afterSignInUrl: '/dashboard',
afterSignUpUrl: '/onboarding',
debug: true
},
staging: {
signInUrl: '/sign-in',
signUpUrl: '/sign-up',
afterSignInUrl: '/dashboard',
afterSignUpUrl: '/onboarding',
debug: true
},
production: {
signInUrl: '/sign-in',
signUpUrl: '/sign-up',
afterSignInUrl: '/dashboard',
afterSignUpUrl: '/onboarding',
debug: false
}
}
export function getClerkConfig(): ClerkConfig {
const env = (process.env.NEXT_PUBLIC_APP_ENV as Environment) || 'development'
return configs[env]
}
// Validate environment at startup
export function validateClerkEnvironment() {
const pk = process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY
const env = process.env.NEXT_PUBLIC_APP_ENV
if (env === 'production' && pk?.startsWith('pk_test_')) {
throw new Error('Production environment using test keys!')
}
if (env !== 'production' && pk?.startsWith('pk_live_')) {
console.warn('Non-production environment using live keys')
}
}typescript
// lib/clerk-config.ts
type Environment = 'development' | 'staging' | 'production'
interface ClerkConfig {
signInUrl: string
signUpUrl: string
afterSignInUrl: string
afterSignUpUrl: string
debug: boolean
}
const configs: Record<Environment, ClerkConfig> = {
development: {
signInUrl: '/sign-in',
signUpUrl: '/sign-up',
afterSignInUrl: '/dashboard',
afterSignUpUrl: '/onboarding',
debug: true
},
staging: {
signInUrl: '/sign-in',
signUpUrl: '/sign-up',
afterSignInUrl: '/dashboard',
afterSignUpUrl: '/onboarding',
debug: true
},
production: {
signInUrl: '/sign-in',
signUpUrl: '/sign-up',
afterSignInUrl: '/dashboard',
afterSignUpUrl: '/onboarding',
debug: false
}
}
export function getClerkConfig(): ClerkConfig {
const env = (process.env.NEXT_PUBLIC_APP_ENV as Environment) || 'development'
return configs[env]
}
// Validate environment at startup
export function validateClerkEnvironment() {
const pk = process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY
const env = process.env.NEXT_PUBLIC_APP_ENV
if (env === 'production' && pk?.startsWith('pk_test_')) {
throw new Error('Production environment using test keys!')
}
if (env !== 'production' && pk?.startsWith('pk_live_')) {
console.warn('Non-production environment using live keys')
}
}Step 4: ClerkProvider Configuration
步骤4:ClerkProvider配置
typescript
// app/layout.tsx
import { ClerkProvider } from '@clerk/nextjs'
import { getClerkConfig, validateClerkEnvironment } from '@/lib/clerk-config'
// Validate on startup
validateClerkEnvironment()
export default function RootLayout({ children }) {
const config = getClerkConfig()
return (
<ClerkProvider
signInUrl={config.signInUrl}
signUpUrl={config.signUpUrl}
afterSignInUrl={config.afterSignInUrl}
afterSignUpUrl={config.afterSignUpUrl}
>
<html>
<body>
{config.debug && <EnvironmentBanner />}
{children}
</body>
</html>
</ClerkProvider>
)
}
function EnvironmentBanner() {
const env = process.env.NEXT_PUBLIC_APP_ENV
if (env === 'production') return null
const colors = {
development: 'bg-green-500',
staging: 'bg-yellow-500'
}
return (
<div className={`${colors[env]} text-white text-center text-sm py-1`}>
{env?.toUpperCase()} ENVIRONMENT
</div>
)
}typescript
// app/layout.tsx
import { ClerkProvider } from '@clerk/nextjs'
import { getClerkConfig, validateClerkEnvironment } from '@/lib/clerk-config'
// Validate on startup
validateClerkEnvironment()
export default function RootLayout({ children }) {
const config = getClerkConfig()
return (
<ClerkProvider
signInUrl={config.signInUrl}
signUpUrl={config.signUpUrl}
afterSignInUrl={config.afterSignInUrl}
afterSignUpUrl={config.afterSignUpUrl}
>
<html>
<body>
{config.debug && <EnvironmentBanner />}
{children}
</body>
</html>
</ClerkProvider>
)
}
function EnvironmentBanner() {
const env = process.env.NEXT_PUBLIC_APP_ENV
if (env === 'production') return null
const colors = {
development: 'bg-green-500',
staging: 'bg-yellow-500'
}
return (
<div className={`${colors[env]} text-white text-center text-sm py-1`}>
{env?.toUpperCase()} ENVIRONMENT
</div>
)
}Step 5: Webhook Configuration Per Environment
步骤5:按环境配置Webhook
typescript
// app/api/webhooks/clerk/route.ts
import { headers } from 'next/headers'
const WEBHOOK_SECRETS = {
development: process.env.CLERK_WEBHOOK_SECRET_DEV,
staging: process.env.CLERK_WEBHOOK_SECRET_STAGING,
production: process.env.CLERK_WEBHOOK_SECRET
}
export async function POST(req: Request) {
const env = process.env.NEXT_PUBLIC_APP_ENV as keyof typeof WEBHOOK_SECRETS
const WEBHOOK_SECRET = WEBHOOK_SECRETS[env]
if (!WEBHOOK_SECRET) {
console.error(`No webhook secret for environment: ${env}`)
return Response.json({ error: 'Configuration error' }, { status: 500 })
}
// ... rest of webhook handling
}typescript
// app/api/webhooks/clerk/route.ts
import { headers } from 'next/headers'
const WEBHOOK_SECRETS = {
development: process.env.CLERK_WEBHOOK_SECRET_DEV,
staging: process.env.CLERK_WEBHOOK_SECRET_STAGING,
production: process.env.CLERK_WEBHOOK_SECRET
}
export async function POST(req: Request) {
const env = process.env.NEXT_PUBLIC_APP_ENV as keyof typeof WEBHOOK_SECRETS
const WEBHOOK_SECRET = WEBHOOK_SECRETS[env]
if (!WEBHOOK_SECRET) {
console.error(`No webhook secret for environment: ${env}`)
return Response.json({ error: 'Configuration error' }, { status: 500 })
}
// ... rest of webhook handling
}Step 6: CI/CD Environment Promotion
步骤6:CI/CD环境升级配置
yaml
undefinedyaml
undefined.github/workflows/deploy.yml
.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
echo "CLERK_PUBLISHABLE_KEY=${{ secrets.CLERK_PUBLISHABLE_KEY_PROD }}" >> $GITHUB_ENV
echo "CLERK_SECRET_KEY=${{ secrets.CLERK_SECRET_KEY_PROD }}" >> $GITHUB_ENV
else
echo "DEPLOY_ENV=staging" >> $GITHUB_ENV
echo "CLERK_PUBLISHABLE_KEY=${{ secrets.CLERK_PUBLISHABLE_KEY_STAGING }}" >> $GITHUB_ENV
echo "CLERK_SECRET_KEY=${{ secrets.CLERK_SECRET_KEY_STAGING }}" >> $GITHUB_ENV
fi
- name: Build
run: npm run build
env:
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: ${{ env.CLERK_PUBLISHABLE_KEY }}
NEXT_PUBLIC_APP_ENV: ${{ env.DEPLOY_ENV }}
- name: Deploy to Vercel
run: vercel deploy --prod
env:
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}undefinedname: 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
echo "CLERK_PUBLISHABLE_KEY=${{ secrets.CLERK_PUBLISHABLE_KEY_PROD }}" >> $GITHUB_ENV
echo "CLERK_SECRET_KEY=${{ secrets.CLERK_SECRET_KEY_PROD }}" >> $GITHUB_ENV
else
echo "DEPLOY_ENV=staging" >> $GITHUB_ENV
echo "CLERK_PUBLISHABLE_KEY=${{ secrets.CLERK_PUBLISHABLE_KEY_STAGING }}" >> $GITHUB_ENV
echo "CLERK_SECRET_KEY=${{ secrets.CLERK_SECRET_KEY_STAGING }}" >> $GITHUB_ENV
fi
- name: Build
run: npm run build
env:
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: ${{ env.CLERK_PUBLISHABLE_KEY }}
NEXT_PUBLIC_APP_ENV: ${{ env.DEPLOY_ENV }}
- name: Deploy to Vercel
run: vercel deploy --prod
env:
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}undefinedStep 7: User Data Isolation
步骤7:用户数据隔离
typescript
// lib/user-sync.ts
// Ensure user data doesn't leak between environments
export async function syncUser(clerkUser: any) {
const env = process.env.NEXT_PUBLIC_APP_ENV
await db.user.upsert({
where: {
clerkId_environment: {
clerkId: clerkUser.id,
environment: env
}
},
update: { /* ... */ },
create: {
clerkId: clerkUser.id,
environment: env,
// ... other fields
}
})
}typescript
// lib/user-sync.ts
// Ensure user data doesn't leak between environments
export async function syncUser(clerkUser: any) {
const env = process.env.NEXT_PUBLIC_APP_ENV
await db.user.upsert({
where: {
clerkId_environment: {
clerkId: clerkUser.id,
environment: env
}
},
update: { /* ... */ },
create: {
clerkId: clerkUser.id,
environment: env,
// ... other fields
}
})
}Environment Matrix
环境矩阵
| Environment | Keys | Domain | Data |
|---|---|---|---|
| Development | pk_test_dev | localhost:3000 | Dev DB |
| Staging | pk_test_staging | staging.myapp.com | Staging DB |
| Production | pk_live | myapp.com | Prod DB |
| 环境 | 密钥 | 域名 | 数据 |
|---|---|---|---|
| 开发环境 | pk_test_dev | localhost:3000 | 开发数据库 |
| 预发布环境 | pk_test_staging | staging.myapp.com | 预发布数据库 |
| 生产环境 | pk_live | myapp.com | 生产数据库 |
Output
输出结果
- Separate Clerk instances per environment
- Environment-aware configuration
- Webhook handling per environment
- CI/CD pipeline configured
- 每个环境拥有独立的Clerk实例
- 支持环境感知的配置
- 按环境处理Webhook
- 已配置CI/CD流水线
Best Practices
最佳实践
- Never share keys between environments
- Use test keys for non-production
- Validate key/environment match at startup
- Separate webhook secrets per environment
- Isolate user data by environment
- 切勿在环境之间共享密钥
- 非生产环境使用测试密钥
- 启动时验证密钥与环境的匹配性
- 为每个环境配置独立的Webhook密钥
- 按环境隔离用户数据
Error Handling
错误处理
| Error | Cause | Solution |
|---|---|---|
| Wrong environment keys | Misconfiguration | Validate at startup |
| Webhook signature fails | Wrong secret | Check env-specific secret |
| User not found | Env mismatch | Check environment isolation |
| 错误 | 原因 | 解决方案 |
|---|---|---|
| 环境密钥错误 | 配置错误 | 启动时进行验证 |
| Webhook签名验证失败 | 密钥错误 | 检查对应环境的密钥 |
| 用户未找到 | 环境不匹配 | 检查环境隔离配置 |
Resources
参考资源
Next Steps
后续步骤
Proceed to for monitoring and logging.
clerk-observability继续执行进行监控与日志配置。
clerk-observability