Fix · Works locally, fails in production: missing env vars and secrets

NEXT_PUBLIC env variable undefined in browser after build

A NEXT_PUBLIC_ variable works locally but is undefined in the production browser bundle, or the hosted build fails because the variable is undefined. Next.js copies the value into the JavaScript when next build runs. The usual cause is that the environment running the build did not have the variable at that moment.

Likely causes, most common first

Cause 1 · in 2 of 3 matching cases

The variable was not set in the environment that runs the build

Locally, Next.js reads the value from a .env file, which is normally excluded from the repository. The build host has no such file, so you must set the value in its environment variable settings, scoped to the environment being built. If your code throws on undefined, a missing value fails the build. If your code falls back to a placeholder, the build ships the placeholder, for example a mock sign-in picker or a localhost site URL in robots.txt.

How to tell: The variable exists in your local .env.local but is missing from the host's settings for the Production or Preview environment that failed.

Cause 2

The value was added or changed after the deployment was built

A NEXT_PUBLIC_ value is fixed when the build runs. Editing it in the host's settings does not change an existing deployment. The live site keeps the old value, or undefined, until the next build.

How to tell: The settings page shows the right value, but the live bundle still has the old one and the deployment is older than the edit.

Cause 3

The name has no NEXT_PUBLIC_ prefix

Next.js does not send variables without the prefix to the browser. They resolve on the server and are undefined in client code. A rename or a typo in the prefix produces this symptom.

How to tell: The same variable works in a Route Handler or Server Component but is undefined in a Client Component.

Cause 4

Client code reads the variable through a dynamic lookup

Next.js inlines the literal expression process.env.NEXT_PUBLIC_NAME. It does not replace a read through a variable key or through an alias of process.env, so the browser sees undefined even when the build had the value.

How to tell: Client code uses process.env[name] or reads from const env = process.env.

Check and fix it, step by step

  1. Read the live robots.txt for a localhost URL

    If a site URL variable is involved, request /robots.txt on the production domain and read the Sitemap line. A localhost address there means the build used a fallback instead of your production URL.

    curl -s https://your-domain.example/robots.txt | grep -i sitemap

    Docs: nextjs.org →

  2. Check the variable is set for the environment that built

    In the host's Environment Variables settings, confirm the variable is enabled for Production. If preview builds fail, confirm it is enabled for Preview too. A build gets no value from an environment that is not ticked.

    Docs: vercel.com →

  3. Check the name and how client code reads it

    Make sure the name starts with NEXT_PUBLIC_ and that client code writes the full literal, not a variable key or an alias of process.env.

    // inlined at build
    const id = process.env.NEXT_PUBLIC_ANALYTICS_ID
    
    // not inlined
    const key = 'NEXT_PUBLIC_ANALYTICS_ID'
    const bad = process.env[key]

    Docs: nextjs.org →

  4. Add the variable to the missing environments

    Create the variable in the host's settings for each environment that builds: Production, and Preview if previews should work. Use the production value, not a localhost one.

    Docs: vercel.com →

  5. Trigger a new production build

    A settings change does not reach existing deployments. Push a commit to the production branch or redeploy, so next build runs with the new value.

    vercel --prod

    Docs: vercel.com →

  6. Derive the site URL from a platform variable if you want a fallback

    On Vercel, VERCEL_PROJECT_PRODUCTION_URL holds the production domain without https://, so add the scheme yourself. Enable system variables in project settings. After the redeploy, re-check /robots.txt, because robots.ts is cached by default.

    // app/robots.ts
    import type { MetadataRoute } from 'next'
    
    const base = process.env.NEXT_PUBLIC_SITE_URL
      ?? `https://${process.env.VERCEL_PROJECT_PRODUCTION_URL}`
    
    export default function robots(): MetadataRoute.Robots {
      return { rules: { userAgent: '*', allow: '/' }, sitemap: `${base}/sitemap.xml` }
    }

    Docs: vercel.com →

Quick check: curl -s https://your-domain/robots.txt | grep -i localhost; grep -rn "process.env\[" app src

How often this shows up in our data

3 of the 431 verified cases from the last 12 months in our data match this symptom (0.7%). The most common cause was “The variable was not set in the environment that runs the build” (2 of 3); 1 didn't show which cause. How we collect and verify cases.

With Gemmein

Gemmein's app key (pk_…) is public and locked to your domains, so the docs say it is safe to commit and belongs in the repo, e.g. in .env.production. Secret keys are kept out of the browser bundle: npx gemmein check flags, and npx gemmein go-live refuses, a build whose output carries one.

Questions

Why is process.env.NEXT_PUBLIC_X undefined in the browser but fine locally?

The browser bundle contains the value the build environment had when next build ran. Locally, Next.js finds the value in your .env files. The build host does not have those files, so set the variable in its settings.


Do I need to redeploy after changing an environment variable?

Yes. On Vercel, a change to environment variables applies to new deployments, not previous ones. The Vercel docs also recommend a redeploy after changing environment variables.


Can I read a NEXT_PUBLIC_ value at runtime instead of build time?

Not in the browser bundle. Next.js says that to get runtime values in the client, you need your own API that provides them. On the server, variables can be read at request time during dynamic rendering.


Why does the variable work in a Route Handler but not in a Client Component?

Without the NEXT_PUBLIC_ prefix, Next.js keeps a variable on the server. Rename it with the prefix if the browser needs it. Don't do that for secrets, because the value ends up in the JavaScript.


Sources

Every cause and step above was checked against these pages on 6 Oct 2026.

The broader pattern

This is one symptom of a wider failure pattern: Works locally, fails in production: missing env vars and secrets. The guide covers every cause we see for it, on any stack.

Get a heads-up when your stack breaks something

Leave your email and we'll let you know when something big changes for your stack. Unsubscribe any time by replying. Gemmein Limited. Research terms · Privacy

← All fixes