Environment variables are undefined in Next.js (process.env returns undefined)
Filed under Next.js & React errors
The fix, fast
In Next.js, a variable only reaches the browser if its name starts with NEXT_PUBLIC_ and it was present when the app was built — everything else stays server-only and reads as undefined in client code. Put your variables in .env.local in the project root, restart the dev server locally, and on Vercel add them under Project Settings, then Environment Variables, and redeploy, because changing a variable does not update an already-built deployment.
Why this happens
Environment variables — settings stored outside your code, like API URLs and keys — work differently in Next.js than beginners expect, because Next.js builds two bundles: one that runs on the server and one that is sent to the browser. Anything sent to the browser is public by definition, so Next.js only includes variables whose names start with NEXT_PUBLIC_. A variable named API_URL will work in a Server Component, a route handler, or any server code, and silently be undefined the moment client code tries to read it.
Timing is the second trap. Public variables are inlined — literally baked into the JavaScript — at build time, not read fresh on every request. So fixing a typo, adding a variable in the Vercel dashboard, or editing .env.local changes nothing for the copy that is already built and running. You need a restart locally and a redeploy on Vercel. Placement is the third trap: Next.js only loads files like .env.local from the project root, next to package.json, and a file named just .env committed to git or tucked in a subfolder is easy to get wrong.
- Missing NEXT_PUBLIC_ prefix: the value exists on the server but is stripped from browser code.
- Wrong file or folder: the file is not
.env.local(or.env) in the project root. - No restart or redeploy: the running build still holds the old, baked-in values.
- Dashboard vs local mismatch: it works locally because
.env.localexists, but the variable was never added on Vercel.
The fix, step by step
- Decide whether this value should be public at all. Ask where the code that reads it runs. If it runs in the browser — a Client Component with
"use client", or any code the browser downloads — it needs theNEXT_PUBLIC_prefix and every visitor can see the value. Never put a true secret, like a private API key or a service role key, behindNEXT_PUBLIC_. Secrets belong in unprefixed variables read only by server code.// Browser code can read this: NEXT_PUBLIC_API_URL=https://api.example.com // Server-only. Browser code will get undefined: DATABASE_URL=postgres://... API_SECRET_KEY=... - Check the file name and location. In your project root — the folder containing
package.json— you should have a file named.env.localfor local development. Notenv.txt, not insidesrc/, not insideapp/. Each variable goes on its own line asNAME=valuewith no spaces around the equals sign and usually no quotes. Confirm the variable name in the file matches the name in your code character for character, including the prefix.your-project/ package.json .env.local <-- here, in the root app/ page.tsx - Read it the static way in client code. Next.js replaces a direct reference like
process.env.NEXT_PUBLIC_API_URLat build time. Accessing it dynamically, for example by building the name in a variable or destructuring the wholeprocess.envobject in client code, can defeat that replacement and return undefined even when everything else is right. Use the full, literal name where you need it.// Works in client code: const apiUrl = process.env.NEXT_PUBLIC_API_URL; // Often fails in client code: const key = "NEXT_PUBLIC_" + "API_URL"; const apiUrl2 = process.env[key]; - Restart the dev server after every .env change. Stop the dev server completely and start it again. A hot reload or a browser refresh is not enough, because Next.js reads the env files when the server starts and when it builds. If the value still shows undefined after a restart, print the exact name you are reading and compare it to the file — a missing underscore is a common culprit.
# In your terminal: Ctrl+C to stop, then: npm run dev - Add the variable on Vercel and redeploy. In the Vercel dashboard, open your project, go to Settings, then Environment Variables, and add the same name and value. Choose the environments it should apply to — Production, Preview, or both. Then trigger a new deployment from the Deployments tab (or push a new commit). Editing the dashboard alone does not change the live site, because the previous build already baked in the old values.
- Never commit secrets, and confirm .env.local is ignored. Check that
.gitignoreincludes.env*.localso your local secrets never go to git. If a secret was ever committed or shipped with aNEXT_PUBLIC_prefix, treat it as exposed: rotate it — generate a new key and revoke the old one — rather than just deleting the line. For teammates, share variable names in a checked-in.env.examplefile with empty values, never the real values.
Give your AI this prompt
I am in a Next.js App Router project and process.env is returning undefined for my variable. Variable name I am reading in code: [paste the exact process.env... line and say whether the file using it is a Server Component, a Client Component with "use client", or a route handler] File my variables are in: [name and location, e.g. .env.local in the project root] Where it fails: [locally / on Vercel / both] Please check in this order: 1) whether browser code needs the NEXT_PUBLIC_ prefix and whether the value is safe to expose publicly, 2) file name and root placement, 3) whether I am accessing process.env with a literal name rather than a dynamic key, 4) whether I restarted locally, 5) whether the variable exists in Vercel Project Settings > Environment Variables and I redeployed after adding it. Constraints: do not put any secret value behind NEXT_PUBLIC_, do not commit .env.local, make one change at a time, and do not rewrite unrelated files. Show me the checklist result for each step before changing code.