aussiegingersnap

infra-env

0
0
# Install this skill:
npx skills add aussiegingersnap/cursor-skills --skill "infra-env"

Install specific skill from multi-skill repository

# Description

Environment variable conventions and security practices for Next.js projects. This skill should be used when setting up environment configuration, managing secrets, or establishing security patterns for a new project.

# SKILL.md


name: infra-env
description: Environment variable conventions and security practices for Next.js projects. This skill should be used when setting up environment configuration, managing secrets, or establishing security patterns for a new project.


Environment Variables Skill

Conventions and security practices for managing environment variables in Next.js projects.

When to Use This Skill

  • Setting up a new project's environment configuration
  • Adding new environment variables
  • Reviewing security of environment handling
  • Creating .env.example templates
  • Troubleshooting environment issues

Core Principles

  1. Never commit secrets - .env* files with real values stay out of git
  2. Document with examples - .env.example shows structure without values
  3. Prefix client-safe vars - Only NEXT_PUBLIC_* reaches the browser
  4. Validate at startup - Fail fast if required vars are missing
  5. Use typed configuration - Type-safe access to environment

File Hierarchy

File Purpose Git?
.env Default values (shared) Optional
.env.local Local overrides (secrets) Never
.env.development Dev-specific defaults Optional
.env.production Prod defaults (no secrets) Optional
.env.example Template for developers Always

Load order (later overrides earlier):
1. .env
2. .env.local
3. .env.development / .env.production (based on NODE_ENV)
4. .env.development.local / .env.production.local

Standard .env.example

Create .env.example as documentation:

# =============================================================================
# ENVIRONMENT CONFIGURATION
# =============================================================================
# Copy this file to .env.local and fill in the values
# NEVER commit .env.local to git
# =============================================================================

# -----------------------------------------------------------------------------
# Application
# -----------------------------------------------------------------------------
NEXT_PUBLIC_APP_URL=http://localhost:3000
NEXT_PUBLIC_APP_NAME=MyApp

# -----------------------------------------------------------------------------
# Database
# -----------------------------------------------------------------------------
# PostgreSQL connection string
# Format: postgres://USER:PASSWORD@HOST:PORT/DATABASE
DATABASE_URL=postgres://postgres:postgres@localhost:5432/myapp

# -----------------------------------------------------------------------------
# Authentication (Better Auth)
# -----------------------------------------------------------------------------
# Generate with: openssl rand -base64 32
BETTER_AUTH_SECRET=your-secret-key-min-32-characters-here
BETTER_AUTH_URL=http://localhost:3000

# OAuth Providers (get from provider console)
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=

# -----------------------------------------------------------------------------
# Third-Party Services (Optional)
# -----------------------------------------------------------------------------
# Analytics
# NEXT_PUBLIC_POSTHOG_KEY=
# NEXT_PUBLIC_POSTHOG_HOST=https://app.posthog.com

# Email (Resend)
# RESEND_API_KEY=

# Storage (S3/R2/Minio)
# S3_BUCKET=
# S3_REGION=
# S3_ACCESS_KEY_ID=
# S3_SECRET_ACCESS_KEY=
# S3_ENDPOINT=

# -----------------------------------------------------------------------------
# Development Only
# -----------------------------------------------------------------------------
# Set to 'true' to enable debug features
# DEBUG=false

Naming Conventions

Server-Only Variables (Default)

# Database
DATABASE_URL=...

# Auth secrets
BETTER_AUTH_SECRET=...
GOOGLE_CLIENT_SECRET=...

# API keys
RESEND_API_KEY=...
OPENAI_API_KEY=...

Client-Accessible Variables

Must start with NEXT_PUBLIC_:

# URLs and public identifiers
NEXT_PUBLIC_APP_URL=http://localhost:3000
NEXT_PUBLIC_POSTHOG_KEY=phc_xxx
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_xxx

Naming Style

  • Use SCREAMING_SNAKE_CASE
  • Be descriptive: DATABASE_URL not DB
  • Group by service: GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET
  • Prefix third-party: RESEND_API_KEY, STRIPE_SECRET_KEY

Type-Safe Environment

Environment Validation

Create src/lib/env.ts:

import { z } from 'zod';

const envSchema = z.object({
  // Database
  DATABASE_URL: z.string().url(),

  // Auth
  BETTER_AUTH_SECRET: z.string().min(32),
  BETTER_AUTH_URL: z.string().url(),

  // OAuth (optional in development)
  GOOGLE_CLIENT_ID: z.string().optional(),
  GOOGLE_CLIENT_SECRET: z.string().optional(),
  GITHUB_CLIENT_ID: z.string().optional(),
  GITHUB_CLIENT_SECRET: z.string().optional(),

  // Public
  NEXT_PUBLIC_APP_URL: z.string().url(),

  // Node environment
  NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
});

// Validate at module load
const parsed = envSchema.safeParse(process.env);

if (!parsed.success) {
  console.error('❌ Invalid environment variables:');
  console.error(parsed.error.flatten().fieldErrors);
  throw new Error('Invalid environment variables');
}

