config-manager
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseConfiguration Manager Skill
配置管理Skill
Overview
概述
This skill helps you design and implement robust configuration management for applications. Covers environment variables, config files, secrets management, validation, type safety, and multi-environment deployments.
本Skill可帮助你设计并实现健壮的应用配置管理方案,涵盖环境变量、配置文件、密钥管理、验证、类型安全以及多环境部署等内容。
Configuration Philosophy
配置理念
Twelve-Factor App Principles
十二要素应用原则
- Store config in the environment: Separate config from code
- Strict separation: Config that varies between deploys should be in env vars
- No secrets in code: Ever. Period.
- 将配置存储在环境中:将配置与代码分离
- 严格分离:不同部署环境间有差异的配置应放在环境变量中
- 代码中绝不包含密钥:永远不要这么做,没有例外。
Configuration Hierarchy
配置优先级(从高到低)
Priority (highest to lowest):
1. Command-line arguments
2. Environment variables
3. Environment-specific config files (.env.local)
4. Default config files (.env)
5. Application defaults in codePriority (highest to lowest):
1. Command-line arguments
2. Environment variables
3. Environment-specific config files (.env.local)
4. Default config files (.env)
5. Application defaults in codeWhat Goes Where
配置内容分类
- Environment Variables: API keys, database URLs, feature flags
- Config Files: Non-sensitive defaults, complex structures
- Secrets Manager: Production credentials, API tokens
- Code: Application logic, never secrets
- 环境变量:API密钥、数据库URL、功能开关
- 配置文件:非敏感默认值、复杂结构
- 密钥管理器:生产环境凭证、API令牌
- 代码:应用逻辑,绝不包含密钥
Environment Variables
环境变量
.env File Structure
.env文件结构
bash
undefinedbash
undefined.env.example - Commit this file as documentation
.env.example - Commit this file as documentation
Copy to .env and fill in values
Copy to .env and fill in values
===================
===================
Application
Application
===================
===================
NODE_ENV=development
PORT=3000
APP_URL=http://localhost:3000
NODE_ENV=development
PORT=3000
APP_URL=http://localhost:3000
===================
===================
Database
Database
===================
===================
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
DATABASE_POOL_SIZE=10
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
DATABASE_POOL_SIZE=10
===================
===================
Authentication
Authentication
===================
===================
Get from Supabase Dashboard > Settings > API
Get from Supabase Dashboard > Settings > API
NEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key
NEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key
===================
===================
External Services
External Services
===================
===================
STRIPE_SECRET_KEY=sk_test_xxx
STRIPE_WEBHOOK_SECRET=whsec_xxx
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_xxx
STRIPE_SECRET_KEY=sk_test_xxx
STRIPE_WEBHOOK_SECRET=whsec_xxx
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_xxx
===================
===================
===================
===================
RESEND_API_KEY=re_xxx
EMAIL_FROM=noreply@example.com
RESEND_API_KEY=re_xxx
EMAIL_FROM=noreply@example.com
===================
===================
Feature Flags
Feature Flags
===================
===================
ENABLE_ANALYTICS=true
ENABLE_BETA_FEATURES=false
undefinedENABLE_ANALYTICS=true
ENABLE_BETA_FEATURES=false
undefined.gitignore Configuration
.gitignore配置
gitignore
undefinedgitignore
undefinedEnvironment files
Environment files
.env
.env.local
.env.*.local
.env.development.local
.env.test.local
.env.production.local
.env
.env.local
.env.*.local
.env.development.local
.env.test.local
.env.production.local
Keep example file
Keep example file
!.env.example
undefined!.env.example
undefinedMulti-Environment Setup
多环境设置
bash
undefinedbash
undefined.env.development
.env.development
NODE_ENV=development
DATABASE_URL=postgresql://localhost:5432/myapp_dev
LOG_LEVEL=debug
NODE_ENV=development
DATABASE_URL=postgresql://localhost:5432/myapp_dev
LOG_LEVEL=debug
.env.test
.env.test
NODE_ENV=test
DATABASE_URL=postgresql://localhost:5432/myapp_test
LOG_LEVEL=error
NODE_ENV=test
DATABASE_URL=postgresql://localhost:5432/myapp_test
LOG_LEVEL=error
.env.production
.env.production
NODE_ENV=production
DATABASE_URL=${DATABASE_URL} # Set in deployment platform
LOG_LEVEL=info
undefinedNODE_ENV=production
DATABASE_URL=${DATABASE_URL} # Set in deployment platform
LOG_LEVEL=info
undefinedType-Safe Configuration
类型安全配置
Zod Schema Validation
Zod Schema验证
typescript
// src/config/env.ts
import { z } from 'zod';
// Define schema
const envSchema = z.object({
// Node environment
NODE_ENV: z.enum(['development', 'test', 'production']).default('development'),
// Server
PORT: z.coerce.number().default(3000),
APP_URL: z.string().url(),
// Database
DATABASE_URL: z.string().url(),
DATABASE_POOL_SIZE: z.coerce.number().min(1).max(100).default(10),
// Supabase
NEXT_PUBLIC_SUPABASE_URL: z.string().url(),
NEXT_PUBLIC_SUPABASE_ANON_KEY: z.string().min(1),
SUPABASE_SERVICE_ROLE_KEY: z.string().min(1),
// Stripe (optional in development)
STRIPE_SECRET_KEY: z.string().startsWith('sk_').optional(),
STRIPE_WEBHOOK_SECRET: z.string().startsWith('whsec_').optional(),
// Feature flags
ENABLE_ANALYTICS: z.coerce.boolean().default(false),
ENABLE_BETA_FEATURES: z.coerce.boolean().default(false),
});
// Type export
export type Env = z.infer<typeof envSchema>;
// Parse and validate
function loadEnv(): Env {
const result = envSchema.safeParse(process.env);
if (!result.success) {
console.error('Invalid environment variables:');
console.error(result.error.format());
process.exit(1);
}
return result.data;
}
// Singleton export
export const env = loadEnv();typescript
// src/config/env.ts
import { z } from 'zod';
// Define schema
const envSchema = z.object({
// Node environment
NODE_ENV: z.enum(['development', 'test', 'production']).default('development'),
// Server
PORT: z.coerce.number().default(3000),
APP_URL: z.string().url(),
// Database
DATABASE_URL: z.string().url(),
DATABASE_POOL_SIZE: z.coerce.number().min(1).max(100).default(10),
// Supabase
NEXT_PUBLIC_SUPABASE_URL: z.string().url(),
NEXT_PUBLIC_SUPABASE_ANON_KEY: z.string().min(1),
SUPABASE_SERVICE_ROLE_KEY: z.string().min(1),
// Stripe (optional in development)
STRIPE_SECRET_KEY: z.string().startsWith('sk_').optional(),
STRIPE_WEBHOOK_SECRET: z.string().startsWith('whsec_').optional(),
// Feature flags
ENABLE_ANALYTICS: z.coerce.boolean().default(false),
ENABLE_BETA_FEATURES: z.coerce.boolean().default(false),
});
// Type export
export type Env = z.infer<typeof envSchema>;
// Parse and validate
function loadEnv(): Env {
const result = envSchema.safeParse(process.env);
if (!result.success) {
console.error('Invalid environment variables:');
console.error(result.error.format());
process.exit(1);
}
return result.data;
}
// Singleton export
export const env = loadEnv();Environment Validation at Build Time
构建时环境验证
typescript
// src/config/validate-env.ts
import { z } from 'zod';
// Client-side env (exposed to browser)
const clientEnvSchema = z.object({
NEXT_PUBLIC_SUPABASE_URL: z.string().url(),
NEXT_PUBLIC_SUPABASE_ANON_KEY: z.string().min(1),
NEXT_PUBLIC_APP_URL: z.string().url(),
});
// Server-side env (never exposed to browser)
const serverEnvSchema = z.object({
DATABASE_URL: z.string(),
SUPABASE_SERVICE_ROLE_KEY: z.string(),
STRIPE_SECRET_KEY: z.string().optional(),
});
// Combined
const envSchema = clientEnvSchema.merge(serverEnvSchema);
export function validateEnv() {
// Only validate server-side env on server
if (typeof window !== 'undefined') {
return clientEnvSchema.parse({
NEXT_PUBLIC_SUPABASE_URL: process.env.NEXT_PUBLIC_SUPABASE_URL,
NEXT_PUBLIC_SUPABASE_ANON_KEY: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
NEXT_PUBLIC_APP_URL: process.env.NEXT_PUBLIC_APP_URL,
});
}
return envSchema.parse(process.env);
}typescript
// src/config/validate-env.ts
import { z } from 'zod';
// Client-side env (exposed to browser)
const clientEnvSchema = z.object({
NEXT_PUBLIC_SUPABASE_URL: z.string().url(),
NEXT_PUBLIC_SUPABASE_ANON_KEY: z.string().min(1),
NEXT_PUBLIC_APP_URL: z.string().url(),
});
// Server-side env (never exposed to browser)
const serverEnvSchema = z.object({
DATABASE_URL: z.string(),
SUPABASE_SERVICE_ROLE_KEY: z.string(),
STRIPE_SECRET_KEY: z.string().optional(),
});
// Combined
const envSchema = clientEnvSchema.merge(serverEnvSchema);
export function validateEnv() {
// Only validate server-side env on server
if (typeof window !== 'undefined') {
return clientEnvSchema.parse({
NEXT_PUBLIC_SUPABASE_URL: process.env.NEXT_PUBLIC_SUPABASE_URL,
NEXT_PUBLIC_SUPABASE_ANON_KEY: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
NEXT_PUBLIC_APP_URL: process.env.NEXT_PUBLIC_APP_URL,
});
}
return envSchema.parse(process.env);
}T3 Env Pattern (Next.js)
T3 Env模式(Next.js)
typescript
// src/env.mjs
import { createEnv } from '@t3-oss/env-nextjs';
import { z } from 'zod';
export const env = createEnv({
/**
* Server-side environment variables
*/
server: {
DATABASE_URL: z.string().url(),
NODE_ENV: z.enum(['development', 'test', 'production']),
SUPABASE_SERVICE_ROLE_KEY: z.string(),
STRIPE_SECRET_KEY: z.string().startsWith('sk_'),
},
/**
* Client-side environment variables (exposed to browser)
* Prefix with NEXT_PUBLIC_
*/
client: {
NEXT_PUBLIC_SUPABASE_URL: z.string().url(),
NEXT_PUBLIC_SUPABASE_ANON_KEY: z.string(),
NEXT_PUBLIC_APP_URL: z.string().url(),
},
/**
* Runtime values
*/
runtimeEnv: {
DATABASE_URL: process.env.DATABASE_URL,
NODE_ENV: process.env.NODE_ENV,
SUPABASE_SERVICE_ROLE_KEY: process.env.SUPABASE_SERVICE_ROLE_KEY,
STRIPE_SECRET_KEY: process.env.STRIPE_SECRET_KEY,
NEXT_PUBLIC_SUPABASE_URL: process.env.NEXT_PUBLIC_SUPABASE_URL,
NEXT_PUBLIC_SUPABASE_ANON_KEY: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
NEXT_PUBLIC_APP_URL: process.env.NEXT_PUBLIC_APP_URL,
},
/**
* Skip validation in certain environments
*/
skipValidation: !!process.env.SKIP_ENV_VALIDATION,
});typescript
// src/env.mjs
import { createEnv } from '@t3-oss/env-nextjs';
import { z } from 'zod';
export const env = createEnv({
/**
* Server-side environment variables
*/
server: {
DATABASE_URL: z.string().url(),
NODE_ENV: z.enum(['development', 'test', 'production']),
SUPABASE_SERVICE_ROLE_KEY: z.string(),
STRIPE_SECRET_KEY: z.string().startsWith('sk_'),
},
/**
* Client-side environment variables (exposed to browser)
* Prefix with NEXT_PUBLIC_
*/
client: {
NEXT_PUBLIC_SUPABASE_URL: z.string().url(),
NEXT_PUBLIC_SUPABASE_ANON_KEY: z.string(),
NEXT_PUBLIC_APP_URL: z.string().url(),
},
/**
* Runtime values
*/
runtimeEnv: {
DATABASE_URL: process.env.DATABASE_URL,
NODE_ENV: process.env.NODE_ENV,
SUPABASE_SERVICE_ROLE_KEY: process.env.SUPABASE_SERVICE_ROLE_KEY,
STRIPE_SECRET_KEY: process.env.STRIPE_SECRET_KEY,
NEXT_PUBLIC_SUPABASE_URL: process.env.NEXT_PUBLIC_SUPABASE_URL,
NEXT_PUBLIC_SUPABASE_ANON_KEY: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
NEXT_PUBLIC_APP_URL: process.env.NEXT_PUBLIC_APP_URL,
},
/**
* Skip validation in certain environments
*/
skipValidation: !!process.env.SKIP_ENV_VALIDATION,
});Configuration Files
配置文件
JSON Configuration
JSON配置
typescript
// config/default.json
{
"app": {
"name": "My Application",
"version": "1.0.0"
},
"server": {
"port": 3000,
"host": "localhost"
},
"cache": {
"ttl": 3600,
"maxSize": 1000
},
"features": {
"darkMode": true,
"betaFeatures": false
}
}
// config/production.json (overrides default)
{
"server": {
"host": "0.0.0.0"
},
"cache": {
"ttl": 86400
}
}typescript
// config/default.json
{
"app": {
"name": "My Application",
"version": "1.0.0"
},
"server": {
"port": 3000,
"host": "localhost"
},
"cache": {
"ttl": 3600,
"maxSize": 1000
},
"features": {
"darkMode": true,
"betaFeatures": false
}
}
// config/production.json (overrides default)
{
"server": {
"host": "0.0.0.0"
},
"cache": {
"ttl": 86400
}
}Config Loader
配置加载器
typescript
// src/config/loader.ts
import { readFileSync, existsSync } from 'fs';
import { join } from 'path';
import { z } from 'zod';
const configSchema = z.object({
app: z.object({
name: z.string(),
version: z.string(),
}),
server: z.object({
port: z.number(),
host: z.string(),
}),
cache: z.object({
ttl: z.number(),
maxSize: z.number(),
}),
features: z.object({
darkMode: z.boolean(),
betaFeatures: z.boolean(),
}),
});
type Config = z.infer<typeof configSchema>;
function deepMerge(target: any, source: any): any {
const result = { ...target };
for (const key of Object.keys(source)) {
if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
result[key] = deepMerge(result[key] || {}, source[key]);
} else {
result[key] = source[key];
}
}
return result;
}
export function loadConfig(): Config {
const env = process.env.NODE_ENV || 'development';
const configDir = join(process.cwd(), 'config');
// Load default config
const defaultPath = join(configDir, 'default.json');
let config = JSON.parse(readFileSync(defaultPath, 'utf-8'));
// Merge environment-specific config
const envPath = join(configDir, `${env}.json`);
if (existsSync(envPath)) {
const envConfig = JSON.parse(readFileSync(envPath, 'utf-8'));
config = deepMerge(config, envConfig);
}
// Merge local overrides (not committed)
const localPath = join(configDir, 'local.json');
if (existsSync(localPath)) {
const localConfig = JSON.parse(readFileSync(localPath, 'utf-8'));
config = deepMerge(config, localConfig);
}
// Validate
return configSchema.parse(config);
}
export const config = loadConfig();typescript
// src/config/loader.ts
import { readFileSync, existsSync } from 'fs';
import { join } from 'path';
import { z } from 'zod';
const configSchema = z.object({
app: z.object({
name: z.string(),
version: z.string(),
}),
server: z.object({
port: z.number(),
host: z.string(),
}),
cache: z.object({
ttl: z.number(),
maxSize: z.number(),
}),
features: z.object({
darkMode: z.boolean(),
betaFeatures: z.boolean(),
}),
});
type Config = z.infer<typeof configSchema>;
function deepMerge(target: any, source: any): any {
const result = { ...target };
for (const key of Object.keys(source)) {
if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
result[key] = deepMerge(result[key] || {}, source[key]);
} else {
result[key] = source[key];
}
}
return result;
}
export function loadConfig(): Config {
const env = process.env.NODE_ENV || 'development';
const configDir = join(process.cwd(), 'config');
// Load default config
const defaultPath = join(configDir, 'default.json');
let config = JSON.parse(readFileSync(defaultPath, 'utf-8'));
// Merge environment-specific config
const envPath = join(configDir, `${env}.json`);
if (existsSync(envPath)) {
const envConfig = JSON.parse(readFileSync(envPath, 'utf-8'));
config = deepMerge(config, envConfig);
}
// Merge local overrides (not committed)
const localPath = join(configDir, 'local.json');
if (existsSync(localPath)) {
const localConfig = JSON.parse(readFileSync(localPath, 'utf-8'));
config = deepMerge(config, localConfig);
}
// Validate
return configSchema.parse(config);
}
export const config = loadConfig();Secrets Management
密钥管理
Local Development Secrets
本地开发密钥
bash
undefinedbash
undefinedUse a secrets manager locally too
Use a secrets manager locally too
Option 1: 1Password CLI
Option 1: 1Password CLI
op read "op://Development/MyApp/API_KEY"
op read "op://Development/MyApp/API_KEY"
Option 2: Doppler
Option 2: Doppler
doppler secrets download --no-file --format env > .env
doppler secrets download --no-file --format env > .env
Option 3: AWS SSM (for local AWS dev)
Option 3: AWS SSM (for local AWS dev)
aws ssm get-parameter --name "/myapp/dev/api-key" --with-decryption --query "Parameter.Value"
undefinedaws ssm get-parameter --name "/myapp/dev/api-key" --with-decryption --query "Parameter.Value"
undefinedProduction Secrets with Vercel
Vercel生产环境密钥
bash
undefinedbash
undefinedAdd secrets via CLI
Add secrets via CLI
vercel env add STRIPE_SECRET_KEY production
vercel env add DATABASE_URL production
vercel env add STRIPE_SECRET_KEY production
vercel env add DATABASE_URL production
Pull secrets to local .env
Pull secrets to local .env
vercel env pull .env.local
undefinedvercel env pull .env.local
undefinedSecrets in Docker
Docker中的密钥
yaml
undefinedyaml
undefineddocker-compose.yml
docker-compose.yml
version: '3.8'
services:
app:
build: .
environment:
- NODE_ENV=production
env_file:
- .env
secrets:
- db_password
- api_key
secrets:
db_password:
file: ./secrets/db_password.txt
api_key:
external: true # From Docker Swarm/secrets manager
undefinedversion: '3.8'
services:
app:
build: .
environment:
- NODE_ENV=production
env_file:
- .env
secrets:
- db_password
- api_key
secrets:
db_password:
file: ./secrets/db_password.txt
api_key:
external: true # From Docker Swarm/secrets manager
undefinedAWS Secrets Manager Integration
AWS Secrets Manager集成
typescript
// src/config/secrets.ts
import {
SecretsManagerClient,
GetSecretValueCommand,
} from '@aws-sdk/client-secrets-manager';
const client = new SecretsManagerClient({ region: 'us-east-1' });
interface AppSecrets {
database_url: string;
stripe_secret_key: string;
jwt_secret: string;
}
let cachedSecrets: AppSecrets | null = null;
export async function getSecrets(): Promise<AppSecrets> {
if (cachedSecrets) {
return cachedSecrets;
}
const secretId = process.env.AWS_SECRET_ID || 'myapp/production/secrets';
const command = new GetSecretValueCommand({ SecretId: secretId });
const response = await client.send(command);
if (!response.SecretString) {
throw new Error('Secret not found');
}
cachedSecrets = JSON.parse(response.SecretString);
return cachedSecrets!;
}
// Usage
async function connectDatabase() {
const secrets = await getSecrets();
return createConnection(secrets.database_url);
}typescript
// src/config/secrets.ts
import {
SecretsManagerClient,
GetSecretValueCommand,
} from '@aws-sdk/client-secrets-manager';
const client = new SecretsManagerClient({ region: 'us-east-1' });
interface AppSecrets {
database_url: string;
stripe_secret_key: string;
jwt_secret: string;
}
let cachedSecrets: AppSecrets | null = null;
export async function getSecrets(): Promise<AppSecrets> {
if (cachedSecrets) {
return cachedSecrets;
}
const secretId = process.env.AWS_SECRET_ID || 'myapp/production/secrets';
const command = new GetSecretValueCommand({ SecretId: secretId });
const response = await client.send(command);
if (!response.SecretString) {
throw new Error('Secret not found');
}
cachedSecrets = JSON.parse(response.SecretString);
return cachedSecrets!;
}
// Usage
async function connectDatabase() {
const secrets = await getSecrets();
return createConnection(secrets.database_url);
}Feature Flags
功能开关
Simple Feature Flag System
简单功能开关系统
typescript
// src/config/features.ts
import { env } from './env';
export const features = {
analytics: env.ENABLE_ANALYTICS,
betaFeatures: env.ENABLE_BETA_FEATURES,
darkMode: true, // Always enabled
// Computed flags
get isProduction() {
return env.NODE_ENV === 'production';
},
get enableDebugLogs() {
return env.NODE_ENV === 'development' || env.DEBUG === 'true';
},
} as const;
// Type-safe feature checking
export function isEnabled(feature: keyof typeof features): boolean {
return Boolean(features[feature]);
}
// Usage
if (isEnabled('analytics')) {
initAnalytics();
}typescript
// src/config/features.ts
import { env } from './env';
export const features = {
analytics: env.ENABLE_ANALYTICS,
betaFeatures: env.ENABLE_BETA_FEATURES,
darkMode: true, // Always enabled
// Computed flags
get isProduction() {
return env.NODE_ENV === 'production';
},
get enableDebugLogs() {
return env.NODE_ENV === 'development' || env.DEBUG === 'true';
},
} as const;
// Type-safe feature checking
export function isEnabled(feature: keyof typeof features): boolean {
return Boolean(features[feature]);
}
// Usage
if (isEnabled('analytics')) {
initAnalytics();
}Environment-Aware Feature Flags
环境感知型功能开关
typescript
// src/config/feature-flags.ts
type Environment = 'development' | 'staging' | 'production';
interface FeatureConfig {
enabled: boolean | Environment[];
description: string;
}
const featureFlags: Record<string, FeatureConfig> = {
newCheckout: {
enabled: ['development', 'staging'],
description: 'New checkout flow',
},
aiAssistant: {
enabled: true,
description: 'AI assistant feature',
},
experimentalApi: {
enabled: ['development'],
description: 'Experimental API endpoints',
},
};
export function isFeatureEnabled(
feature: keyof typeof featureFlags
): boolean {
const config = featureFlags[feature];
const currentEnv = process.env.NODE_ENV as Environment;
if (typeof config.enabled === 'boolean') {
return config.enabled;
}
return config.enabled.includes(currentEnv);
}typescript
// src/config/feature-flags.ts
type Environment = 'development' | 'staging' | 'production';
interface FeatureConfig {
enabled: boolean | Environment[];
description: string;
}
const featureFlags: Record<string, FeatureConfig> = {
newCheckout: {
enabled: ['development', 'staging'],
description: 'New checkout flow',
},
aiAssistant: {
enabled: true,
description: 'AI assistant feature',
},
experimentalApi: {
enabled: ['development'],
description: 'Experimental API endpoints',
},
};
export function isFeatureEnabled(
feature: keyof typeof featureFlags
): boolean {
const config = featureFlags[feature];
const currentEnv = process.env.NODE_ENV as Environment;
if (typeof config.enabled === 'boolean') {
return config.enabled;
}
return config.enabled.includes(currentEnv);
}Configuration Patterns
配置模式
Singleton Configuration
单例配置
typescript
// src/config/index.ts
import { env } from './env';
import { loadConfig } from './loader';
import { features } from './features';
class AppConfig {
private static instance: AppConfig;
public readonly env = env;
public readonly features = features;
public readonly settings = loadConfig();
private constructor() {
// Freeze to prevent modifications
Object.freeze(this);
}
static getInstance(): AppConfig {
if (!AppConfig.instance) {
AppConfig.instance = new AppConfig();
}
return AppConfig.instance;
}
// Helper methods
get isDevelopment(): boolean {
return this.env.NODE_ENV === 'development';
}
get isProduction(): boolean {
return this.env.NODE_ENV === 'production';
}
get databaseUrl(): string {
return this.env.DATABASE_URL;
}
}
export const config = AppConfig.getInstance();typescript
// src/config/index.ts
import { env } from './env';
import { loadConfig } from './loader';
import { features } from './features';
class AppConfig {
private static instance: AppConfig;
public readonly env = env;
public readonly features = features;
public readonly settings = loadConfig();
private constructor() {
// Freeze to prevent modifications
Object.freeze(this);
}
static getInstance(): AppConfig {
if (!AppConfig.instance) {
AppConfig.instance = new AppConfig();
}
return AppConfig.instance;
}
// Helper methods
get isDevelopment(): boolean {
return this.env.NODE_ENV === 'development';
}
get isProduction(): boolean {
return this.env.NODE_ENV === 'production';
}
get databaseUrl(): string {
return this.env.DATABASE_URL;
}
}
export const config = AppConfig.getInstance();Runtime Configuration Updates
运行时配置更新
typescript
// src/config/runtime.ts
import { EventEmitter } from 'events';
class RuntimeConfig extends EventEmitter {
private values: Map<string, any> = new Map();
get<T>(key: string, defaultValue?: T): T | undefined {
return this.values.get(key) ?? defaultValue;
}
set<T>(key: string, value: T): void {
const oldValue = this.values.get(key);
this.values.set(key, value);
this.emit('change', { key, oldValue, newValue: value });
}
// Subscribe to changes
onChange(callback: (change: { key: string; oldValue: any; newValue: any }) => void) {
this.on('change', callback);
return () => this.off('change', callback);
}
}
export const runtimeConfig = new RuntimeConfig();
// Usage: Update config at runtime
runtimeConfig.set('maintenanceMode', true);
// Usage: React to changes
runtimeConfig.onChange(({ key, newValue }) => {
if (key === 'maintenanceMode' && newValue) {
showMaintenanceBanner();
}
});typescript
// src/config/runtime.ts
import { EventEmitter } from 'events';
class RuntimeConfig extends EventEmitter {
private values: Map<string, any> = new Map();
get<T>(key: string, defaultValue?: T): T | undefined {
return this.values.get(key) ?? defaultValue;
}
set<T>(key: string, value: T): void {
const oldValue = this.values.get(key);
this.values.set(key, value);
this.emit('change', { key, oldValue, newValue: value });
}
// Subscribe to changes
onChange(callback: (change: { key: string; oldValue: any; newValue: any }) => void) {
this.on('change', callback);
return () => this.off('change', callback);
}
}
export const runtimeConfig = new RuntimeConfig();
// Usage: Update config at runtime
runtimeConfig.set('maintenanceMode', true);
// Usage: React to changes
runtimeConfig.onChange(({ key, newValue }) => {
if (key === 'maintenanceMode' && newValue) {
showMaintenanceBanner();
}
});Validation Checklist
验证检查清单
Environment Setup
环境设置
- with all variables documented
.env.example - excludes all
.gitignorefiles except example.env - Validation runs at application startup
- Helpful error messages for missing/invalid config
- Type definitions for all config values
- 包含所有变量说明的文件
.env.example - 排除所有
.gitignore文件,仅保留示例文件.env - 应用启动时运行配置验证
- 针对缺失/无效配置提供清晰的错误提示
- 所有配置值的类型定义
Security
安全
- No secrets in version control
- Secrets use appropriate secrets manager
- Production secrets rotated regularly
- Minimal exposure of sensitive values in logs
- NEXT_PUBLIC_ prefix only for truly public values
- 版本控制中不包含任何密钥
- 使用合适的密钥管理器存储密钥
- 定期轮换生产环境密钥
- 日志中尽量减少敏感值的暴露
- 仅对真正公开的值使用NEXT_PUBLIC_前缀
Developer Experience
开发者体验
- Easy local setup (copy )
.env.example - Clear documentation of each variable
- Defaults for development environment
- Config validation gives actionable errors
- IDE autocomplete for config values
- 本地设置简单(复制即可)
.env.example - 每个变量都有清晰的说明文档
- 开发环境提供默认值
- 配置验证给出可操作的错误信息
- 配置值支持IDE自动补全
Multi-Environment
多环境
- Separate configs for dev/staging/production
- Environment-specific overrides work correctly
- Feature flags for gradual rollouts
- Easy switching between environments
- 为开发/预发布/生产环境分别配置独立的配置
- 环境特定的覆盖配置可正常工作
- 用于逐步发布的功能开关
- 可轻松在不同环境间切换
When to Use This Skill
何时使用本Skill
Invoke this skill when:
- Setting up configuration for a new project
- Adding new environment variables
- Implementing secrets management
- Creating feature flag systems
- Debugging configuration issues
- Setting up multi-environment deployments
- Migrating configuration between providers
- Implementing runtime configuration changes
在以下场景中调用本Skill:
- 为新项目设置配置
- 添加新的环境变量
- 实现密钥管理方案
- 创建功能开关系统
- 调试配置相关问题
- 设置多环境部署
- 在不同服务商之间迁移配置
- 实现运行时配置变更