---
# System prepended metadata

title: Next.js 中 NEXT_PUBLIC_ 環境變數的 Build-time 陷阱
tags: [next.js]

---

# Next.js 中 NEXT_PUBLIC_ 環境變數的 Build-time 陷阱

在使用 Next.js 進行開發時，許多人會直覺地認為，只要修改 `.env` 檔案或系統環境變數，前端程式碼就能在部署後即時反映最新設定。然而，當應用程式完成正式建置並部署至 Production 環境後，往往會發現一件令人困惑的事情──**以 `NEXT_PUBLIC_` 開頭的環境變數並不會隨環境變化而更新**。

這個行為在開發階段不易察覺，但一旦進入正式環境，便很容易成為實際踩到的坑。

---

## 原因：NEXT_PUBLIC_ 會在建置階段被「寫死」

根據官方文件  
[Bundling Environment Variables for the Browser](https://nextjs.org/docs/pages/guides/environment-variables#bundling-environment-variables-for-the-browser)，Next.js 在建置（build）階段，會將所有以 `NEXT_PUBLIC_` 開頭的環境變數**直接內嵌（inline）到前端 bundle 中**

這代表：

- 以 `NEXT_PUBLIC_` 開頭的變數，一旦完成建置，無論部署到哪個環境，這些值都不會再改變

## 解決方法

如果確實有需求在前端取得**執行時（runtime）**的環境變數，一個可行且穩定的做法是：

1. 在 **Server Component**（例如 Root Layout）中讀取 `process.env`
2. 將變數透過 `<Script>` 注入到瀏覽器的 `window` 物件
3. 由前端程式碼在 runtime 讀取這些值

這樣即可避開 Next.js 在建置階段對 `NEXT_PUBLIC_` 的靜態替換行為。

### Layout 範例

```tsx
import Script from "next/script";

export default async function RootLayout({
  children,
  params,
}: {
  children: React.ReactNode;
  params: Promise<{ lng: string }>;
}) {
  const { lng } = await params;

  // 使用動態索引方式，避免在 build-time 被靜態替換
  const runtimeUrl = process.env["NEXT_PUBLIC_APP_URL"];

  return (
    <html lang={lng}>
      <body>
        <Script id="env-script" strategy="beforeInteractive">
          {`window.ENV = {
            NEXT_PUBLIC_APP_URL: ${JSON.stringify(runtimeUrl)}
          };`}
        </Script>

        {children}
      </body>
    </html>
  );
}
```

這樣在前端任何的地方都可以透過 `window.ENV.NEXT_PUBLIC_APP_URL` 來取得動態變數

### Hono Client 範例

```TypeScript
import { hc } from "hono/client";
import type { HonoAppRoute } from "@/app/api/[...route]/route";

const getBaseUrl = () => {
    if (typeof window !== "undefined" && (window as any).ENV?.NEXT_PUBLIC_APP_URL) {
        return (window as any).ENV.NEXT_PUBLIC_APP_URL;
    }

    return process.env.NEXT_PUBLIC_APP_URL || "";
};

const client = hc<HonoAppRoute>(`${getBaseUrl()}/`, {
    init: {
        credentials: "include",
    },
});

export { client as apiClient };
```