export const env = parsed.data;

Usage

import { env } from '@/lib/env';

// Type-safe access
const dbUrl = env.DATABASE_URL;
const isProduction = env.NODE_ENV === 'production';

Client Environment

For client-side validation, create src/lib/env-client.ts:

import { z } from 'zod';

const clientEnvSchema = z.object({
  NEXT_PUBLIC_APP_URL: z.string().url(),
  NEXT_PUBLIC_POSTHOG_KEY: z.string().optional(),
});

export const clientEnv = clientEnvSchema.parse({
  NEXT_PUBLIC_APP_URL: process.env.NEXT_PUBLIC_APP_URL,
  NEXT_PUBLIC_POSTHOG_KEY: process.env.NEXT_PUBLIC_POSTHOG_KEY,
});

Security Practices

Never Do

# ❌ Secrets in .env.example with real values
API_KEY=sk_live_real_key_here

# ❌ Secrets with NEXT_PUBLIC_ prefix
NEXT_PUBLIC_DATABASE_URL=postgres://...

# ❌ Committing .env.local
# (should be in .gitignore)

Always Do

# βœ… Placeholder values in .env.example
API_KEY=your-api-key-here

# βœ… Secrets stay server-only
DATABASE_URL=postgres://...

# βœ… Only public info gets NEXT_PUBLIC_
NEXT_PUBLIC_APP_URL=http://localhost:3000

.gitignore

Ensure these are in .gitignore:

# Environment files with secrets
.env.local
.env.*.local
.env.development.local
.env.production.local

# Keep .env.example in git
!.env.example

Secret Generation

# Generate random secret (32 bytes, base64)
openssl rand -base64 32

# Generate random secret (hex)
openssl rand -hex 32

# Generate UUID
uuidgen

Per-Environment Configuration

Development

# .env.development
NODE_ENV=development
NEXT_PUBLIC_APP_URL=http://localhost:3000

Production

# Set in deployment platform (Railway, Vercel, AWS)
# Never in files committed to git

NODE_ENV=production
DATABASE_URL=postgres://prod-user:prod-pass@prod-host:5432/prod-db
BETTER_AUTH_SECRET=production-secret-here
NEXT_PUBLIC_APP_URL=https://myapp.com

Testing

# .env.test
NODE_ENV=test
DATABASE_URL=postgres://postgres:postgres@localhost:5432/myapp_test

Platform-Specific Setup

Local Development

  1. Copy template: cp .env.example .env.local
  2. Fill in values for local services
  3. Start Docker services: docker compose up -d
  4. Run app: npm run dev

Railway

# Set via CLI
railway variables --set DATABASE_URL=postgres://...
railway variables --set BETTER_AUTH_SECRET=...

# Or use Railway dashboard
# Settings β†’ Variables β†’ Add Variable

Vercel

# Set via CLI
vercel env add DATABASE_URL production
vercel env add BETTER_AUTH_SECRET production

# Or use Vercel dashboard
# Settings β†’ Environment Variables

AWS ECS

Use AWS Secrets Manager or Parameter Store:

# Store secret
aws secretsmanager create-secret \
  --name myapp/production/database-url \
  --secret-string "postgres://..."

# Reference in task definition
"secrets": [
  {
    "name": "DATABASE_URL",
    "valueFrom": "arn:aws:secretsmanager:region:account:secret:myapp/production/database-url"
  }
]

Debugging Environment Issues

Check What's Loaded

// Temporary debug (remove before commit!)
console.log('DATABASE_URL exists:', !!process.env.DATABASE_URL);
console.log('NODE_ENV:', process.env.NODE_ENV);

Common Issues

Variable undefined at runtime:
- Check file is named correctly (.env.local not .env.local.txt)
- Restart dev server after adding new variables
- Verify variable name matches exactly (case-sensitive)

Client can't access variable:
- Must have NEXT_PUBLIC_ prefix
- Must rebuild after adding (for production builds)

Wrong value used:
- Check load order (.env.local overrides .env)
- Clear .next cache: rm -rf .next

Works locally, fails in production:
- Variables set in deployment platform?
- Names match exactly?
- No extra spaces or quotes in values?

Checklist for New Projects

  • [ ] Create .env.example with all variables
  • [ ] Add .env.local to .gitignore
  • [ ] Set up src/lib/env.ts for validation
  • [ ] Document required vs optional variables
  • [ ] Generate secrets with proper randomness
  • [ ] Configure deployment platform variables
  • [ ] Test with production-like values locally

Quick Reference

Variable Type Prefix Accessible In
Server secret None Server only
Public config NEXT_PUBLIC_ Server + Client
File Committed? Contains Secrets?
.env.example Yes No (placeholders)
.env Optional No
.env.local No Yes

# Supported AI Coding Agents

This skill is compatible with the SKILL.md standard and works with all major AI coding agents:

Learn more about the SKILL.md standard and how to use these skills with your preferred AI coding agent.