This guide documents the advanced environment variable synchronization system built around the sync-env.js script and .env.source-of-truth.local file. This system provides centralized environment management across Next.js, Convex, and deployment environments with validation, security checks, and automatic backup.
.env.source-of-truth.local (Master)
↓
scripts/sync-env.js
↙ ↘
apps/web/.env.local apps/convex/.env.local
↓ ↓
Next.js Frontend Convex Backend
↓
Convex Deployment
Benefits:
- Centralized Management: All environment variables in one file
- Automatic Distribution: Script handles generating app-specific files
- Validation: Built-in security and consistency checks
- Backup: Automatic backup before changes
- Documentation: Human-readable table format
The .env.source-of-truth.local file uses a human-readable table format:
| NEXTJS | CONVEX | GROUP | KEY | VALUE |
|--------|--------|-------------------|---------------------------|------------------------------------------|
| true | false | Local Development | NEXT_PUBLIC_APP_URL | http://localhost:3000 |
| true | false | Local Development | PORT | 3000 |
| false | true | GitHub OAuth | GITHUB_CLIENT_ID | Ov23l-xxxxx-xxxxxx |
| false | true | GitHub OAuth | GITHUB_CLIENT_SECRET | 799ad-xxxxx-xxxxxx |
| true | true | Convex | CONVEX_DEPLOYMENT | dev:helpful-567 |
| true | true | Convex | NEXT_PUBLIC_CONVEX_URL | https://helpful-567.convex.cloud |
- NEXTJS:
true/false- Include in Next.js environment file - CONVEX:
true/false- Include in Convex environment file - GROUP: Descriptive category for organization
- KEY: Environment variable name
- VALUE: Environment variable value
Common groups used:
Local Development- Development server configurationGitHub OAuth- GitHub authentication credentialsGoogle OAuth- Google authentication credentialsOAuth- General OAuth configurationLLM Config- AI/LLM API configurationConvex- Convex backend configuration
# 1. Edit the source file
nano .env.source-of-truth.local
# 2. Sync environment variables
bun run sync-env
# 3. Restart services to pick up changes
bun dev# 1. Copy the example file
cp .env.source-of-truth.example .env.source-of-truth.local
# 2. Fill in your actual values
nano .env.source-of-truth.local
# 3. Sync environment variables
bun run sync-env
# 4. Start development
bun dev-
Add to Source File:
# Add new row to .env.source-of-truth.local | false | true | New Service | NEW_API_KEY | your-actual-key-here |
-
Sync Changes:
bun run sync-env
-
Restart Services:
# Restart affected services bun dev
-
Edit Source File:
# Update VALUE column in .env.source-of-truth.local | false | true | LLM Config | LLM_MODEL | openai/gpt-4o |
-
Sync with Dry Run (recommended):
bun run sync-env --dry-run
-
Apply Changes:
bun run sync-env
# Standard sync (most common)
bun run sync-env
# Preview changes without applying
bun run sync-env --dry-run
# Verbose logging for debugging
bun run sync-env --verbose# Direct script execution with options
node ./scripts/sync-env.js --dry-run --verbose
# Target specific deployment
node ./scripts/sync-env.js --deployment=preview
# Full help
node ./scripts/sync-env.js --help# Development deployment (default)
bun run sync-env --deployment=dev
# Preview deployment
bun run sync-env --deployment=preview
# Production (blocked for security)
# Use manual Convex commands for productionLocation: apps/web/.env.local
Content Structure:
# =============================================================================
# Next.js Environment Configuration
# =============================================================================
# Auto-generated from .env.source-of-truth.local - DO NOT EDIT MANUALLY
# Run 'bun run sync-env' to regenerate this file
# Local Development
# ------------------
NEXT_PUBLIC_APP_URL=http://localhost:3000
PORT=3000
# GitHub OAuth
# -------------
# ⚠️ PUBLIC: This variable is exposed to the browser
NEXT_PUBLIC_CONVEX_URL=https://helpful-567.convex.cloudLocation: apps/convex/.env.local
Content Structure:
# =============================================================================
# Convex Backend Environment Configuration
# =============================================================================
# Auto-generated from .env.source-of-truth.local - DO NOT EDIT MANUALLY
# Run 'bun run sync-env' to regenerate this file
# GitHub OAuth
# -------------
GITHUB_CLIENT_ID=Ov23l-xxxxx-xxxxxx
GITHUB_CLIENT_SECRET=799ad-xxxxx-xxxxxx
# Convex
# ------
CONVEX_DEPLOYMENT=dev:helpful-567
NEXT_PUBLIC_CONVEX_URL=https://helpful-567.convex.cloudLocation: .env.backup.local
Purpose: Automatic backup of Convex environment before changes
Content: Previous Convex environment state with timestamp
-
Empty Value Check:
- Warns about empty environment variables
- Helps catch configuration mistakes
-
Public Variable Security:
- Scans
NEXT_PUBLIC_variables for sensitive data - Prevents accidental exposure of secrets
- Scans
-
Required Variable Check:
- Validates essential Convex variables
- Ensures proper Next.js/Convex distribution
-
Deployment Safety:
- Blocks production deployments for security
- Requires manual production management
# Never commit source file
echo ".env.source-of-truth.local" >> .gitignore
# Use secure secret generation
openssl rand -base64 32
# Rotate secrets regularly
# Update source file → sync → deployThe script automatically syncs environment variables to your Convex deployment:
-
Backup Current Environment:
- Creates timestamped backup
- Stores in
.env.backup.local
-
Calculate Differences:
- Compares source vs current deployment
- Shows what will be added/updated/removed
-
Apply Changes:
- Adds new variables
- Updates changed variables
- Removes obsolete variables
-
Verify Success:
- Double-checks applied changes
- Reports any failures
🚀 Starting advanced environment sync...
📖 Reading environment source file...
✅ Parsed 15 environment variables from source
🔧 Generating Next.js environment configuration...
✅ Next.js environment file generated: apps/web/.env.local
🔧 Generating Convex environment configuration...
✅ Convex environment file generated: apps/convex/.env.local
🔗 Syncing Convex deployment environment...
💾 Creating automatic backup of current environment...
✅ Environment backup saved to: .env.backup.local
🔍 Calculating environment differences...
📋 Environment Changes Summary:
==================================================
➕ Variables to ADD (2):
• NEW_API_KEY = sk-xxxxx...
• FEATURE_FLAG = true
🔄 Variables to UPDATE (1):
• LLM_MODEL
OLD: openai/gpt-4o-mini
NEW: openai/gpt-4o
✅ All environment variables synchronized successfully!Error: "Source file not found"
# Check if source file exists
ls -la .env.source-of-truth.local
# Copy from example if missing
cp .env.source-of-truth.example .env.source-of-truth.localError: "Convex command failed"
# Check Convex authentication
cd apps/convex && bunx convex dev
# Verify deployment exists
bunx convex env listError: "Invalid table format"
# Check table structure in source file
head -5 .env.source-of-truth.local
# Ensure proper pipe-delimited format
# | NEXTJS | CONVEX | GROUP | KEY | VALUE |-
Validate Source File Format:
# Check file structure cat .env.source-of-truth.local | head -10 # Look for formatting issues grep -n "^[^|]" .env.source-of-truth.local
-
Test Dry Run Mode:
# Preview without applying changes bun run sync-env --dry-run --verbose -
Check Generated Files:
# Verify Next.js file generated ls -la apps/web/.env.local # Check Convex file generated ls -la apps/convex/.env.local
-
Verify Convex Sync:
# Check current Convex environment cd apps/convex && bunx convex env list
# Sync to preview environment
bun run sync-env --deployment=preview
# Target specific branch deployment
bun run sync-env --deployment=feature-branch# Multiple environment setup
for env in dev preview; do
bun run sync-env --deployment=$env
done# In GitHub Actions (development only)
- name: Sync Environment
run: bun run sync-env --deployment=dev
# Production requires manual management
# Never automate production environment sync-
Source File Security:
- Keep
.env.source-of-truth.localsecure - Never commit to version control
- Back up separately from code
- Keep
-
Regular Maintenance:
- Clean up unused variables monthly
- Rotate secrets quarterly
- Review public variables for security
-
Team Collaboration:
- Share source file format standards
- Document variable purposes in GROUP column
- Use descriptive variable names
-
Environment Changes:
- Always edit source file first
- Run dry-run to preview changes
- Sync and restart services
- Test application functionality
-
New Team Members:
- Provide source file template
- Document required external services
- Include setup verification steps
-
Production Deployment:
- Use manual Convex commands
- Never sync production automatically
- Maintain separate production source file
# After setting up OAuth services
# 1. Update source file with credentials
| false | true | GitHub OAuth | GITHUB_CLIENT_ID | your-client-id |
| false | true | GitHub OAuth | GITHUB_CLIENT_SECRET | your-client-secret |
# 2. Sync environment
bun run sync-env
# 3. Test authentication
bun dev# After obtaining LLM API keys
# 1. Add to source file
| false | true | LLM Config | OPENROUTER_API_KEY | sk-or-v1-xxxxx |
# 2. Sync and test
bun run sync-env
curl -H "Authorization: Bearer $(grep OPENROUTER apps/convex/.env.local | cut -d= -f2)" \
"https://openrouter.ai/api/v1/models"Problem: Production deployment shows localhost URLs or development values instead of production values.
Common Symptoms:
// Production site showing:
{
"NEXT_PUBLIC_APP_URL": "http://localhost:3000",
"NEXT_PUBLIC_LOG_WORKER_URL": "http://localhost:8787"
}Root Causes & Solutions:
Diagnosis: GitHub Actions secrets exist but contain wrong values
Solution:
- Go to GitHub Repository → Settings → Security → Secrets and variables → Actions
- Check Repository secrets for production values:
NEXT_PUBLIC_APP_URLshould behttps://your-site.pages.devNEXT_PUBLIC_LOG_WORKER_URLshould behttps://your-worker.workers.dev
- Update any secrets that contain localhost values
Debug Method: Add to CI workflow:
- name: Debug Environment Variables
run: |
[ -n "${{ secrets.NEXT_PUBLIC_APP_URL }}" ] && echo "✅ SECRET exists" || echo "❌ SECRET missing"
[ -n "$NEXT_PUBLIC_APP_URL" ] && echo "✅ ENV set (length: ${#NEXT_PUBLIC_APP_URL})" || echo "❌ ENV empty"Diagnosis: Environment variables are undefined or empty in CI
Solution:
- Create missing GitHub repository secrets
- Ensure secret names match exactly what's used in CI workflow
- Verify secrets are in Repository scope, not Environment scope
Diagnosis: Secrets not properly injected into build environment
Solution: Verify CI workflow has proper env section:
build:
env:
NEXT_PUBLIC_APP_URL: ${{ secrets.NEXT_PUBLIC_APP_URL }}
NEXT_PUBLIC_CONVEX_URL: ${{ secrets.NEXT_PUBLIC_CONVEX_URL }}
NEXT_PUBLIC_LOG_WORKER_URL: ${{ secrets.NEXT_PUBLIC_LOG_WORKER_URL }}# Local environment check
bun run sync-env
cat apps/web/.env.local | grep NEXT_PUBLIC
# Production environment check (via dev center)
# Environment variables are shown in /dev page under "Environment Variables Debug"Add systematic environment variable logging to CI:
- name: Environment Debug
run: |
echo "=== Secret Existence Check ==="
[ -n "${{ secrets.NEXT_PUBLIC_APP_URL }}" ] && echo "✅ APP_URL secret exists" || echo "❌ missing"
echo "=== Environment Variables ==="
echo "APP_URL length: ${#NEXT_PUBLIC_APP_URL}"
env | grep NEXT_PUBLIC || echo "No NEXT_PUBLIC vars found"Use string length to identify value source:
http://localhost:3000= 21 characters (development)https://your-site.pages.dev= 30+ characters (production)
-
Use Debug Environment Section:
- Environment variables are displayed in
/devpage for verification - Shows NEXTPUBLIC* variables available in the browser
- Environment variables are displayed in
-
CI Validation:
- Add checks for localhost values in production builds
- Fail build if production environment contains development URLs
-
Documentation:
- Document expected environment variable values
- Include character length references for validation
- Scripts and Commands Reference - All available commands
- Google OAuth Setup - Service-specific setup
- LLM API Setup - AI service configuration
- API Security and Secret Management - Security best practices
Created: For centralized environment management across monorepo
Security: Follow source file security practices
Automation: Integrates with development and deployment workflows