Fix Next.js Environment Variables Not Working on Vercel
Next.js environment variables undefined on Vercel? Add NEXT_PUBLIC_ prefix, set variables in Vercel dashboard, and redeploy. Step-by-step guide with debugging tips.
- NEXT_PUBLIC_ variables are inlined at build time and frozen in your browser bundle. Server-only variables stay on the server and are never accessible to the browser.
- Add the NEXT_PUBLIC_ prefix to any variable you need in browser code, otherwise Next.js assumes it is secret.
- Set all variables in the Vercel dashboard, then redeploy your app. Adding a variable and clicking Save does nothing; Vercel inlines variables at build time, not at runtime.
- Use
vercel env pullto download your Vercel environment variables to.env.local and test them locally before deploying. - If Vercel's usage-based pricing or data residency constraints become blocking, Ship offers flat-rate hosting with EU servers and a self-hosted option.
Your Next.js environment variables work locally but fail on Vercel. This happens because Vercel inlines NEXT_PUBLIC_ variables at build time, and you either forgot the prefix, left the variable out of the Vercel dashboard, or you are trying to access a server-only secret in the browser. This guide walks you through every cause and fix.
Why Are My Next.js Environment Variables Undefined on Vercel?
Your app works locally because your development server loads.env files at runtime. Vercel works differently: it inlines NEXT_PUBLIC_ variables during the build step, then freezes them into the JavaScript bundle. Server-only variables never reach the browser. When you deploy, the mismatch between what your local.env says and what Vercel actually provides causes undefined errors.
The core problem is not your code. It is the difference between local development and Vercel's build process.
Local development reads your.env file every time you start the dev server. This file is in.gitignore, so it never reaches Vercel. When you deploy to Vercel, your code has no.env file to read. Instead, Vercel reads the environment variables you set in its dashboard or via CLI, then bakes them into your build.
Vercel's build system runs in two phases: build time (where NEXT_PUBLIC_ variables are inlined) and runtime (where your server can access non-public variables). Browser code always runs after build time, so it can only see NEXT_PUBLIC_ variables. Trying to access process.env.API_KEY in a React component returns undefined because API_KEY was never a NEXT_PUBLIC_ variable.
NEXT_PUBLIC_ vs. Server-Only: What's the Difference and Where Do They Work?
NEXT_PUBLIC_ variables are bundled into the browser and inlined at build time. Server-only variables stay on the server and are never sent to the browser. Browser code that tries to access a server-only variable gets undefined.
Here is where each works:
- NEXT_PUBLIC_API_URL: Available in browser code (React components, client scripts), server code (API routes, getServerSideProps), and build time.
- DATABASE_URL (no prefix): Available only in server code (API routes, getServerSideProps, server utilities). The browser never sees it.
- process.env.API_KEY in a React component: Returns undefined if API_KEY is not NEXT_PUBLIC_.
The NEXT_PUBLIC_ prefix tells Next.js to inline this variable into the browser bundle. Without it, Next.js assumes the variable is secret and does not include it.
When you set DATABASE_URL=postgres://... in the Vercel dashboard and forget to deploy the change, your server-side code gets the old value or undefined. Server-only variables are read from the environment at runtime, not at build time. This is different from NEXT_PUBLIC_ variables, which are frozen the moment your build completes.
What Do These Error Messages Actually Mean?
These four errors are the most common, and they all point to the same root cause: a mismatch between local and Vercel.
ReferenceError: process is not defined
You tried to access process.env in browser code. Next.js's build system replaced all references to process.env.SOME_VAR with their literal values (if NEXT_PUBLIC_). If a variable has no NEXT_PUBLIC_ prefix, the build sees it as a reference to the global process object, which does not exist in the browser. Add the NEXT_PUBLIC_ prefix to make it browser-safe.
API_URL is undefined
Your code references process.env.API_URL in a React component, but you set API_URL (not NEXT_PUBLIC_API_URL) in the Vercel dashboard. Rename your variable to NEXT_PUBLIC_API_URL in the dashboard, and update your code to use process.env.NEXT_PUBLIC_API_URL.
process.env is undefined
You tried to iterate over process.env or access it dynamically. Next.js's build system cannot inline dynamic process.env access. Use explicit variable names only: process.env.NEXT_PUBLIC_FOO works; process.env[variable] does not.
Variable is undefined even after deploying
You set the variable in the Vercel dashboard but did not redeploy. Environment variables are inlined at build time, not at runtime. Adding a variable to the dashboard does nothing until you trigger a new build. Go to the Vercel dashboard, click Deployments, and click the three-dot menu next to your latest deployment, then Redeploy.
How to Configure Environment Variables in Vercel (Step-by-Step)
Vercel environment variables are scoped to Production, Preview, and Development environments. Each can have different values.
- Go to your Vercel project dashboard.
- Click Settings > Environment Variables.
- Enter the variable name (e.g., NEXT_PUBLIC_API_URL or DATABASE_URL).
- Enter the value (e.g., your API domain URL or your database connection string).
- Select which environments should have this variable. Check Production to use it in production deployments. Check Preview to use it in preview deployments from pull requests. Check Development to use it when you run
vercel devlocally. - Click Save.
- Trigger a new deployment. Go to Deployments and click Redeploy on your latest deployment. Do not just click Save and expect the variable to appear; Vercel inlines variables at build time.
If you have multiple environments (staging, production), create separate Vercel projects or use Vercel's branch-specific preview variables. Preview deployments from pull requests can have different variables than your main branch production deployment.
Vercel also enforces a 64 KB size limit per deployment for all environment variables combined. If you have many secrets, compress them or split them across multiple deployment processes.
How to Set Up.env Files Correctly for Local Development
Your.env file is never uploaded to Vercel. It exists only on your local machine for development. Vercel reads from its dashboard, not from your repo.
Next.js loads.env files in this priority order:
1..env.production.local (production builds only)
2..env.development.local (development with next dev only)
3..env.local (all environments)
4..env.production (production builds only, committed to git)
5..env.development (development only, committed to git)
6..env (all environments, committed to git)
Create.env.local in your project root. Add your NEXT_PUBLIC_ variables with values you want for local development, your database connection string, and any API keys your server needs. For example, NEXT_PUBLIC_API_URL could point to a local development API server, DATABASE_URL could point to a local database, and API_KEY could contain your development secret key.
Add.env.local and.env*.local to.gitignore so they never reach your repo or Vercel:
.env.local
.env.*.local
.env.development.local
.env.production.local
Commit.env.development and.env.production if they contain non-secret defaults, but keep all secrets in.env.local or in Vercel's dashboard.
When you run next dev locally, Next.js loads.env.development.local, then.env.local, then.env.development, then.env. When you run next build && next start (production), it loads.env.production.local, then.env.local, then.env.production, then.env.
Build-Time vs. Runtime: The Critical Distinction
NEXT_PUBLIC_ variables are inlined at build time. Once the build finishes, their values are frozen in the JavaScript bundle. Changing a NEXT_PUBLIC_ variable in the Vercel dashboard and restarting your server does nothing; you must redeploy.
Server-only variables are read at runtime. Changing DATABASE_URL in the Vercel dashboard and restarting your server picks up the new value. But the browser never has access to it.
This is the key difference. NEXT_PUBLIC_ variables are static; server-only variables are dynamic.
Example: You set NEXT_PUBLIC_API_URL=https://old-api.com in Vercel. Your build completes and bundles https://old-api.com into the JavaScript. Now you change it to https://new-api.com in the Vercel dashboard. Your deployed app still points to the old URL because the new value is not in the bundle. You must click Redeploy to trigger a new build that includes the new URL.
If you need to change a secret at runtime without redeploying, keep it as a server-only variable (no NEXT_PUBLIC_ prefix). Your API routes can read it from process.env.DATABASE_URL at request time. Browser code cannot access it, but your server can.
How to Debug Environment Variables Locally and in Vercel
Start by checking what variables are available locally.
Add this to a test API route (e.g., pages/api/debug.js):
export default function handler(req, res) {
res.json({
next_public_api_url: process.env.NEXT_PUBLIC_API_URL,
database_url: process.env.DATABASE_URL || 'not set',
});
}
Run next dev and visit your development server's debug API route. This shows what variables your local server sees.
For browser-accessible variables, log them in a React component:
console.log(process.env.NEXT_PUBLIC_API_URL);
Open the browser console. If it is undefined, you forgot the NEXT_PUBLIC_ prefix.
To debug on Vercel, pull the environment variables that Vercel has stored:
vercel env pull.env.local
This downloads the variables you set in the Vercel dashboard to a local.env.local file. Now run next dev and check that your variables load correctly. If they match what you set in Vercel but your deployed app still fails, your code is trying to access a server-only variable in the browser.
To test a production build locally, run:
next build
next start
This simulates the production build and runtime. Any NEXT_PUBLIC_ variables in.env.local are inlined and frozen. If your production build works locally but fails on Vercel, Vercel's variables are set differently than your.env.local.
For deeper validation, use Zod or a similar schema library to enforce that required variables are present:
import { z } from 'zod';
const envSchema = z.object({
NEXT_PUBLIC_API_URL: z.string().url(),
DATABASE_URL: z.string().url(),
});
const env = envSchema.parse(process.env);
export default env;
If a required variable is missing, the app fails loudly at startup rather than later with an undefined error. This catches configuration mistakes early.
When to Consider Moving Away from Vercel
Vercel is the default for Next.js, but it has trade-offs. Vercel charges per request and per execution time, so variable costs scale with traffic. Data residency is limited to Vercel's regions (US, Europe, Asia). You cannot run custom server code beyond Node.js. If any of these constraints matter to you, consider an alternative.
Ship is a managed Next.js alternative with flat-rate pricing. Instead of paying per request, you pay a fixed monthly fee for a fixed allocation of compute and bandwidth. This works better for teams that want predictable costs or are frustrated by Vercel's usage-based billing.
Ship also offers EU data residency in Germany, so you can keep data within EU borders if compliance requires it. If you need even more control, Ship's self-hosted option lets you deploy the entire platform to your own infrastructure.
Nortflank is another option if you want pay-as-you-go pricing without Vercel's per-request model. Northflank charges $0.01667 per vCPU-hour, so a 1vCPU container costs about $12 per month. This is cheaper than Vercel's Pro plan ($20/month) for low-traffic apps, but you pay for the full month even if your app uses less than you estimated.
Start with Vercel. Fix your environment variables, redeploy, and move on. Only switch platforms if Vercel's costs, data residency, or control constraints become blockers.
Frequently Asked Questions
How do I know if a variable should be NEXT_PUBLIC_?
If your React component, Next.js page, or client-side script needs it, add NEXT_PUBLIC_. If only your API routes or getServerSideProps need it, omit the prefix. Database connection strings, API keys, and secrets should never be NEXT_PUBLIC_.
Why does my variable work locally but not on Vercel?
Your.env.local works locally because next dev reads it at runtime. Vercel does not use your.env file. You must set the variable in the Vercel dashboard and redeploy.
Do I need to restart Vercel after changing a NEXT_PUBLIC_ variable?
No. You must redeploy. Restarting does nothing because the old value is already in the bundle. Redeploy triggers a new build that includes the new value.
Can I use.env files on Vercel?
No. Vercel does not read.env files from your repository. All variables must be set in the Vercel dashboard, via CLI, or via the Vercel API.
How do I set different variables for Production and Preview deployments?
In the Vercel dashboard, select the variable and check Production, Preview, or Development. You can set the same variable name to different values for each environment.
What is the difference between.env and.env.local?
.env is committed to git and shared with your team..env.local is not committed (add it to.gitignore) and is for your machine only. Use.env for non-secret defaults and.env.local for personal secrets.
Do environment variables work in static pages or only in API routes?
NEXT_PUBLIC_ variables work everywhere: static pages, API routes, client components, server components. Server-only variables work only in API routes, getServerSideProps, and server components. They do not work in static HTML generated at build time.
The Bottom Line
Your Next.js environment variables are undefined on Vercel because you are mixing local development (which reads.env files at runtime) with Vercel's build process (which inlines NEXT_PUBLIC_ variables at build time). Add the NEXT_PUBLIC_ prefix for browser-accessible variables, set them in the Vercel dashboard, and redeploy. Server-only variables stay on your server and are never sent to the browser. If Vercel's per-request pricing or data residency becomes a constraint, Ship offers predictable flat-rate pricing and EU hosting as an alternative.