env-config
About
The env-config skill helps developers manage environment variables and secrets across development, staging, and production environments within an SST-based monorepo. Use it for adding new secrets, updating stage-specific configurations, or debugging environment-related issues. It provides capabilities to read, edit, and grep environment files to maintain consistent configuration management.
Documentation
Environment Configuration Skill
This skill helps you manage environment variables and secrets across the monorepo.
When to Use This Skill
- Adding new environment variables
- Configuring stage-specific variables
- Setting up secrets for deployment
- Debugging environment variable issues
- Managing API keys and tokens
- Configuring database connections
- Setting up third-party integrations
Environment File Structure
sgcarstrends/
├── .env.example # Example env file (committed)
├── .env.local # Local development (not committed)
├── .env.test # Test environment (not committed)
├── apps/
│ ├── api/
│ │ ├── .env.example
│ │ └── .env.local
│ └── web/
│ ├── .env.example
│ └── .env.local
└── packages/
└── database/
├── .env.example
└── .env.local
Environment Variable Categories
Database
# PostgreSQL
DATABASE_URL=postgresql://user:password@host:5432/database
# Connection pooling (optional)
DATABASE_POOL_MIN=2
DATABASE_POOL_MAX=10
Redis
# Upstash Redis
UPSTASH_REDIS_REST_URL=https://your-instance.upstash.io
UPSTASH_REDIS_REST_TOKEN=your-token-here
AI Services
# Google Gemini
GOOGLE_GEMINI_API_KEY=your-api-key
GEMINI_MODEL=gemini-1.5-pro-latest
Workflows & Queues
# QStash
QSTASH_TOKEN=your-qstash-token
QSTASH_CURRENT_SIGNING_KEY=your-signing-key
QSTASH_NEXT_SIGNING_KEY=your-next-signing-key
Social Media
# Discord
DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/...
# Telegram
TELEGRAM_BOT_TOKEN=your-bot-token
TELEGRAM_CHAT_ID=your-chat-id
# Twitter
TWITTER_BEARER_TOKEN=your-bearer-token
# LinkedIn
LINKEDIN_ACCESS_TOKEN=your-access-token
LINKEDIN_ORG_ID=your-org-id
Storage
# Vercel Blob
BLOB_READ_WRITE_TOKEN=vercel_blob_token
Next.js Public Variables
# Public variables (exposed to browser)
NEXT_PUBLIC_API_URL=https://api.sgcarstrends.com
NEXT_PUBLIC_GA_MEASUREMENT_ID=G-XXXXXXXXXX
NEXT_PUBLIC_SITE_URL=https://sgcarstrends.com
Build & Runtime
# Node environment
NODE_ENV=development # development | production | test
# Next.js
NEXT_TELEMETRY_DISABLED=1
Environment File Examples
Root .env.example
# Database
DATABASE_URL=postgresql://user:password@localhost:5432/sgcarstrends
# Redis
UPSTASH_REDIS_REST_URL=https://your-instance.upstash.io
UPSTASH_REDIS_REST_TOKEN=your-token-here
# AI Services
GOOGLE_GEMINI_API_KEY=your-api-key
# Workflows
QSTASH_TOKEN=your-qstash-token
# Social Media
DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/...
TELEGRAM_BOT_TOKEN=your-bot-token
TELEGRAM_CHAT_ID=your-chat-id
# Storage
BLOB_READ_WRITE_TOKEN=vercel_blob_token
apps/web/.env.example
# API
NEXT_PUBLIC_API_URL=http://localhost:3000
# Database (for server components)
DATABASE_URL=postgresql://user:password@localhost:5432/sgcarstrends
# Redis
UPSTASH_REDIS_REST_URL=https://your-instance.upstash.io
UPSTASH_REDIS_REST_TOKEN=your-token-here
# Analytics
NEXT_PUBLIC_GA_MEASUREMENT_ID=G-XXXXXXXXXX
# Site URL
NEXT_PUBLIC_SITE_URL=http://localhost:3001
apps/api/.env.example
# Database
DATABASE_URL=postgresql://user:password@localhost:5432/sgcarstrends
# Redis
UPSTASH_REDIS_REST_URL=https://your-instance.upstash.io
UPSTASH_REDIS_REST_TOKEN=your-token-here
# All other API-specific vars...
Stage-Specific Configuration
Development
# .env.local (development)
NODE_ENV=development
DATABASE_URL=postgresql://localhost:5432/sgcarstrends_dev
NEXT_PUBLIC_API_URL=http://localhost:3000
NEXT_PUBLIC_SITE_URL=http://localhost:3001
Staging
# Staging (set in SST or CI/CD)
NODE_ENV=production
DATABASE_URL=postgresql://...staging...
NEXT_PUBLIC_API_URL=https://api.staging.sgcarstrends.com
NEXT_PUBLIC_SITE_URL=https://staging.sgcarstrends.com
Production
# Production (set in SST or CI/CD)
NODE_ENV=production
DATABASE_URL=postgresql://...production...
NEXT_PUBLIC_API_URL=https://api.sgcarstrends.com
NEXT_PUBLIC_SITE_URL=https://sgcarstrends.com
SST Configuration
Using Environment Variables in SST
// infra/api.ts
import { StackContext, Function } from "sst/constructs";
export function API({ stack, app }: StackContext) {
const api = new Function(stack, "api", {
handler: "apps/api/src/index.handler",
environment: {
NODE_ENV: app.stage === "production" ? "production" : "development",
DATABASE_URL: process.env.DATABASE_URL!,
UPSTASH_REDIS_REST_URL: process.env.UPSTASH_REDIS_REST_URL!,
UPSTASH_REDIS_REST_TOKEN: process.env.UPSTASH_REDIS_REST_TOKEN!,
GOOGLE_GEMINI_API_KEY: process.env.GOOGLE_GEMINI_API_KEY!,
QSTASH_TOKEN: process.env.QSTASH_TOKEN!,
DISCORD_WEBHOOK_URL: process.env.DISCORD_WEBHOOK_URL!,
TELEGRAM_BOT_TOKEN: process.env.TELEGRAM_BOT_TOKEN!,
TELEGRAM_CHAT_ID: process.env.TELEGRAM_CHAT_ID!,
},
});
return { api };
}
SST Secrets
For sensitive values, use SST secrets:
# Set secret for specific stage
npx sst secrets set DATABASE_URL "postgresql://..." --stage production
npx sst secrets set UPSTASH_REDIS_REST_TOKEN "token" --stage production
# List secrets
npx sst secrets list --stage production
# Remove secret
npx sst secrets remove DATABASE_URL --stage production
Access in code:
import { Config } from "sst/node/config";
export const handler = async () => {
const databaseUrl = Config.DATABASE_URL;
// Use secret...
};
Define in SST config:
// infra/api.ts
import { Config } from "sst/constructs";
export function API({ stack }: StackContext) {
const DATABASE_URL = new Config.Secret(stack, "DATABASE_URL");
const api = new Function(stack, "api", {
handler: "apps/api/src/index.handler",
bind: [DATABASE_URL],
});
}
TypeScript Type Safety
Declare Environment Variables
// env.d.ts (root or app-specific)
declare global {
namespace NodeJS {
interface ProcessEnv {
// Database
DATABASE_URL: string;
// Redis
UPSTASH_REDIS_REST_URL: string;
UPSTASH_REDIS_REST_TOKEN: string;
// AI
GOOGLE_GEMINI_API_KEY: string;
GEMINI_MODEL?: string;
// Workflows
QSTASH_TOKEN: string;
// Social Media
DISCORD_WEBHOOK_URL: string;
TELEGRAM_BOT_TOKEN: string;
TELEGRAM_CHAT_ID: string;
// Storage
BLOB_READ_WRITE_TOKEN: string;
// Next.js Public
NEXT_PUBLIC_API_URL: string;
NEXT_PUBLIC_SITE_URL: string;
NEXT_PUBLIC_GA_MEASUREMENT_ID?: string;
// Build
NODE_ENV: "development" | "production" | "test";
}
}
}
export {};
Validate Environment Variables
// lib/env.ts
import { z } from "zod";
const envSchema = z.object({
// Database
DATABASE_URL: z.string().url(),
// Redis
UPSTASH_REDIS_REST_URL: z.string().url(),
UPSTASH_REDIS_REST_TOKEN: z.string().min(1),
// AI
GOOGLE_GEMINI_API_KEY: z.string().min(1),
// Workflows
QSTASH_TOKEN: z.string().min(1),
// Social Media
DISCORD_WEBHOOK_URL: z.string().url().optional(),
TELEGRAM_BOT_TOKEN: z.string().optional(),
TELEGRAM_CHAT_ID: z.string().optional(),
// Next.js
NEXT_PUBLIC_API_URL: z.string().url(),
NEXT_PUBLIC_SITE_URL: z.string().url(),
// Build
NODE_ENV: z.enum(["development", "production", "test"]),
});
export const env = envSchema.parse(process.env);
// Usage
import { env } from "./lib/env";
const db = new Database(env.DATABASE_URL);
Loading Environment Variables
Next.js
Next.js automatically loads .env.local:
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
env: {
CUSTOM_KEY: process.env.CUSTOM_KEY,
},
};
export default nextConfig;
Drizzle Studio
// packages/database/drizzle.config.ts
import { defineConfig } from "drizzle-kit";
import * as dotenv from "dotenv";
// Load environment variables
dotenv.config({ path: "../../.env.local" });
export default defineConfig({
schema: "./src/db/schema",
out: "./migrations",
dialect: "postgresql",
dbCredentials: {
url: process.env.DATABASE_URL!,
},
});
Vitest
// vitest.config.ts
import { defineConfig } from "vitest/config";
import * as dotenv from "dotenv";
// Load test environment
dotenv.config({ path: ".env.test" });
export default defineConfig({
test: {
env: {
DATABASE_URL: process.env.DATABASE_URL!,
},
},
});
GitHub Actions
Secrets Configuration
Set secrets in GitHub:
- Go to repository Settings
- Secrets and variables → Actions
- Add repository secrets
Use in Workflows
# .github/workflows/deploy-prod.yml
name: Deploy Production
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
UPSTASH_REDIS_REST_URL: ${{ secrets.UPSTASH_REDIS_REST_URL }}
UPSTASH_REDIS_REST_TOKEN: ${{ secrets.UPSTASH_REDIS_REST_TOKEN }}
GOOGLE_GEMINI_API_KEY: ${{ secrets.GOOGLE_GEMINI_API_KEY }}
steps:
- uses: actions/checkout@v4
- name: Deploy
run: pnpm deploy:prod
Debugging Environment Issues
Check Variables Are Set
// Check at runtime
console.log("DATABASE_URL:", process.env.DATABASE_URL ? "✓ Set" : "✗ Not set");
console.log("REDIS_URL:", process.env.UPSTASH_REDIS_REST_URL ? "✓ Set" : "✗ Not set");
// Fail early if missing
if (!process.env.DATABASE_URL) {
throw new Error("DATABASE_URL is required");
}
Use dotenv-cli
# Install
pnpm add -D dotenv-cli
# Run command with env file
pnpm dotenv -e .env.local -- pnpm dev
# Run tests with test env
pnpm dotenv -e .env.test -- pnpm test
Debug Script
// scripts/check-env.ts
import * as dotenv from "dotenv";
dotenv.config({ path: ".env.local" });
const requiredVars = [
"DATABASE_URL",
"UPSTASH_REDIS_REST_URL",
"UPSTASH_REDIS_REST_TOKEN",
];
console.log("Checking environment variables...\n");
let missing = 0;
for (const varName of requiredVars) {
const value = process.env[varName];
if (value) {
console.log(`✓ ${varName}`);
} else {
console.log(`✗ ${varName} - MISSING`);
missing++;
}
}
if (missing > 0) {
console.error(`\n❌ ${missing} required variables are missing!`);
process.exit(1);
} else {
console.log("\n✅ All required variables are set!");
}
Add to package.json:
{
"scripts": {
"check-env": "tsx scripts/check-env.ts"
}
}
Common Patterns
Conditional Loading
// Load different configs based on environment
const config = {
development: {
apiUrl: "http://localhost:3000",
debug: true,
},
production: {
apiUrl: process.env.NEXT_PUBLIC_API_URL,
debug: false,
},
test: {
apiUrl: "http://localhost:3000",
debug: false,
},
}[process.env.NODE_ENV || "development"];
Default Values
const config = {
apiUrl: process.env.NEXT_PUBLIC_API_URL || "http://localhost:3000",
cacheTimeout: parseInt(process.env.CACHE_TIMEOUT || "3600", 10),
enableFeature: process.env.ENABLE_FEATURE === "true",
};
Required vs Optional
// Required (throw if missing)
const databaseUrl = process.env.DATABASE_URL!;
// Optional (with default)
const cacheTimeout = process.env.CACHE_TIMEOUT || "3600";
// Optional (may be undefined)
const optionalKey: string | undefined = process.env.OPTIONAL_KEY;
Security Best Practices
1. Never Commit Secrets
# .gitignore
.env
.env.local
.env.*.local
.env.development.local
.env.test.local
.env.production.local
2. Use Environment-Specific Files
.env.example ✅ Committed (no sensitive data)
.env.local ❌ Not committed (has secrets)
.env.development ❌ Not committed
.env.production ❌ Not committed
3. Rotate Secrets Regularly
# Update secret for all stages
npx sst secrets set API_KEY "new-key" --stage dev
npx sst secrets set API_KEY "new-key" --stage staging
npx sst secrets set API_KEY "new-key" --stage production
4. Least Privilege
Only grant necessary permissions:
- Database: Use read-only user for read operations
- API keys: Use restricted scopes
- AWS: Use IAM roles with minimal permissions
5. Audit Access
# Check who has access to secrets
npx sst secrets list --stage production
# Review CloudWatch logs for unauthorized access
Testing with Environment Variables
// __tests__/api.test.ts
import { describe, it, expect, beforeAll } from "vitest";
describe("API Tests", () => {
beforeAll(() => {
// Set test environment variables
process.env.DATABASE_URL = "postgresql://test:test@localhost:5432/test";
process.env.UPSTASH_REDIS_REST_URL = "https://test.upstash.io";
});
it("uses correct environment", () => {
expect(process.env.DATABASE_URL).toContain("test");
});
});
Troubleshooting
Variables Not Loading
Issue: Environment variables are undefined Solutions:
- Check file name is exactly
.env.local - Restart development server
- Verify file is in correct directory
- Check for syntax errors in .env file
Next.js Public Variables
Issue: NEXT_PUBLIC_ variables not available in browser
Solutions:
- Ensure variable starts with
NEXT_PUBLIC_ - Restart Next.js dev server
- Check browser console for actual value
SST Secrets Not Working
Issue: SST secrets return undefined Solutions:
- Verify secret is set for correct stage
- Check
bindconfiguration in SST - Use
Configimport fromsst/node/config
References
- Next.js Environment Variables: https://nextjs.org/docs/app/building-your-application/configuring/environment-variables
- SST Config: https://docs.sst.dev/config
- Related files:
.env.example- Example environment fileinfra/- SST infrastructure with env config- Root CLAUDE.md - Project documentation
Best Practices
- Use .env.example: Provide template for required variables
- Type Safety: Define types for environment variables
- Validation: Validate on application startup
- SST Secrets: Use for sensitive production values
- Never Commit: Add
.env.localto.gitignore - Document: Comment what each variable is for
- Stage-Specific: Use different values per environment
- Fail Fast: Validate required variables at startup
Quick Install
/plugin add https://github.com/sgcarstrends/sgcarstrends/tree/main/env-configCopy and paste this command in Claude Code to install this skill
GitHub 仓库
Related Skills
subagent-driven-development
DevelopmentThis skill executes implementation plans by dispatching a fresh subagent for each independent task, with code review between tasks. It enables fast iteration while maintaining quality gates through this review process. Use it when working on mostly independent tasks within the same session to ensure continuous progress with built-in quality checks.
algorithmic-art
MetaThis Claude Skill creates original algorithmic art using p5.js with seeded randomness and interactive parameters. It generates .md files for algorithmic philosophies, plus .html and .js files for interactive generative art implementations. Use it when developers need to create flow fields, particle systems, or other computational art while avoiding copyright issues.
executing-plans
DesignUse the executing-plans skill when you have a complete implementation plan to execute in controlled batches with review checkpoints. It loads and critically reviews the plan, then executes tasks in small batches (default 3 tasks) while reporting progress between each batch for architect review. This ensures systematic implementation with built-in quality control checkpoints.
cost-optimization
OtherThis Claude Skill helps developers optimize cloud costs through resource rightsizing, tagging strategies, and spending analysis. It provides a framework for reducing cloud expenses and implementing cost governance across AWS, Azure, and GCP. Use it when you need to analyze infrastructure costs, right-size resources, or meet budget constraints.
