Environment variables are the backbone of any production edge functions vercel ai sdk environment variables setup. They separate credentials from code, enable per-environment tuning, and keep your API keys out of version control. This guide walks through the complete lifecycle: local development with .env.local, preview deployments via Vercel CLI, production configuration in the dashboard, and runtime validation so you catch misconfiguration before it hits users.
Step 1: Define the contract with a schema
Before adding any values, codify what your edge function expects. A Zod schema gives you parse-time safety and self-documenting errors.
// lib/env.ts
import { z } from "zod";
export const edgeEnvSchema = z.object({
OPENAI_API_KEY: z.string().min(1, "OpenAI API key is required"),
ANTHROPIC_API_KEY: z.string().optional(),
DEFAULT_MODEL: z.string().default("gpt-4o-mini"),
MAX_TOKENS: z.coerce.number().int().positive().default(4096),
TEMPERATURE: z.coerce.number().min(0).max(2).default(0.7),
LOG_LEVEL: z.enum(["debug", "info", "warn", "error"]).default("info"),
RATE_LIMIT_RPM: z.coerce.number().int().positive().default(60),
});
export type EdgeEnv = z.infer<typeof edgeEnvSchema>;
Why Zod over plain process.env checks: You get a single source of truth, automatic coercion for numeric flags, and a clear error message when MAX_TOKENS="unlimited" slips through.
Step 2: Create local development files
Vercel respects .env.local for vercel dev and ignores it in .gitignore by default. Create the file at your repository root:
# .env.local
OPENAI_API_KEY=sk-proj-abc123...
ANTHROPIC_API_KEY=sk-ant-api03-xyz789...
DEFAULT_MODEL=gpt-4o-mini
MAX_TOKENS=4096
TEMPERATURE=0.7
LOG_LEVEL=debug
RATE_LIMIT_RPM=120
Add .env.local to .gitignore if it isn’t already:
# .gitignore
.env.local
.env*.local
Verification: Run vercel dev and confirm the function starts without schema validation errors. You should see your LOG_LEVEL=debug output in the console.
Step 3: Load and validate at runtime
Edge functions run in a constrained V8 isolate — no fs access, no require.cache. Load once at module initialization, not per-request.
// lib/env.ts (continued)
let cachedEnv: EdgeEnv | null = null;
export function getEdgeEnv(): EdgeEnv {
if (cachedEnv) return cachedEnv;
const parsed = edgeEnvSchema.safeParse(process.env);
if (!parsed.success) {
const messages = parsed.error.errors.map(e => `${e.path.join(".")}: ${e.message}`).join("; ");
throw new Error(`Invalid environment configuration: ${messages}`);
}
cachedEnv = parsed.data;
return cachedEnv;
}
Use it in your route handler:
// app/api/chat/route.ts
import { streamText } from "ai";
import { openai } from "@ai-sdk/openai";
import { getEdgeEnv } from "@/lib/env";
export const runtime = "edge";
export async function POST(req: Request) {
const env = getEdgeEnv();
const { messages } = await req.json();
const result = await streamText({
model: openai(env.DEFAULT_MODEL),
messages,
maxTokens: env.MAX_TOKENS,
temperature: env.TEMPERATURE,
});
return result.toDataStreamResponse();
}
Verification: Hit the endpoint with curl -X POST http://localhost:3000/api/chat -d '{"messages":[{"role":"user","content":"ping"}]}' -H "Content-Type: application/json" and confirm a streaming response.
Step 4: Configure preview deployments via CLI
Every git push to a non-main branch creates a preview deployment. Set its environment variables without touching the dashboard:
# Pull current preview env (optional, for inspection)
vercel env ls --scope=<team-slug> --environment=preview
# Add or update a secret for preview
vercel env add OPENAI_API_KEY preview --scope=<team-slug>
# Enter value when prompted: sk-proj-preview-key...
# Bulk upsert from a file (useful for CI)
vercel env pull .env.preview --environment=preview --scope=<team-slug>
# Edit .env.preview, then:
vercel env push .env.preview --environment=preview --scope=<team-slug>
Key distinction: vercel env add prompts interactively. vercel env push reads KEY=VALUE lines from a file — ideal for scripted CI jobs.
Verification: Open the preview URL, hit the chat endpoint, and verify the response uses the preview model (check logs for DEFAULT_MODEL value).
Step 5: Set production variables in the dashboard
Production values live in Vercel Dashboard → Project → Settings → Environment Variables. Use the UI for secrets you never want in CLI history.
- Click Add New.
- Key:
OPENAI_API_KEY, Value: your production key, Environment: Production only. - Repeat for
ANTHROPIC_API_KEY,DEFAULT_MODEL=gpt-4o,MAX_TOKENS=8192,LOG_LEVEL=warn,RATE_LIMIT_RPM=300. - Save. Vercel triggers a new production deployment automatically.
Verification: Check the deployment log for “Environment Variables: 6 applied”. Call the production endpoint and confirm LOG_LEVEL=warn suppresses debug output.
Step 6: Handle runtime-specific quirks
Edge runtime differs from Node.js in ways that bite environment variable handling:
| Constraint | Workaround |
|---|---|
No process.env mutation |
Treat env as read-only; derive computed values in getEdgeEnv() |
No dotenv package |
Rely on Vercel’s built-in injection; don’t import dotenv |
| 1 MB bundle limit | Keep schema and validation lean; avoid heavy deps |
No crypto module (older isolates) |
Use Web Crypto API (crypto.subtle) for any key derivation |
If you need a derived value — say, a hashed API key for logging — compute it inside the getter:
// lib/env.ts (addition)
import { createHash } from "node:crypto"; // available in edge runtime
function hashKey(key: string): string {
return createHash("sha256").update(key).digest("hex").slice(0, 8);
}
export function getEdgeEnv(): EdgeEnv {
// ... existing parse logic ...
return {
...cachedEnv,
OPENAI_API_KEY_HASH: hashKey(cachedEnv.OPENAI_API_KEY),
} as EdgeEnv & { OPENAI_API_KEY_HASH: string };
}
Step 7: Validate in CI before deploy
Fail fast. Add a GitHub Actions step that runs the schema against the preview environment file.
# .github/workflows/validate-env.yml
name: Validate environment variables
on:
pull_request:
paths:
- ".env.preview"
- "lib/env.ts"
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
- run: npm ci
- run: npx tsx -e "
import { edgeEnvSchema } from './lib/env';
import { config } from 'dotenv';
config({ path: '.env.preview' });
const result = edgeEnvSchema.safeParse(process.env);
if (!result.success) {
console.error(result.error.format());
process.exit(1);
}
console.log('Environment validation passed');
"
Verification: Open a PR with a malformed .env.preview (e.g., MAX_TOKENS=high) and watch the check fail with a clear Zod error.
Step 8: Rotate secrets without downtime
Vercel applies environment variable changes on the next deployment. For zero-downtime rotation:
- Add the new key with a suffix:
OPENAI_API_KEY_NEW. - Deploy a version of your function that reads
process.env.OPENAI_API_KEY_NEW ?? process.env.OPENAI_API_KEY. - Verify the new key works in preview.
- Promote to production.
- Remove the old key from the dashboard.
- Deploy again to drop the fallback.
// Temporary rotation shim
function getActiveOpenAIKey(): string {
return process.env.OPENAI_API_KEY_NEW ?? process.env.OPENAI_API_KEY;
}
Verification: Monitor error rates during the overlap window. Zero 401s from the provider means rotation succeeded.
Step 9: Debug missing or wrong values in production
When a production function misbehaves, check three places in order:
- Vercel Dashboard → Functions → View Function Logs — look for the schema validation error thrown at module load.
- Runtime logs — search for
Invalid environment configuration. - Edge network tab — confirm the deployed function bundle includes your env validation code (it should, since it runs at import time).
Common failure modes:
| Symptom | Cause | Fix |
|---|---|---|
OPENAI_API_KEY: Required in logs |
Variable not set for Production environment | Add in Dashboard → Settings → Environment Variables |
MAX_TOKENS: Expected number, received nan |
Value set to empty string | Set explicit numeric value |
| Function works locally but fails on preview | Preview env missing variable | vercel env add KEY preview |
TypeScript error: Property 'OPENAI_API_KEY_HASH' does not exist |
Type definition out of sync | Regenerate types or cast in getter |
Step 10: Optional — integrate with external secret stores
For teams requiring audit trails, rotation policies, or shared secrets across projects, pull values at build time from 1Password, AWS Secrets Manager, or HashiCorp Vault. Example with 1Password CLI:
# .github/workflows/deploy.yml (excerpt)
- name: Load secrets from 1Password
uses: 1password/load-secrets-action@v1
with:
env-file: ".env.production.generated"
env:
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
- name: Push to Vercel production
run: vercel env push .env.production.generated --environment=production --scope=$VERCEL_SCOPE --token=$VERCEL_TOKEN
The generated file contains only KEY=VALUE lines — no schema, no comments. Your getEdgeEnv() still validates on startup.
Verification: Confirm the generated file exists in the workflow artifacts (masked), and the subsequent deployment shows the expected variable count.
You now have a complete, auditable pipeline: schema-defined contract, local override, preview isolation, production hardening, CI validation, and zero-downtime rotation. The same pattern scales to any edge functions vercel ai sdk environment variables configuration — add keys to the schema, push to the appropriate environment, and the runtime validation catches drift before users do.