修復 Next.js 瀏覽器環境變數顯示 undefined:NEXT_PUBLIC_ 前綴及重新建置
Fix: a setting shows as "undefined" in the browser of your Next.js app (NEXT_PUBLIC_ environment variable, tested repro, Next 16 + React 19.3)
Next.js App Router 中,伺服器可用但瀏覽器顯示 undefined 的環境變數,常因缺少 NEXT_PUBLIC_ 前綴或在建置後才加入;安全公開的值須改名並重新建置。
A setting such as a booking link works on the server but shows up as undefined in the browser, with no error message and a passing build. In an AI-built (or hand-built) Next.js app, the usual causes are a variable name without the NEXT_PUBLIC_ prefix, or a variable added after the app was built.
We reproduced it on a small synthetic app, applied the smallest fix, and checked it by clicking the button in headless Chrome. Below: the symptom, the cause, the fix, how we verified it, and what we did not test.
Short answer: If a setting such as your booking link or API address works on the server but shows up as
undefinedin the browser, either the variable's name doesn't start withNEXT_PUBLIC_, or you added it after the app was built. The smallest fix is to rename it (and the line of code that reads it) with theNEXT_PUBLIC_prefix, then rebuild. Verified on a synthetic app only.
Prerequisites
- A Next.js app using the App Router (an
app/folder), with a setting stored as an environment variable (a.env.localfile or your host's "Environment Variables" page). - Terminal access, or your AI tool's code view and your host's settings page.
- The symptom: a button, link or request in the browser uses the text
undefined(for example a link that goes toundefined/book). There is no error message, and the build passes. - No keys or accounts are needed for this fix.
-
Only rename settings that are safe for anyone to see. A
NEXT_PUBLIC_value is copied into the code every visitor downloads. Secret keys (service-role keys, payment secrets, passwords) must never get this prefix. See "Security notes".
The exact symptom
There is no red error. This is what we saw in headless Chrome after clicking the button:
text shown after clicking "Book a call": "undefined/book"
The page's code was process.env.BOOKING_URL + "/book", and BOOKING_URL=https://booking.example.test was set in .env.local. The build log even says Environments: .env.local and exits 0. If your code sends that value to a service, you will typically see failed requests or undefined inside a URL in your browser's Network tab instead (we did not test that part).
Versions we used
| Item | Version |
|---|---|
| Node.js | 20.19.2 |
| next | 16.4.3 (Turbopack) |
| react / react-dom | 19.3.1 |
| Browser | Google Chrome 155.0.8059.84, headless, via playwright-core 1.64.1 |
| OS | Linux, single machine |
Cause
Next.js runs your code in two places: on the server, and in your visitor's browser. The browser can't see your server's settings. To let it see a setting, Next.js copies the value into the downloaded JavaScript when you run next build, but only for variables whose names start with NEXT_PUBLIC_, and only if the variable exists at that moment (official docs).
That gives two common ways to get undefined:
-
No prefix.
BOOKING_URLstays on the server. In the browser,process.env.BOOKING_URLis empty. In our built bundle the code was still a lookup at click time:process.env.BOOKING_URL+"/book". -
Added too late.
NEXT_PUBLIC_BOOKING_URLis right, but it wasn't set whennext buildran (for example you added it to your host's settings after the last deploy). The value is "frozen" at build time, so the browser still getsundefineduntil you rebuild. We reproduced this too: building with no variable, then adding.env.localand restartingnext startstill gaveundefined.
Public reports of the same family: the Next.js discussion #74624 (Next 15 + React 19 + Turbopack) and a Stack Overflow answer saying env files are read when the server starts and production needs a rebuild. We found these through web search and did not measure how common the problem is, or how often AI tools cause it.
Smallest working fix
- Rename the variable everywhere it is set (
.env.localand your host's settings) fromBOOKING_URLtoNEXT_PUBLIC_BOOKING_URL. - Change the code that reads it to the same name, written exactly as
process.env.NEXT_PUBLIC_BOOKING_URL. - Rebuild and redeploy (locally: stop the server, run
npx next build, start again; in dev, stop and restartnpm run dev). On a host, trigger a new deploy after saving the variable.
Before (shows undefined/book): app/BookButton.js
"use client";
import { useState } from "react";
export default function BookButton() {
const [link, setLink] = useState("");
return (
<div>
<button onClick={() => setLink(process.env.BOOKING_URL + "/book")}>Book a call</button>
<p id="link">{link}</p>
</div>
);
}
.env.local: BOOKING_URL=https://booking.example.test
After (shows the real link)
- <button onClick={() => setLink(process.env.BOOKING_URL + "/book")}>Book a call</button>
+ <button onClick={() => setLink(process.env.NEXT_PUBLIC_BOOKING_URL + "/book")}>Book a call</button>
.env.local: NEXT_PUBLIC_BOOKING_URL=https://booking.example.test
Write it exactly as process.env.NEXT_PUBLIC_NAME. Per the Next.js docs and the discussion linked above, forms like process.env[name], saving process.env in a variable first, or optional chaining (process?.env?.NAME) are not replaced. We did not test those forms.
If the value must be changeable without rebuilding, or must stay secret, don't use NEXT_PUBLIC_. Read the value in server code and give the browser only what it needs, for example through your own API route. We did not test that approach.
Copyable prompt for your AI coding tool
In my Next.js App Router app, a setting works on the server but is undefined in the browser. Find every place in files marked "use client" (or components imported by them) that reads process.env.SOMETHING where SOMETHING does not start with NEXT_PUBLIC_. For each one, tell me whether the value is safe for any visitor to see. For the safe ones only, rename the variable to NEXT_PUBLIC_SOMETHING in the code, in .env.local and in .env.example, and write the access exactly as process.env.NEXT_PUBLIC_SOMETHING (no optional chaining, no process.env[name], no aliasing). For secrets (keys, tokens, passwords), do NOT add the prefix; instead move that code to a server route and tell me. Do not change anything else, show me the diff, and remind me to set the renamed variables on my host and redeploy.
Review the diff yourself and check no secret got the prefix. We did not test this prompt in any AI tool.
How we verified
Run on 2026-10-24 on a small synthetic app (three copies: before, after, and "added too late"), using headless Chrome with default settings.
-
Builds pass either way:
next buildexited 0 for before and after. The build does not catch this. -
Before, production (
next build+next start): after the click the page showedundefined/book. -
Before, dev (
next dev): same result,undefined/book. -
After, production and dev: the page showed
https://booking.example.test/book. -
Bundle check: the built "before" code still reads
process.env.BOOKING_URLat click time, while the "after" code has the link written into it. -
Added-too-late case (correctly named variable):
- built with no
.env.local, then started:undefined/book; - then added
.env.localand restarted the server without rebuilding: stillundefined/book; - then rebuilt and restarted:
https://booking.example.test/book.
- built with no
Every run also logged one unrelated 404 resource error that we did not identify (probably a missing icon file); we did not confirm which file it was.
Not verified
- Other browsers (Firefox, Safari) and real phones or visitors.
- Other Next.js, React or Node versions, and the Pages Router.
- Vercel, Netlify, Cloudflare, Lovable, Bolt or other hosts: where and when they apply variables during their build. We only simulated "variable missing at build time" locally.
- Vite-style apps (
VITE_names andimport.meta.env). A third-party migration guide says exported Lovable apps needVITE_renamed toNEXT_PUBLIC_; we did not test that. - The optional-chaining,
process.env[name]and aliasing cases, and the "read it on the server and pass it down" alternative. - What a real service request does when given an
undefinedURL; we only checked the text the page built. - Code generated by Cursor, Lovable, Bolt or v0; our app is hand-written and synthetic. We did not measure how often AI tools produce an unprefixed name.
- Whether the AI prompt produces a correct and safe diff in any tool.
Troubleshooting
-
Still
undefinedafter renaming: you probably didn't rebuild. Stop the server, rebuild, start again; on a host, redeploy after saving the variable. -
Works in
npm run devbut not on your live site: the host's build may not have the variable. Add it in the host's settings under the exact same name, then redeploy. -
Check that
.env.localis in the project root (the folder withpackage.json) and the name has no typos or spaces around=. - Mixed up the two places: a variable set only on your laptop isn't on your host, and vice versa.
Security notes
- Anything named
NEXT_PUBLIC_ends up in the code anyone can download and read. Use it only for values meant to be public (a booking link, a public analytics ID, a publishable key). Never for a service-role key, secret key, password or token. - Never paste real keys into an AI chat; use placeholders like
https://booking.example.test. - Don't commit
.env.localto a public repository.
The original guide, with the before/after files, is on The Agent Ledger: A setting shows as "undefined" in the browser: Next.js fix.
AI disclosure: This article was written with the help of AI. The reproduction and the before/after browser runs described above were actually performed and checked against the logs before publishing.
來源:DEV Community · dev.to