Next.js Environment Variables Undefined on Vercel? Fix
Environment variables not working on Vercel? Learn the exact configuration needed for Next.js 15 deployment with zero errors.
Deploy Next.js 15 to Vercel Without Environment Variable Errors#
Environment variable errors are the #1 cause of failed Vercel deployments. Your app works locally, but in production you get "undefined" errors, API calls fail, or the build crashes. This guide covers everything you need to know about environment variables in Next.js 15 and Vercel.
This article is part of our comprehensive Deploying Next.js + Supabase to Production guide.
Understanding Environment Variables in Next.js#
Next.js has specific rules for environment variables, documented in the environment variables guide — the prefix is not a convention, it is what decides whether a value ever reaches the browser:
- NEXT_PUBLIC_* - Exposed to browser (client-side)
- Regular variables - Server-side only
- Build-time - Bundled during build
- Runtime - Loaded when server starts
Common Error Messages#
## Error 1: Undefined variable
ReferenceError: process is not defined
## Error 2: Variable not found
Error: NEXT_PUBLIC_API_URL is not defined
## Error 3: Build failure
Error: Environment variable DATABASE_URL is required
## Error 4: Wrong value in production
API_URL: undefined (expected: https://api.example.com)Solution 1: Understand NEXT_PUBLIC_ Prefix#
The most important rule:
Client-Side Variables MUST Have NEXT_PUBLIC_#
## .env.local
## ❌ WRONG: Won't work in browser
API_URL=https://api.example.com
## ✅ CORRECT: Accessible in browser
NEXT_PUBLIC_API_URL=https://api.example.com
## ✅ CORRECT: Server-only (no prefix needed)
DATABASE_URL=postgresql://...
API_SECRET_KEY=secret123Usage in Code#
// ❌ BAD: Won't work in client components
'use client'
export function ApiClient() {
const apiUrl = process.env.API_URL // ❌ undefined in browser
return <div>{apiUrl}</div>
}
// ✅ GOOD: Use NEXT_PUBLIC_ prefix
'use client'
export function ApiClient() {
const apiUrl = process.env.NEXT_PUBLIC_API_URL // ✅ Works!
return <div>{apiUrl}</div>
}
// ✅ GOOD: Server-only variables in Server Components
export async function ServerComponent() {
const dbUrl = process.env.DATABASE_URL // ✅ Works (server-side)
// Use dbUrl for database connection
}Solution 2: Configure Vercel Environment Variables#
Step-by-step Vercel configuration:
Add Variables in Vercel Dashboard#
## 1. Go to: Vercel Dashboard > Your Project > Settings > Environment Variables
## 2. Add each variable:
Name: NEXT_PUBLIC_SUPABASE_URL
Value: https://your-project.supabase.co
Environment: Production, Preview, Development
Name: NEXT_PUBLIC_SUPABASE_ANON_KEY
Value: your-anon-key
Environment: Production, Preview, Development
Name: DATABASE_URL
Value: postgresql://...
Environment: Production (server-only, don't expose)
## 3. Click "Save"
## 4. Redeploy your applicationEnvironment-Specific Variables#
## Production only
NEXT_PUBLIC_API_URL=https://api.production.com
DATABASE_URL=postgresql://prod-db
## Preview (branch deployments)
NEXT_PUBLIC_API_URL=https://api.staging.com
DATABASE_URL=postgresql://staging-db
## Development (local)
NEXT_PUBLIC_API_URL=http://localhost:3000
DATABASE_URL=postgresql://localhost:5432Solution 3: Use .env Files Correctly#
Local development setup:
File Priority Order#
## Next.js loads env files in this order (later overrides earlier):
1. .env # All environments
2. .env.local # Local overrides (gitignored)
3. .env.development # Development only
4. .env.development.local # Local dev overrides
5. .env.production # Production only
6. .env.production.local # Local prod overridesExample .env Files#
## .env (committed to git)
NEXT_PUBLIC_APP_NAME=My App
NEXT_PUBLIC_API_VERSION=v1
## .env.local (gitignored - your local secrets)
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-key
DATABASE_URL=postgresql://localhost:5432/mydb
## .env.production (committed - production defaults)
NEXT_PUBLIC_API_URL=https://api.production.com
NODE_ENV=production.gitignore Configuration#
## .gitignore
.env*.local
.env.local
.env.development.local
.env.test.local
.env.production.localSolution 4: Handle Build-Time vs Runtime#
Critical difference:
Build-Time Variables#
// These are bundled at BUILD time
const apiUrl = process.env.NEXT_PUBLIC_API_URL
// If you change the variable after build, it won't update!
// You must rebuild the applicationRuntime Variables (Server-Only)#
// Server-side variables are loaded at RUNTIME
export async function GET() {
const dbUrl = process.env.DATABASE_URL // ✅ Loaded at runtime
// Connect to database
}Solution: Use Runtime Configuration#
In the App Router (Next.js 13.4+), publicRuntimeConfig, serverRuntimeConfig
and next/config's getConfig() have been removed — they only worked in the
Pages Router. Read process.env directly instead. Server Components and Route
Handlers can read any env var at runtime; the client can only read vars prefixed
with NEXT_PUBLIC_.
// Server Component / Route Handler — any env var, read at RUNTIME
export async function GET() {
const dbUrl = process.env.DATABASE_URL // ✅ server-only, runtime
const apiUrl = process.env.NEXT_PUBLIC_API_URL // ✅ also fine on server
}
// Client Component — only NEXT_PUBLIC_* vars are inlined at build time
'use client'
export function Client() {
const apiUrl = process.env.NEXT_PUBLIC_API_URL // ✅ inlined
// process.env.DATABASE_URL is undefined here (not exposed to the client)
}If you need a single typed wrapper, create src/lib/env.ts:
// src/lib/env.ts — server-only, runtime values
export const serverEnv = {
databaseUrl: process.env.DATABASE_URL,
} as const
export const publicEnv = {
apiUrl: process.env.NEXT_PUBLIC_API_URL,
} as constNote: if you are still on the Pages Router (
pages/_app.tsx), the legacypublicRuntimeConfig/serverRuntimeConfig+next/configAPI still works there. It is the App Router that removed it.
Solution 5: Validate Environment Variables#
Catch missing variables early:
Create Validation Schema#
// lib/env.ts
import { z } from 'zod'
const envSchema = z.object({
// Client-side (NEXT_PUBLIC_)
NEXT_PUBLIC_SUPABASE_URL: z.string().url(),
NEXT_PUBLIC_SUPABASE_ANON_KEY: z.string().min(1),
NEXT_PUBLIC_API_URL: z.string().url(),
// Server-side
DATABASE_URL: z.string().url(),
API_SECRET_KEY: z.string().min(32),
SMTP_HOST: z.string().optional(),
})
export const env = envSchema.parse({
NEXT_PUBLIC_SUPABASE_URL: process.env.NEXT_PUBLIC_SUPABASE_URL,
NEXT_PUBLIC_SUPABASE_ANON_KEY: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL,
DATABASE_URL: process.env.DATABASE_URL,
API_SECRET_KEY: process.env.API_SECRET_KEY,
SMTP_HOST: process.env.SMTP_HOST,
})
// Usage
import { env } from '@/lib/env'
const apiUrl = env.NEXT_PUBLIC_API_URL // ✅ Type-safe and validatedBuild-Time Validation#
// scripts/validate-env.js
const requiredEnvVars = [
'NEXT_PUBLIC_SUPABASE_URL',
'NEXT_PUBLIC_SUPABASE_ANON_KEY',
'DATABASE_URL',
]
const missing = requiredEnvVars.filter(key => !process.env[key])
if (missing.length > 0) {
console.error('Missing required environment variables:')
missing.forEach(key => console.error(` - ${key}`))
process.exit(1)
}
console.log('✅ All required environment variables are set')// package.json
{
"scripts": {
"validate-env": "node scripts/validate-env.js",
"build": "npm run validate-env && next build"
}
}Solution 6: Use Vercel CLI for Testing#
Test locally with production environment:
Install Vercel CLI#
npm install -g vercel
## Login
vercel login
## Link project
vercel linkPull Environment Variables#
## Download production env vars to .env.local
vercel env pull .env.local
## Now your local environment matches production
npm run devTest Production Build#
## Build with production env vars
vercel build
## Test production build locally
vercel dev --prodSolution 7: Handle Different Environments#
Manage multiple environments:
Environment-Specific Configuration#
// lib/config.ts
const config = {
development: {
apiUrl: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000',
debug: true,
},
production: {
apiUrl: process.env.NEXT_PUBLIC_API_URL,
debug: false,
},
preview: {
apiUrl: process.env.NEXT_PUBLIC_API_URL,
debug: true,
},
}
const env = process.env.NODE_ENV || 'development'
export default config[env]Vercel Environment Detection#
// Detect Vercel environment
const isProduction = process.env.VERCEL_ENV === 'production'
const isPreview = process.env.VERCEL_ENV === 'preview'
const isDevelopment = process.env.VERCEL_ENV === 'development'
// Use Vercel URL
const vercelUrl = process.env.VERCEL_URL
const siteUrl = isProduction
? 'https://www.iloveblogs.blog'
: `https://${vercelUrl}`Common Mistakes#
-
Mistake #1: Forgetting NEXT_PUBLIC_ prefix - Client-side variables must have this prefix
-
Mistake #2: Not redeploying after adding variables - Vercel needs redeploy to pick up new variables
-
Mistake #3: Committing .env.local - This file should be gitignored
-
Mistake #4: Using wrong environment - Check which environment (Production/Preview/Development) you're configuring
-
Mistake #5: Exposing secrets - Never use NEXT_PUBLIC_ for sensitive data
Conclusion#
Environment variable errors on Vercel are usually caused by missing NEXT_PUBLIC_ prefix for client-side variables, or forgetting to configure variables in the Vercel dashboard. The key rules are:
- Use NEXT_PUBLIC_ prefix for browser-accessible variables
- Configure all variables in Vercel dashboard
- Redeploy after adding/changing variables
- Never commit .env.local to git
- Validate environment variables at build time
Use the Vercel CLI to pull production environment variables locally for testing — note that Vercel scopes each variable to Production, Preview and Development separately, which is the single most common cause of "works locally, undefined in prod". Always validate required variables before deployment to catch issues early.
Remember: NEXT_PUBLIC_ variables are bundled into your JavaScript and visible to users. Never use this prefix for secrets, API keys, or sensitive data.
Further reading: Next.js 'Module not found: Can't resolve': 6 Fixes.
Frequently Asked Questions
Still stuck? Paste your error.
Paste the error message or stack trace — we check it against every fix and tool on the site.
One email a month — the latest explained errors
What the site published that month — mostly Next.js, Supabase and Node errors, explained. You confirm by email; unsubscribe any time in one click.
Continue Reading
Stripe Webhook Signature Verification Failed in Next.js
If Stripe webhooks return `Webhook signature verification failed`, your Next.js route is parsing the JSON before Stripe sees it. Here's the exact raw-body pattern for App Router, Pages Router, and Vercel Edge — plus the three secret-mismatch traps that cause the same error.
Next.js 'Module not found: Can't resolve': 6 Fixes
Module not found errors only in production? Learn why Next.js builds fail after deploy and get 6 proven fixes that work on Vercel, AWS, and other platforms.
Fix "cookies() should be awaited" Error in Next.js 15
Next.js 15 broke synchronous `cookies().get()`. Every server-side call must now `await cookies()` first. Here's the precise migration — App Router pages, route handlers, Server Actions, and Supabase SSR — plus the codemod that fixes 90% of call sites automatically.


