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
Telegram
TELEGRAM_BOT_TOKEN=your-bot-token TELEGRAM_CHAT_ID=your-chat-id
TWITTER_BEARER_TOKEN=your-bearer-token
LINKEDIN_ACCESS_TOKEN=your-access-token LINKEDIN_ORG_ID=your-org-id
Error Reporting
Discord (workflow error logging only)
DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/...
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
TELEGRAM_BOT_TOKEN=your-bot-token TELEGRAM_CHAT_ID=your-chat-id
Error Reporting (Discord webhook)
DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/...
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
TELEGRAM_BOT_TOKEN: string;
TELEGRAM_CHAT_ID: string;
// Error Reporting
DISCORD_WEBHOOK_URL: 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 TELEGRAM_BOT_TOKEN: z.string().optional(), TELEGRAM_CHAT_ID: z.string().optional(),
// Error Reporting DISCORD_WEBHOOK_URL: z.string().url().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
- Never Commit Secrets
.gitignore
.env .env.local .env.*.local .env.development.local .env.test.local .env.production.local
- Use Environment-Specific Files
.env.example ✅ Committed (no sensitive data) .env.local ❌ Not committed (has secrets) .env.development ❌ Not committed .env.production ❌ Not committed
- 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
- 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
- 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 bind configuration in SST
-
Use Config import from sst/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 file
-
infra/
-
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.local to .gitignore
-
Document: Comment what each variable is for
-
Stage-Specific: Use different values per environment
-
Fail Fast: Validate required variables at startup