Vercel Environment Variables Not Updating? Build Time vs Runtime
You edit a variable in the Vercel dashboard, reload the site, and the old value is still there. Sometimes it's worse: the server logs the new value but the browser still sends requests to the old API URL. Both come from the same two facts, and neither is a bug. This post walks through them, using the Vercel docs and the Next.js 16 docs bundled in node_modules as of November 2026.
Fact 1: a change only applies to new deployments
Vercel's docs say it directly: changes to environment variables are not applied to previous deployments, only to new ones, and you must redeploy for a changed value to take effect. A deployment is a snapshot. The variables it was built and run with are fixed when it is created, so editing the setting afterwards does nothing to the deployment that is already live.
The fix is to create a new deployment of the right kind:
- Production variables apply to your next Production deployment. Push a commit to the production branch, or run
vercel --prod. - Preview variables apply to the next Preview deployment, meaning a push to any other branch or a plain
vercel. - Development variables are only used locally. Download them with
vercel env pull, which writes them to a.envfile.
A common version of this mistake is changing a variable for Production and then redeploying an old Preview, or the other way around. Check which environment boxes were ticked when you saved.
The dashboard's Redeploy action is the shortest route. Vercel's deployment docs list "changing the Environment Variables" among the cases where you should redeploy. The redeploy dialog also asks whether to use the existing build cache; I cover the build cache in another post.
Fact 2: NEXT_PUBLIC_ values are baked into the JavaScript
Redeploying fixes most server-side cases. The browser case has an extra twist, and it is a Next.js behavior, not a Vercel one. The Next.js docs explain that a variable prefixed with NEXT_PUBLIC_ is inlined at build time: during next build, every reference to process.env.NEXT_PUBLIC_... is replaced with the literal value, and the result ships in the client bundle.
// Before build
const api = process.env.NEXT_PUBLIC_API_URL;
// What the browser receives (value taken from the build environment)
const api = "https://api.example.com";The docs spell out the consequence: after the build, the app no longer responds to changes in these variables. They are frozen with the value from the moment of the build. The docs use a Docker image promoted between environments as the example, but the same logic applies on Vercel. The value belongs to the build that produced the deployment.
So for a NEXT_PUBLIC_ change there is no "just restart it". You need a deployment that was actually built after the change.
Dynamic lookups are not inlined
Inlining is a text replacement on the exact expression process.env.NEXT_PUBLIC_NAME. The Next.js docs warn that these two forms are not inlined:
const name = "NEXT_PUBLIC_API_URL";
const a = process.env[name]; // not inlined
const env = process.env;
const b = env.NEXT_PUBLIC_API_URL; // not inlinedIn the browser those reads come back undefined. If a helper such as getEnv("NEXT_PUBLIC_API_URL") works on the server and fails in the client, this is the first thing to check. Write the full process.env.NEXT_PUBLIC_API_URL expression where the value is used.
Server variables: build time or request time?
A variable without the prefix never reaches the browser, but when it is read still matters. Next.js docs say you can read environment variables on the server at request time during dynamic rendering. Their example calls connection() from next/server before reading process.env, so the value is evaluated when the request arrives:
import { connection } from "next/server";
export default async function Page() {
await connection();
const flag = process.env.FEATURE_FLAG;
return <p>Flag: {flag}</p>;
}The docs also note that cookies, headers and other request-time APIs opt a route into dynamic rendering in the same way. A page that is rendered statically at build time is different: whatever it read from process.env during the build is part of the generated output. Treat statically generated pages and NEXT_PUBLIC_ values as build-time, and request-time reads in dynamic routes as runtime.
Even runtime reads only see the variables of the deployment they run in. Dynamic rendering does not change Fact 1, so after editing a server variable you still redeploy.
If you want a value the browser can change without a rebuild, the Next.js docs recommend your own API that provides the runtime value to the client, on demand or at initialization. A small route handler that returns the setting is enough, and it should return only values that are safe to expose.
Other things worth checking
These come from the Vercel docs and are quick to rule out:
- Size. Variables can total 64 KB per deployment on Node.js, Python, Ruby and Go runtimes, and no single variable can exceed that. Functions and Middleware on the
edgeruntime are limited to 5 KB per variable. A large certificate or JWT is the usual way to hit this. - Branch overrides. For Preview, a variable can be limited to a specific Git branch. A branch-specific variable overrides a general Preview variable with the same name, so a branch may be using a different value than the one you just edited.
- Local files. Next.js looks up variables in this order:
process.env, then.env.$(NODE_ENV).local,.env.local,.env.$(NODE_ENV),.env. A stale value in.env.localcan mask what you pulled. With asrcdirectory, the.envfiles still live in the project root, not insidesrc. - Where you changed it. Variables can be declared per project or at team level. Check that you edited the one the project actually uses.
A short debugging order
- Is the page you are looking at from a deployment created after the change? Look at the deployment's creation time, not the dashboard's setting.
- Is it the right environment: Production, Preview or Development?
- Is the variable
NEXT_PUBLIC_? If so, only a fresh build helps, and every use must be a literalprocess.env.NEXT_PUBLIC_NAME. - For server variables, is the code running at request time, or was it evaluated while the page was generated?
- Is a branch override, a
.env.localfile or a team-level variable supplying a different value?
Summary
Vercel freezes environment variables per deployment, so any change needs a new deployment. On top of that, NEXT_PUBLIC_ variables are written into the JavaScript during the build, which means a rebuilt deployment is the only thing that changes them, and they must be referenced as a literal expression. Reads in dynamically rendered server code see the value of their own deployment at request time. Once you think about each value as "which build and which deployment does this belong to", most stale-variable reports explain themselves.