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:
- 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
Related Articles#
- Next.js Turbopack Stuck on Compiling How to Fix
- Fix Next.js Build Error Module Not Found After Deploy
- Resolve Next.js Hydration Mismatch Errors Complete Guide
- Fix ChunkLoadError: Loading Chunk Failed in Next.js
- VERCEL_EXPERIMENTAL_DEV_SKIP_LINK: Stop Dev Link Hangs
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. 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.
Frequently Asked Questions
One email a month — no fluff
RLS gotchas, Next.js cache debugging, and the one Supabase setting that bit me last month.
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.
7 Lessons From Running Next.js + Supabase in Production
Seven recurring patterns from production runs of Next.js + Supabase, and the fixes that prevented the most common failures.
Browse by Topic
Find stories that matter to you.
