配置管理
AI SaaS Template 采用现代化的环境变量管理和配置系统,确保在不同环境下的安全性和灵活性。所有配置通过类型安全的方式管理,并支持运行时验证。
概述
配置系统特性:
- 环境变量管理: 统一的环境变量配置和验证
- 类型安全: 基于 Zod 的环境变量类型推断
- 多环境支持: 开发、测试、生产环境配置
- 配置验证: 启动时自动验证所有必需配置
- 敏感信息保护: 服务器端和客户端变量分离
环境变量结构
环境文件
.env.local # 本地开发环境(不提交到版本控制)
.env.example # 环境变量模板(可提交)
.env.production # 生产环境变量(部署平台管理)
核心环境变量
# === 应用基本配置 ===
NODE_ENV=development
NEXT_PUBLIC_APP_URL=http://localhost:3000
NEXT_PUBLIC_APP_NAME="AI SaaS Template"
# === 数据库配置 ===
DATABASE_URL="postgresql://username:password@localhost:5432/ai_saas_template"
# === Clerk 认证配置 ===
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY="pk_test_..."
CLERK_SECRET_KEY="sk_test_..."
NEXT_PUBLIC_CLERK_SIGN_IN_URL="/auth/sign-in"
NEXT_PUBLIC_CLERK_SIGN_UP_URL="/auth/sign-up"
NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL="/dashboard"
NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL="/dashboard"
# === AI 服务配置 ===
# OpenAI
OPENAI_API_KEY="sk-..."
OPENAI_ORGANIZATION="org-..."
# Anthropic Claude
ANTHROPIC_API_KEY="sk-ant-..."
# Google AI
GOOGLE_AI_API_KEY="AIza..."
# XAI
XAI_API_KEY="xai-..."
# === 支付配置 (Stripe) ===
STRIPE_SECRET_KEY="sk_test_..."
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY="pk_test_..."
STRIPE_WEBHOOK_SECRET="whsec_..."
# === 缓存配置 (Upstash Redis) ===
UPSTASH_REDIS_REST_URL="https://..."
UPSTASH_REDIS_REST_TOKEN="..."
# === 文件存储配置 (可选) ===
CLOUDFLARE_R2_ACCESS_KEY_ID="..."
CLOUDFLARE_R2_SECRET_ACCESS_KEY="..."
CLOUDFLARE_R2_BUCKET_NAME="..."
CLOUDFLARE_R2_ENDPOINT="https://..."
# === 邮件服务配置 (可选) ===
RESEND_API_KEY="re_..."
RESEND_FROM_EMAIL="[email protected]"
# === 监控和分析 (可选) ===
SENTRY_DSN="https://..."
NEXT_PUBLIC_GOOGLE_ANALYTICS_ID="G-..."
环境变量验证
项目使用 Zod 进行环境变量的类型验证和转换,确保启动时所有必需的配置都正确设置。
// env.ts
import { createEnv } from '@t3-oss/env-nextjs'
import { z } from 'zod'
export const env = createEnv({
// 服务器端环境变量
server: {
NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
DATABASE_URL: z.string().url(),
// Clerk 认证
CLERK_SECRET_KEY: z.string().min(1),
// AI 服务
OPENAI_API_KEY: z.string().optional(),
ANTHROPIC_API_KEY: z.string().optional(),
GOOGLE_AI_API_KEY: z.string().optional(),
XAI_API_KEY: z.string().optional(),
// 支付
STRIPE_SECRET_KEY: z.string().optional(),
STRIPE_WEBHOOK_SECRET: z.string().optional(),
// 缓存
UPSTASH_REDIS_REST_URL: z.string().url().optional(),
UPSTASH_REDIS_REST_TOKEN: z.string().optional(),
// 存储
CLOUDFLARE_R2_ACCESS_KEY_ID: z.string().optional(),
CLOUDFLARE_R2_SECRET_ACCESS_KEY: z.string().optional(),
CLOUDFLARE_R2_BUCKET_NAME: z.string().optional(),
CLOUDFLARE_R2_ENDPOINT: z.string().url().optional(),
// 邮件
RESEND_API_KEY: z.string().optional(),
RESEND_FROM_EMAIL: z.string().email().optional(),
// 监控
SENTRY_DSN: z.string().url().optional(),
},
// 客户端环境变量(必须以 NEXT_PUBLIC_ 开头)
client: {
NEXT_PUBLIC_APP_URL: z.string().url(),
NEXT_PUBLIC_APP_NAME: z.string().default('AI SaaS Template'),
// Clerk 认证
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: z.string().min(1),
NEXT_PUBLIC_CLERK_SIGN_IN_URL: z.string().default('/auth/sign-in'),
NEXT_PUBLIC_CLERK_SIGN_UP_URL: z.string().default('/auth/sign-up'),
NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL: z.string().default('/dashboard'),
NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL: z.string().default('/dashboard'),
// 支付
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: z.string().optional(),
// 分析
NEXT_PUBLIC_GOOGLE_ANALYTICS_ID: z.string().optional(),
},
// 共享环境变量
shared: {
NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
},
// 运行时环境变量
runtimeEnv: {
NODE_ENV: process.env.NODE_ENV,
DATABASE_URL: process.env.DATABASE_URL,
// 服务器端变量
CLERK_SECRET_KEY: process.env.CLERK_SECRET_KEY,
OPENAI_API_KEY: process.env.OPENAI_API_KEY,
ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY,
GOOGLE_AI_API_KEY: process.env.GOOGLE_AI_API_KEY,
XAI_API_KEY: process.env.XAI_API_KEY,
STRIPE_SECRET_KEY: process.env.STRIPE_SECRET_KEY,
STRIPE_WEBHOOK_SECRET: process.env.STRIPE_WEBHOOK_SECRET,
UPSTASH_REDIS_REST_URL: process.env.UPSTASH_REDIS_REST_URL,
UPSTASH_REDIS_REST_TOKEN: process.env.UPSTASH_REDIS_REST_TOKEN,
CLOUDFLARE_R2_ACCESS_KEY_ID: process.env.CLOUDFLARE_R2_ACCESS_KEY_ID,
CLOUDFLARE_R2_SECRET_ACCESS_KEY: process.env.CLOUDFLARE_R2_SECRET_ACCESS_KEY,
CLOUDFLARE_R2_BUCKET_NAME: process.env.CLOUDFLARE_R2_BUCKET_NAME,
CLOUDFLARE_R2_ENDPOINT: process.env.CLOUDFLARE_R2_ENDPOINT,
RESEND_API_KEY: process.env.RESEND_API_KEY,
RESEND_FROM_EMAIL: process.env.RESEND_FROM_EMAIL,
SENTRY_DSN: process.env.SENTRY_DSN,
// 客户端变量
NEXT_PUBLIC_APP_URL: process.env.NEXT_PUBLIC_APP_URL,
NEXT_PUBLIC_APP_NAME: process.env.NEXT_PUBLIC_APP_NAME,
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY,
NEXT_PUBLIC_CLERK_SIGN_IN_URL: process.env.NEXT_PUBLIC_CLERK_SIGN_IN_URL,
NEXT_PUBLIC_CLERK_SIGN_UP_URL: process.env.NEXT_PUBLIC_CLERK_SIGN_UP_URL,
NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL: process.env.NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL,
NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL: process.env.NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL,
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY,
NEXT_PUBLIC_GOOGLE_ANALYTICS_ID: process.env.NEXT_PUBLIC_GOOGLE_ANALYTICS_ID,
},
// 跳过环境变量验证的条件
skipValidation: !!process.env.SKIP_ENV_VALIDATION,
})
export type Env = typeof env
应用配置结构
项目采用模块化的配置管理方式,将不同类型的配置分别管理,确保可维护性和扩展性。
应用基础配置
// src/config/app.ts
import { env } from '@/env'
export const appConfig = {
name: env.NEXT_PUBLIC_APP_NAME,
url: env.NEXT_PUBLIC_APP_URL,
description: '基于 Next.js 15 和现代技术栈构建的 AI SaaS 应用模板',
// 应用元数据
meta: {
title: 'AI SaaS Template - 现代化 AI SaaS 解决方案',
description: '使用 Next.js 15、React 19、tRPC 和 Clerk Auth 构建现代 AI SaaS 应用',
keywords: ['AI', 'SaaS', 'Next.js', 'TypeScript', 'tRPC', 'Clerk', 'OpenAI'],
author: 'AI SaaS Template',
version: '2.0.0',
},
// 社交媒体链接
social: {
github: 'https://github.com/geallenboy/ai-saas-template',
twitter: '@ai_saas_template',
},
// 联系方式
contact: {
email: '[email protected]',
support: '[email protected]',
},
// 默认设置
defaults: {
locale: 'zh',
theme: 'system',
currency: 'USD',
timezone: 'UTC',
},
// 功能开关
features: {
auth: true,
payments: env.STRIPE_SECRET_KEY ? true : false,
ai: env.OPENAI_API_KEY || env.ANTHROPIC_API_KEY ? true : false,
analytics: env.NEXT_PUBLIC_GOOGLE_ANALYTICS_ID ? true : false,
redis: env.UPSTASH_REDIS_REST_URL ? true : false,
},
} as const
export type AppConfig = typeof appConfig
tRPC 配置
// src/lib/trpc/server.ts
import { z } from 'zod'
import { createTRPCRouter, protectedProcedure, publicProcedure } from './trpc'
import { env } from '@/env'
// tRPC 服务器配置
export const trpcConfig = {
// API 路径
apiPath: '/api/trpc',
// 超时设置
timeout: 30000,
// 限流配置
rateLimit: {
enabled: env.NODE_ENV === 'production',
requests: 100,
window: '15m',
},
// 错误处理
errorHandling: {
includeStack: env.NODE_ENV === 'development',
logErrors: true,
},
} as const
Clerk 认证配置
// src/lib/clerk.ts
import { ClerkProvider } from '@clerk/nextjs'
import { env } from '@/env'
export const clerkConfig = {
publishableKey: env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY,
secretKey: env.CLERK_SECRET_KEY,
// 路由配置
signInUrl: env.NEXT_PUBLIC_CLERK_SIGN_IN_URL,
signUpUrl: env.NEXT_PUBLIC_CLERK_SIGN_UP_URL,
afterSignInUrl: env.NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL,
afterSignUpUrl: env.NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL,
// 主题配置
appearance: {
elements: {
formButtonPrimary: 'bg-primary text-primary-foreground hover:bg-primary/90',
card: 'bg-card',
headerTitle: 'text-foreground',
headerSubtitle: 'text-muted-foreground',
},
},
// 本地化配置
localization: {
locale: 'zh-CN',
},
} as const
AI 服务配置
// src/lib/ai/config.ts
import { env } from '@/env'
export const aiConfig = {
// OpenAI 配置
openai: {
enabled: !!env.OPENAI_API_KEY,
apiKey: env.OPENAI_API_KEY,
organization: env.OPENAI_ORGANIZATION,
defaultModel: 'gpt-4-turbo-preview',
maxTokens: 4000,
temperature: 0.7,
},
// Anthropic Claude 配置
anthropic: {
enabled: !!env.ANTHROPIC_API_KEY,
apiKey: env.ANTHROPIC_API_KEY,
defaultModel: 'claude-3-sonnet-20240229',
maxTokens: 4000,
temperature: 0.7,
},
// Google AI 配置
googleAI: {
enabled: !!env.GOOGLE_AI_API_KEY,
apiKey: env.GOOGLE_AI_API_KEY,
defaultModel: 'gemini-pro',
maxTokens: 4000,
temperature: 0.7,
},
// XAI 配置
xai: {
enabled: !!env.XAI_API_KEY,
apiKey: env.XAI_API_KEY,
defaultModel: 'grok-beta',
maxTokens: 4000,
temperature: 0.7,
},
// 通用设置
common: {
defaultProvider: 'openai',
timeout: 30000,
retries: 3,
streaming: true,
},
} as const
export type AIConfig = typeof aiConfig
数据库配置
// src/lib/db/config.ts
import { env } from '@/env'
import { drizzle } from 'drizzle-orm/postgres-js'
import postgres from 'postgres'
// 数据库连接配置
export const dbConfig = {
url: env.DATABASE_URL,
// 连接池配置
connection: {
max: 20,
idle_timeout: 20,
connect_timeout: 10,
},
// SSL 配置
ssl: env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false,
// 日志配置
logging: env.NODE_ENV === 'development',
// 并行连接数
prepare: false,
} as const
// 创建数据库实例
const client = postgres(env.DATABASE_URL, dbConfig.connection)
export const db = drizzle(client, {
logger: dbConfig.logging,
})
export type Database = typeof db
Stripe 支付配置
// src/lib/stripe/config.ts
import { env } from '@/env'
import Stripe from 'stripe'
export const stripeConfig = {
enabled: !!env.STRIPE_SECRET_KEY,
secretKey: env.STRIPE_SECRET_KEY!,
publishableKey: env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!,
webhookSecret: env.STRIPE_WEBHOOK_SECRET!,
// API 版本
apiVersion: '2023-10-16' as Stripe.LatestApiVersion,
// 货币配置
currency: 'usd',
// 订阅计划配置
plans: {
basic: {
name: 'Basic Plan',
price: 9.99,
interval: 'month' as const,
features: [
'Up to 5 projects',
'10GB storage',
'Email support',
'Basic analytics',
],
limits: {
projects: 5,
storage: 10 * 1024 * 1024 * 1024, // 10GB
aiRequests: 1000,
},
},
pro: {
name: 'Pro Plan',
price: 29.99,
interval: 'month' as const,
features: [
'Unlimited projects',
'100GB storage',
'Priority support',
'Advanced analytics',
'Team collaboration',
],
limits: {
projects: -1, // unlimited
storage: 100 * 1024 * 1024 * 1024, // 100GB
aiRequests: 10000,
},
},
},
// Webhook 事件配置
webhookEvents: [
'checkout.session.completed',
'customer.subscription.created',
'customer.subscription.updated',
'customer.subscription.deleted',
'invoice.payment_succeeded',
'invoice.payment_failed',
],
} as const
export type StripeConfig = typeof stripeConfig
配置管理最佳实践
配置加载和验证
// src/lib/config/index.ts
import { env } from '@/env'
import { appConfig } from './app'
import { stripeConfig } from './stripe'
import { aiConfig } from './ai'
// 统一的配置导出
export const config = {
app: appConfig,
stripe: stripeConfig,
ai: aiConfig,
env,
} as const
// 配置验证工具
export function validateRequiredConfig() {
const requiredEnvVars = [
'DATABASE_URL',
'NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY',
'CLERK_SECRET_KEY',
]
const missing = requiredEnvVars.filter(
(key) => !process.env[key]
)
if (missing.length > 0) {
throw new Error(
`Missing required environment variables: ${missing.join(', ')}`
)
}
}
// 在应用启动时调用
if (typeof window === 'undefined') {
validateRequiredConfig()
}
export type Config = typeof config
配置使用示例
// 在组件中使用配置
import { config } from '@/lib/config'
import { env } from '@/env'
// 检查功能是否启用
function PaymentButton() {
if (!config.stripe.enabled) {
return null
}
return (
<button onClick={handlePayment}>
Subscribe to {config.stripe.plans.basic.name}
</button>
)
}
// 在 API 路由中使用配置
import { env } from '@/env'
import { OpenAI } from 'openai'
export async function POST() {
if (!env.OPENAI_API_KEY) {
return new Response('OpenAI not configured', { status: 500 })
}
const openai = new OpenAI({
apiKey: env.OPENAI_API_KEY,
})
// ... AI 调用逻辑
}
// 在服务器组件中使用配置
export default function HomePage() {
return (
<div>
<h1>{config.app.meta.title}</h1>
<p>{config.app.description}</p>
{config.app.features.payments && (
<PricingSection />
)}
{config.app.features.ai && (
<AIFeaturesSection />
)}
</div>
)
}
环境特定配置
// 开发环境配置
if (env.NODE_ENV === 'development') {
// 启用详细日志
console.log('Development mode enabled')
// 禁用限流
// 启用调试模式
}
// 生产环境配置
if (env.NODE_ENV === 'production') {
// 启用错误监控
// 启用性能监控
// 启用限流
// 禁用调试信息
console.log = () => {}
console.warn = () => {}
}
// 测试环境配置
if (env.NODE_ENV === 'test') {
// 使用模拟数据
// 禁用外部服务调用
// 加速执行
}
常见配置问题
环境变量缺失
# 检查必需的环境变量是否设置
echo $DATABASE_URL
echo $NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY
echo $CLERK_SECRET_KEY
# 复制模板文件
cp .env.example .env.local
# 编辑环境变量
vim .env.local
配置验证错误
// 在应用启动时检查配置
try {
const env = createEnv({...})
console.log('配置验证成功')
} catch (error) {
console.error('配置验证失败:', error.message)
process.exit(1)
}
生产环境部署
# Vercel 部署时设置环境变量
vercel env add DATABASE_URL
vercel env add CLERK_SECRET_KEY
vercel env add STRIPE_SECRET_KEY
# 或者在 Vercel 控制台设置
# Settings -> Environment Variables
安全最佳实践
环境变量安全
-
敏感信息保护
- 绝不提交
.env.local
文件 - 使用
.env.example
作为模板 - 定期轮换 API 密钥
- 绝不提交
-
数据库安全
- 使用强密码
- 启用 SSL 连接
- 限制数据库访问 IP
-
API 密钥管理
- 使用最小权限原则
- 定期检查和更新 API 密钥
- 监控 API 使用情况
// 安全的配置加载
const secretKeys = [
'CLERK_SECRET_KEY',
'STRIPE_SECRET_KEY',
'OPENAI_API_KEY',
]
// 仅在服务器端使用敏感配置
if (typeof window === 'undefined') {
// 服务器端配置
const secrets = secretKeys.reduce((acc, key) => {
if (process.env[key]) {
acc[key] = process.env[key]
}
return acc
}, {} as Record<string, string>)
}
维护和监控
配置更新流程
-
更新环境变量
# 更新本地配置 vim .env.local # 重启开发服务器 npm run dev
-
更新生产配置
- 在部署平台更新环境变量
- 触发重新部署
- 验证配置是否正常生效
-
配置监控
// 监控关键配置状态 export function checkConfigHealth() { const checks = { database: !!env.DATABASE_URL, auth: !!env.CLERK_SECRET_KEY, payments: !!env.STRIPE_SECRET_KEY, ai: !!(env.OPENAI_API_KEY || env.ANTHROPIC_API_KEY), } return checks }
这个更新后的配置管理文档完全基于当前项目的技术架构,包括 Clerk Auth、tRPC、Stripe 支付、多 AI 服务提供商等现代化的配置管理方式。