skills/infra-env/SKILL.md
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.
npx skillsauth add aussiegingersnap/cursor-skills infra-envInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Conventions and security practices for managing environment variables in Next.js projects.
.env.example templates.env* files with real values stay out of git.env.example shows structure without valuesNEXT_PUBLIC_* reaches the browser| 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):
.env.env.local.env.development / .env.production (based on NODE_ENV).env.development.local / .env.production.localCreate .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
# Database
DATABASE_URL=...
# Auth secrets
BETTER_AUTH_SECRET=...
GOOGLE_CLIENT_SECRET=...
# API keys
RESEND_API_KEY=...
OPENAI_API_KEY=...
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
SCREAMING_SNAKE_CASEDATABASE_URL not DBGOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRETRESEND_API_KEY, STRIPE_SECRET_KEYCreate 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;
import { env } from '@/lib/env';
// Type-safe access
const dbUrl = env.DATABASE_URL;
const isProduction = env.NODE_ENV === 'production';
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,
});
# ❌ 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)
# ✅ 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
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
# Generate random secret (32 bytes, base64)
openssl rand -base64 32
# Generate random secret (hex)
openssl rand -hex 32
# Generate UUID
uuidgen
# .env.development
NODE_ENV=development
NEXT_PUBLIC_APP_URL=http://localhost:3000
# 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
# .env.test
NODE_ENV=test
DATABASE_URL=postgres://postgres:postgres@localhost:5432/myapp_test
cp .env.example .env.localdocker compose up -dnpm run dev# Set via CLI
railway variables --set DATABASE_URL=postgres://...
railway variables --set BETTER_AUTH_SECRET=...
# Or use Railway dashboard
# Settings → Variables → Add Variable
# Set via CLI
vercel env add DATABASE_URL production
vercel env add BETTER_AUTH_SECRET production
# Or use Vercel dashboard
# Settings → Environment Variables
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"
}
]
// Temporary debug (remove before commit!)
console.log('DATABASE_URL exists:', !!process.env.DATABASE_URL);
console.log('NODE_ENV:', process.env.NODE_ENV);
Variable undefined at runtime:
.env.local not .env.local.txt)Client can't access variable:
NEXT_PUBLIC_ prefixWrong value used:
.env.local overrides .env).next cache: rm -rf .nextWorks locally, fails in production:
.env.example with all variables.env.local to .gitignoresrc/lib/env.ts for validation| 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 |
tools
# Versioning Skill Semantic versioning automation based on conventional commits. Automatically manages version bumps, changelogs, and git tags using `standard-version`. ## When to Use - Before releasing a new version - When preparing a deployment - To generate/update CHANGELOG.md - When the user asks about version management - Setting up versioning for a new project ## Prerequisites - Conventional commits enforced (recommended: lefthook) - Node.js project with package.json ## Setup (One-Ti
tools
Theme generation with tweakcn for shadcn/ui and Magic UI animations. Use when setting up project themes, customizing color schemes, adding dark mode, or integrating animated components.
tools
shadcn/studio component library with MCP integration, theme generation, and block patterns. This skill should be used when building UI with shadcn components, selecting dashboard layouts, or generating landing pages. Canonical source for all shadcn-based work.
development
Enforce a precise, minimal design system inspired by Linear, Notion, and Stripe. Use this skill when building dashboards, admin interfaces, or any UI that needs Jony Ive-level precision - clean, modern, minimalist with taste. Every pixel matters.