用 Vercel Serverless Functions 代替前端呼叫 OpenRouter,讓學生的作品真的可以連上免費模型,同時學會最重要的秘密管理觀念。
學生的前端作品可以送出問題,經由自己的後端路由呼叫 OpenRouter,取得 AI 回覆。
知道 Vercel 上要在哪裡新增 `OPENROUTER_API_KEY`,以及修改環境變數後為什麼要重新部署。
理解 API key 不能出現在前端程式、GitHub repo、瀏覽器 DevTools、公開 log 與分享截圖中。
課堂核心句:前端可以被看見;後端才適合保管秘密。
API key 就像你的服務門禁卡。拿到它的人,可能可以用你的額度、打你的 API、產生成本,甚至存取不該看的資料。
const apiKey = "sk-..."
放在 `main.js`、`index.html`、前端 `.env`,最後都可能被使用者看到。
process.env.OPENROUTER_API_KEY
只在 serverless function 讀取,前端永遠只打自己的 `/api/chat`。
學生只要記住:前端送需求,後端加秘密。
示意圖:GPT Image 生成,依課程內容繪製。
打包後的 JS 仍然會下載到使用者電腦。只要在檔案裡,就可能被搜尋。
瀏覽器 Network、Sources、Console 都可能看到請求、header 或變數。
公開 repository、commit history、fork 都會讓秘密很難完全收回。
上課、發問、錄影時,畫面上的 token 可能被截走。
判斷規則:只要會被瀏覽器下載,就不是秘密。
| 做法 | 結果 |
|---|---|
| 前端 JS 直接放 key | 使用者可從打包檔或 Network 找到 key。 |
| 把 key commit 到 GitHub | 即使刪掉檔案,commit history 仍可能留下紀錄。 |
| Vercel env + function 代呼叫 | 前端只知道你的 API 路由,不知道 OpenRouter key。 |
示意圖:GPT Image 生成,依課程內容繪製。
對初學者來說,可以先把 serverless function 想成:不需要自己架伺服器,但可以寫一個後端 API。
my-ai-demo/
├─ index.html
├─ style.css
├─ main.js
└─ api/
└─ chat.js ← 後端路由
依 Vercel Node.js Runtime 文件:`/api` 內的 function 會由 Vercel build and serve。
OPENROUTER_API_KEY
操作畫面:Vercel Environment Variables 文件截圖。
OPENROUTER_API_KEY=sk-or-v1-xxxxxxxx
OPENROUTER_SITE_URL=http://localhost:3000
OPENROUTER_SITE_NAME=Student AI Demo
.env
.env.local
.env.*.local
node_modules/
.vercel/
OpenRouter 的 Free Models Router 會從可用的免費模型中挑選合適模型。對初學者教學來說,它能降低「先選哪個模型」的負擔。
免費模型適合學習、展示、低流量實驗;正式產品需要看 rate limit、可用性與成本。
操作畫面:OpenRouter Models 頁面截圖,顯示免費模型項目。
送出使用者輸入。這裡只有 prompt、表單資料、按鈕狀態,不放 secret。
讀 `process.env.OPENROUTER_API_KEY`,加上 Authorization header,呼叫模型供應商。
回傳 AI 結果。你的 function 再把必要資訊傳回前端。
前端永遠不需要知道 OpenRouter API key。
這支檔案是「安全代理人」:接收前端訊息,讀 env,代替前端呼叫 OpenRouter。
module.exports = async (request, response) => {
if (request.method !== "POST")
return response.status(405).json({ error: "Use POST" });
const message = request.body?.message || "";
const ai = await fetch("https://openrouter.ai/api/v1/chat/completions", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.OPENROUTER_API_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
model: "openrouter/free",
messages: [{ role: "user", content: message }]
})
});
const data = await ai.json();
const answer = data.choices?.[0]?.message?.content || "";
return response.status(ai.status).json({ answer, raw: data });
};
async function askAI(message) {
const res = await fetch("/api/chat", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ message })
});
const data = await res.json();
if (!res.ok) throw new Error(data.error || "API error");
return data.answer || "目前沒有回覆";
}
前端只知道你的網站有一個 `/api/chat`。它不需要知道 OpenRouter 的 endpoint,也不應該知道 API key。
網址列試 `/api/chat`,至少應該看到 405 或 JSON 錯誤,而不是 404。
Vercel key 名稱必須和程式裡的 `process.env.OPENROUTER_API_KEY` 完全一致。
Vercel 文件指出 env 變更只會套用到新的 deployment。
如果前端直接打 OpenRouter,通常會遇到 key 外洩或 CORS 類問題。
401 多半是 key 錯、沒貼完整、被撤銷,或沒有帶 Authorization header。
免費模型可能有 rate limit 或暫時不可用;教學時要準備備用模型。
| 狀況 | 可能原因 | 先檢查 |
|---|---|---|
| 401 | key 無效或沒有帶 Authorization | Vercel env 名稱與值 |
| 404 | function 沒部署或路徑錯 | `api/chat.js` 位置 |
| 405 | 用 GET 打到只收 POST 的 route | 前端 `method` |
| 429 | rate limit 或免費額度限制 | 等候、換免費模型或降低頻率 |
| 500 | 後端程式錯、env 缺失、JSON 解析失敗 | Vercel Function Logs |
許多前端框架會把特定前綴的環境變數打包進瀏覽器端程式。這類變數適合放公開設定,不適合放 API key。
VITE_OPENROUTER_API_KEY
NEXT_PUBLIC_API_KEY
這些通常會變成前端可讀。
OPENROUTER_API_KEY
DATABASE_URL
只在 server-side function 讀取。
能只給讀取權就不要給管理權;能限定服務範圍就不要全開。
Development、Preview、Production 使用不同 key,降低單點外洩影響。
知道去哪裡 revoke / rotate key,外洩時能快速換掉。
可加每日額度、rate limit、使用者登入、伺服器端驗證。
教學作品也要讓學生養成產品習慣:秘密管理是工程基本功,不是上線後才補。
| 平台 | 通常放哪裡 | 程式怎麼讀 |
|---|---|---|
| Vercel | Project Environment Variables | `process.env.MY_KEY` |
| Netlify | Environment variables | `process.env.MY_KEY` |
| Cloudflare Workers | Secrets / Bindings | `env.MY_KEY` |
| GitHub Actions | Repository Secrets | `${{ secrets.MY_KEY }}` |
| Docker / Server | Runtime env 或 Secret Manager | 環境變數或 secret mount |
語法會變,原則不變。
實作題最好把「安全驗收」列入分數,學生才會把 API key 當成正式工程問題。
如果學生使用 AI coding 工具,prompt 要明確要求「前端不能保存 API key」。
請幫我做一個前端網頁,使用者輸入問題後取得 AI 回覆。
請使用 Vercel Serverless Function:
1. 前端只能 fetch("/api/chat")
2. OpenRouter API key 只能在 api/chat.js 用
process.env.OPENROUTER_API_KEY 讀取
3. 不要把 API key 寫在任何前端檔案
4. OpenRouter model 使用 "openrouter/free"
5. 請加入錯誤處理與簡單 loading 狀態
判斷不確定時,先當作秘密處理,再請老師或工程同伴確認。
console.log(process.env.OPENROUTER_API_KEY);
console.log(request.headers);
console.log(fullOpenRouterResponse);
console.log("OpenRouter status:", ai.status);
console.log("Has API key:", Boolean(process.env.OPENROUTER_API_KEY));
console.log("Message length:", message.length);
如果要請同學或老師幫忙 debug,先遮蔽 token。不要把完整 header、完整 env、完整 `.env.local` 貼出來。
即使 key 沒外洩,如果任何人都能無限制打你的 route,仍然可能消耗你的額度。
| 順序 | 動作 | 原因 |
|---|---|---|
| 1 | 停用舊 key | 先切斷濫用可能。 |
| 2 | 建立新 key | 舊 key 不能再信任。 |
| 3 | 更新 Vercel env 並 redeploy | 新 deployment 才會讀到新值。 |
| 4 | 清理公開內容與 Git history | 降低再次被掃到的機會。 |
| 5 | 檢查用量與帳單 | 確認外洩期間是否被濫用。 |
標準答案:外洩後不要相信舊 key 還安全。
| 時間 | 活動 | 重點 |
|---|---|---|
| 0-8 分 | 講 API key 是什麼 | 秘密不是普通設定 |
| 8-18 分 | 畫資料流 | 前端公開,後端保密 |
| 18-30 分 | 建立 `api/chat.js` | function 代呼叫 OpenRouter |
| 30-40 分 | Vercel 設定 env | Key、Value、環境、redeploy |
| 40-50 分 | 測試與 debug | 錯誤碼、log、不要貼 key |
Node.js runtime、`/api` directory、Request / Response、function helper。
https://vercel.com/docs/functions/runtimes/node-js
環境變數是 source code 外的 key-value pairs、會加密儲存、變更只套用到新 deployment、本機可用 `.env.local`。
https://vercel.com/docs/environment-variables
`/api/v1/chat/completions`、`Authorization: Bearer`、`model: "openrouter/free"`、免費模型限制。
https://openrouter.ai/docs/quickstart
https://openrouter.ai/docs/guides/get-started/free-models-router-playground