| name | env-config |
| description | Manage environment variables across dev/staging/prod environments and SST config. Use when adding new secrets, updating environment-specific configs, or debugging environment issues. |
| allowed-tools | Read, Edit, Grep |
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