Vite 的 VITE_ 環境變數會外洩嗎?從 bundle 找出不該公開的 secret
如果你把 API_KEY 改成 VITE_API_KEY,前端就能讀到它;但同一個動作也代表這個值會進入瀏覽器可以下載的 HTML、JavaScript 或 source map。Vite 官方文件明確提醒,VITE_* 不應放敏感資訊,因為它們會在建置時被打包進 client source。
因此,問題不是「怎麼把 .env 藏得更深」,而是先判斷這個值是否本來就能讓每位訪客看到。只要它會讓你不敢貼到公開 issue 或 gist,就不該放在前端的 VITE_ 變數裡。
先把公開設定與私密 secret 分開
下面的命名方式刻意把邊界寫在檔案裡:
# 可以讓每個瀏覽器知道的公開設定VITE_API_ORIGIN=https://api.example.com
# 只給伺服器或 edge function 使用PAYMENTS_SECRET=replace-me-on-the-server前端可以讀取公開值:
const apiOrigin = import.meta.env.VITE_API_ORIGIN;
fetch(`${apiOrigin}/public-config`);但 VITE_ 不是權限系統,也不是加密。它只是告訴 Vite:「這個變數預期會被送到 client」。沒有 VITE_ 前綴的變數不會因此自動變成伺服器 API;要使用私密值,仍要由 server、serverless function 或 edge function 讀取,再提供受驗證的 API。
如果你使用 Cloudflare Workers,可以對照 Workers 本機 .dev.vars、.env 與 secrets 的分工,把「建置時公開值」和「runtime secret」放在不同邊界。
先在 dist 找出可能被打包的值
建置後檢查實際輸出,不要只看 .env 是否被 .gitignore 排除:
pnpm exec vite buildrg -n --hidden 'PAYMENTS_SECRET|DATABASE_URL|PRIVATE_TOKEN' dist如果變數名稱沒有被保留,改用一小段非敏感的測試標記搜尋;不要把 live token 直接放進 shell history、CI log 或文章範例。也要檢查已部署的 JavaScript 與 source map,因為舊 deployment 或 CDN cache 可能仍保留先前的輸出。
若確認真的有 secret 進入 bundle,處理順序應該是:
- 先撤銷或輪替已暴露的金鑰,降低舊 bundle 的影響時間。
- 從 client 設定移除私密值,改由 server 或 edge endpoint 代為呼叫上游 API。
- 從乾淨的設定來源重新建置與部署,必要時清理 hosting 平台的舊產物或 cache。
- 再次下載正式環境的資產,確認新 bundle 不含不該公開的值。
私密 API 呼叫要移到 server 邊界
瀏覽器只送出必要的業務資料,server 端才讀取 secret 並呼叫上游:
browser -> /api/report -> server 或 edge function -> private API 讀取 PAYMENTS_SECRET前端只需要呼叫自己的受控路徑:
const response = await fetch('/api/report', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ range: 'week' }),});這個 endpoint 仍要驗證使用者、權限、輸入和上游回應。把 token 移到 server 不代表可以做一個任意轉發 URL 的 proxy,否則只是把憑證藏起來,沒有縮小上游 API 的操作範圍。
envPrefix 要縮小公開契約,不要放寬
Vite 預設只公開 VITE_ 前綴。若專案要使用其他公開命名,可以在 vite.config.ts 指定更清楚的前綴:
import { defineConfig } from 'vite';
export default defineConfig({ envPrefix: ['PUBLIC_'],});這仍然是「公開給瀏覽器」的清單,不是 secret store。不要把 envPrefix 設成空字串,也不要用過寬的前綴讓整個 process environment 進入 bundle;Vite 官方 shared options 甚至會針對空字串設定直接丟錯,避免意外洩漏。
如果只是因為某個值在 production 變成 undefined,不要用加上 VITE_ 來掩蓋問題。先確認 mode、.env.production、執行建置的工作目錄和 hosting 的 build environment;真的屬於私密值時,正確修法是改成 server runtime。
靜態 Vite 部署要記住建置時機
Vite 會在 vite build 時把 client env 替換成靜態資產:
build environment -> vite build -> HTML / JavaScript -> CDN因此,部署平台在 build 完成後才改 dashboard 的環境變數,不會回頭修改已產生的 JavaScript。安全的公開設定需要重新建置並部署;私密設定則不應加入前端 bundle,而要透過 runtime endpoint 或 server-rendered response 提供必要且可公開的部分。
每次合併前可以用三個問題做快速檢查:
- 這個值是每位訪客都可以知道的公開設定,還是只供 server 使用的 secret?
- 變數名稱是否落在預期的公開前綴與 mode?
- 產出的
dist和正式部署資產是否只包含可公開的資料?
結論很簡單:VITE_ 的意思接近「把這個值送到瀏覽器」。如果值被複製到公開程式碼時會造成安全事件,就不要用前端環境變數解決它。
常見問題
Q: VITE_API_KEY 算是 secret 嗎?
A: 不應把它當成 secret。VITE_ 變數會被 Vite 替換進 client source,訪客可以從瀏覽器資產取得。只有本來就能公開的識別值或受限公開金鑰,才適合放在這個前綴下;權限、來源和額度仍要在服務端限制。
Q: 拿掉 VITE_ 後為什麼變成 undefined?
A: 這是 Vite 的公開邊界在運作。沒有公開前綴的值不會提供給 client,應改由 server 或 edge function 讀取;不要為了讓瀏覽器取得它,又把 secret 改回 VITE_。
Q: 自訂 envPrefix 能把 secret 藏起來嗎?
A: 不能。自訂前綴只會改變哪些變數被公開,凡是命中該前綴的值仍會進入 client source。把它限制成明確的 public prefix,並將私密值留在 server runtime。
參考資料:
回報錯字、失效連結,或告訴我你想看的延伸主題。