1. 部署剖析:到底會在哪裡壞掉
先把整條鏈看清楚。你可以把 ChatGPT App 的部署在你的架構中,腦中展開為這樣一條線:
flowchart TD A[你的筆電
git commit] --> B[Git 儲存庫
GitHub/GitLab] B --> C[Vercel Build
npm run build] C --> D[Vercel Deploy
Preview/Prod] D --> E[HTTP endpoint
/mcp, /api/...] E --> F[ChatGPT / Dev Mode
tool calls, widgets]
任何一步都可能出錯,但在 ChatGPT 端看到的症狀大多長得差不多: "Error talking to app"、"Network error",或只是沉默不回。 你的任務不是盲目亂修,而是先釐清:問題發生在建置、執行期,還是 ChatGPT 指向了錯的地方。
方便起見,把問題分成三大類:
- Build 錯誤:Vercel 根本無法建置專案。Production 沒更新——這還算「好」,但你會看到紅色的 build。
- Runtime 錯誤:建置通過了,但請求時回來 500/502、逾時或怪異行為。
- Config drift(設定漂移):本機一切正常、Vercel 日誌也正常,但 ChatGPT 連到舊的 URL、使用舊的 manifest,或 env 變數是空的。
我們會沿著這三層往下走,並同步建立一套通用的除錯策略。
2. Build 錯誤:專案根本建不起來時
這是前言提到的第一種問題——Build 錯誤:專案根本建不起來,因為 Vercel 無法成功建置你的 Next.js 專案。
Node 與 Next.js:不同環境、不同需求
在本機你可能(不幸地)還在用過時的 Node,而 Vercel 會用支援的 Node 版本來建置你的 Next.js 16 專案 (至少 18.18.0)。 若你在 package.json 明確指定了不相容的版本,建置可能在 production 失敗,儘管你的 dev server 先前能跑。
簡單的防護作法——在 package.json 明確宣告 "engines":
{
"engines": {
"node": ">=18.18.0"
}
}
這樣在本機與 CI/Vercel 都會及早發現 Node 太舊。
「在我這台可以!」與遺漏的相依
經典情境:你用 npm install some-lib 安裝了函式庫,但沒把更新的 package-lock.json 提交,或部分相依其實裝在你的全域環境。 在 Vercel 上應用是「從零」建置,它會按清單執行 npm install, 但你最愛的 some-lib 不在那裡——於是出現 build error。
這裡需要嚴謹的紀律:
- 任何新相依安裝後立刻提交;
- 在推到 main/production 前,先在本機跑一次 npm run build。如果本機 build 都失敗,上 Vercel 只會更慘。
大小寫敏感的檔案系統
本機很多人用的是 macOS 或 Windows,預設檔案系統不區分檔名大小寫。 但在 Vercel 的 Linux 環境中,Widget.tsx 與 widget.tsx 是不同檔案。
典型臭蟲:
// 程式碼中的匯入
import { AppWidget } from "@/components/Widget";
// 但版本庫裡的檔案其實是 components/widget.tsx
在你的電腦上一切正常,但在 Vercel 上就報「Cannot find module '@/components/Widget'」。 治療方法是統一命名並嚴格注意大小寫。
Build 階段的環境變數
另一個驚喜來源——在建置階段執行的程式碼中使用 process.env.* (例如在 next.config.mjs 或建置時就會被匯入的模組)。 若你在本機有載入 .env.local,但在 Vercel 的 build 環境忘了設定這些變數, 建置要嘛直接失敗,要嘛——更糟的是——以 undefined 通過,並把無效值「烘焙」進 bundle。
對 ChatGPT App 來說,若你在建置時就組出 MCP 端點的 baseURL 或外部 API 的 URL,這尤其關鍵。
好的做法——在應用啟動前就明確驗證關鍵的 env 變數(稍後會有專節), 讓 build 以大聲且可預期的方式失敗。
3. Runtime 錯誤:建好了,但跑不起來
現在進入前言的第二層——Runtime 錯誤:建置過了,但執行時壞掉。
建置完成、Vercel 顯示綠色部署,你把 ChatGPT App 切到正式 URL——結果在聊天室看到 "Error talking to app"。 表示問題已經到了執行期。
為空或缺失的環境變數
在 ChatGPT App 世界裡,production 事故最常從 undefined 開始。 你本機有漂亮的 .env.local,裏頭有 OPENAI_API_KEY、MCP_BASE_URL 等, 但在 Vercel 上不是忘了建,就是名字打錯。
例如,你這樣讀:
const apiKey = process.env.OPENAI_API_KEY;
但在 Vercel 設的是 OPENAI_APIKEY 或 OPENAI_API_KEY_PROD。 結果第一次呼叫 MCP 工具時,你的 route handler 就因為驗證失敗而崩潰。
更理想的是應用一開始就明確地失敗。好用的模式——在 Next.js 專案中做一個獨立模組, 在匯入時就驗證 env 變數:
// app/lib/env.ts
const required = ["OPENAI_API_KEY", "MCP_BASE_URL"] as const;
type RequiredKey = (typeof required)[number];
function getEnv(key: RequiredKey): string {
const value = process.env[key];
if (!value) {
throw new Error(`Missing required env var: ${key}`);
}
return value;
}
export const env = {
OPENAI_API_KEY: getEnv("OPENAI_API_KEY"),
MCP_BASE_URL: getEnv("MCP_BASE_URL"),
};
現在,如果你在 Vercel 忘了設定變數,Next.js 會在第一次匯入 env 時就失敗, 而日誌裡會有清楚的訊息 "Missing required env var: ..."。
別忘了,在 Vercel 上修改 env 變數並不會自動被執行環境採用。 改了數值之後必須做新的部署(redeploy),否則 runtime 仍會沿用舊值。
route handler 與 MCP 端點中的錯誤
在官方模板中,ChatGPT App 的 MCP 伺服器通常實作為 app/mcp/route.ts。 裡面有解析 JSON‑RPC 請求、路由到工具、回傳結果的程式碼。 如果鏈路中某處有未處理的 throw——使用者在 ChatGPT 端就會收到 500。
務必用 try/catch 包住 MCP handler 的最上層, 把錯誤記錄下來並回傳結構化的回應:
// app/mcp/route.ts
import { NextRequest, NextResponse } from "next/server";
export const dynamic = "force-dynamic";
export const maxDuration = 30; // 秒
export async function POST(req: NextRequest) {
try {
const body = await req.json();
// 在此處理 MCP 請求
const result = await handleMcpRequest(body);
return NextResponse.json(result);
} catch (error) {
console.error("MCP route error", error);
return NextResponse.json(
{ error: "Internal MCP error" },
{ status: 500 }
);
}
}
幾個重點:
- dynamic = "force-dynamic" 可避免 Next.js 16 對 MCP 路由做意外的靜態產生與快取。
- maxDuration = 30 明確告訴 Vercel,route handler 最長可執行 30 秒,對需要較久時間的 LLM 請求很重要。
ChatGPT 裡的逾時與「Network error」
Vercel 對 serverless 函式有執行時間限制:免費方案通常約 10 秒, 付費可更長(到數分鐘)。 若你的 MCP 工具會呼叫很慢的資料庫或外部 API,可能來不及回應, ChatGPT 端就會收到 "Network error" 或被截斷的串流。
若你用串流(SSE)回傳部分結果,特別要在逾時前送出第一個位元組。 這樣傳輸可以繼續更久,但平台不會把函式視為「卡住」。
小技巧:量測工具呼叫的耗時,連同工具名稱一起記錄。 這樣在日誌裡你會看到,例如 search_flights 穩定地花 12 秒, 剛好超出限制一點點。
export async function safeToolCall<TInput, TOutput>(
name: string,
handler: (input: TInput) => Promise<TOutput>,
input: TInput
): Promise<TOutput> {
const started = Date.now();
try {
const result = await handler(input);
console.log("[tool] ok", name, { ms: Date.now() - started });
return result;
} catch (error) {
console.error("[tool] fail", name, {
ms: Date.now() - started,
error,
});
throw error;
}
}
之後把 handler(args) 換成 safeToolCall("search_flights", handler, args) 即可。
網路與外部服務
有時只是 https:// 與 http:// 弄反,或 baseURL 過時。 尤其是你先在本機用某個 URL 測試,到了 production 又是另一個網域或連接埠。
把基礎 URL 提取到(依環境不同的)設定中,而不是寫死在工具程式碼裡,會更好。 這樣換環境時只要改一個 env,而不是去找程式碼裡五個 http://localhost:3001 的地方。
4. 設定與環境漂移
最後來看第三類——不同環境間的設定漂移。
即便 build 成功、runtime 日誌也都健康,ChatGPT 仍可能表現出「好像跑的不是該版本應用」。 這通常不是程式碼本身,而是設定與環境一致性問題。
Dev Mode 與 Production 的差異
在 Dev Mode 中,ChatGPT 指向你手動填的 Connector URL:通常是某個通道 URL (https://myapp-dev.ngrok-free.app/mcp 或類似) 或 Vercel 的 staging URL。 在 production(透過 Store 或組織設定)中,App 應該指向穩定的正式端點, 例如 https://myapp.vercel.app/mcp。
幾乎每個人都會犯的錯:你已經在 Vercel 部署了,但 ChatGPT App 設定還是舊的通道 URL。 本機伺服器關了、通道早就死了,ChatGPT 還是乖乖去敲它,得到 502。 介面上就出現 "Error talking to app",然後學生開始修根本沒被執行的 MCP 程式碼。
解法是紀律:只要換環境(通道 → staging、staging → prod),就檢查 Dev Mode 與 production 設定裡到底指向哪個 URL。
舊的 manifest 與 ChatGPT 快取
ChatGPT 會快取你的 App 資訊: 工具清單、描述、後設資料。 因此「我改了工具的 schema,但模型還以為參數叫舊名字」——確實會發生。
工具有重大變更時,建議:
- 確認你真的部署了新版本(看日誌裡的 commit hash,或在啟動日誌輸出它);
- 在 Dev Mode 重新建立或重新連線 App,逼平台重讀 manifest;
- 除錯時暫時用 MCP Inspector,你能確定看到的工具清單與 schema 是最新的。
環境設定:dev/staging/prod
我們已經談過 env 變數如何把 build 與 runtime 搞掛。 這裡從更高層看 dev/staging/prod 之間數值的一致性。
常見痛點:你的 .env.local 完美無缺,但 Vercel 的各個環境是一個動物園。結果:
- 本機用一組 API 金鑰與外部服務 URL;
- staging 又是完全不同的一組;
- prod 有一半變數根本沒設。
非常有幫助的是在版本庫做個簡單文字檔 docs/env.md,列出: 需要哪些變數、在哪些環境必填、範例值為何。 看似官僚,但在事故當下能省下好幾個小時。
5. 從 ChatGPT 的角度看錯誤長怎樣
現在換成 ChatGPT 使用者的視角。 他只看到介面,對 Vercel、Node、MCP 一無所知。 而你,不幸地,這時也還不知道哪裡壞了。
ChatGPT 裡的典型症狀:
- 在嘗試使用後立刻出現 "Error talking to [App Name]" 訊息;
- 無止盡的轉圈,卻看不到錯誤;
- 紅字顯示 "I encountered an error while running the tool";
- 小工具(widget)沒出現,或空白出現。
每種症狀通常對應到某個層級的故障:
- 若 App 根本不可達(URL 設錯、通道掛了、SSL 錯誤),ChatGPT 打不到你的 MCP 端點—— 先用瀏覽器檢查網域可用性,再看 Vercel 日誌上的 4xx/5xx。
- 若 MCP 回的是帶有 error 欄位的合法 JSON‑RPC, ChatGPT 會誠實地說工具回錯誤——那就是商業邏輯或參數驗證的問題。
- 若 MCP 成功回應,但回傳的 widget HTML 壞掉或出現 JS 錯誤,那在小工具的主控台 (DevTools → widget 的 iframe)會看到哪裡掛了。
因此養成好習慣:一看到聊天室裡有怪行為,立刻記下時間(精確到分鐘), 然後去 Vercel 日誌找那個時間的請求。
6. 除錯策略:不慌張,按步驟來
把上面內容整理成一個小型「playbook」——出事時的行動腳本。 目標是用冷靜的演算法取代繞圈子。
步驟 1:判斷問題類型
如果 Vercel 上的 build 是紅的——先高興一下:錯誤沒進 production。 打開 build 日誌,找第一個真正的錯誤(不是 200 行警告), 然後用 npm run build 在本機重現。
如果 build 是綠的,但 ChatGPT 抱怨——就是 runtime 或設定問題。檢查:
- 你的 App 正式 URL 是否能從瀏覽器存取(https://myapp.vercel.app/mcp 是否至少回點東西);
- MCP 端點回 200/500 還是根本不解析;
- App 設定裡的 URL 是否與你剛剛檢查的一致。
步驟 2:讀日誌,不要讀心術
下一站——Vercel 日誌:看對應部署與對應環境(Preview/Production)的伺服器日誌。
尋找:
- Error: Missing required env var ... 之類——多半是設定問題;
- MCP handler 的 stack trace——很可能是商業邏輯或輸入解析在掉;
- 逾時或函式執行時間超標的訊息。
同時別忘了 MCP Inspector。 若用 Inspector 連到同一個 MCP 端點並手動呼叫工具, 很快就能判斷問題在 MCP 本身,還是在 ChatGPT ↔ MCP 之間。
步驟 3:快速回退還是直接熱修?
如果你看到 production 部署明顯是壞的(例如 MCP 路由每個請求都丟同一個錯), 而前一版是好的,那就該回退。 Vercel 允許快速切回前一次成功的部署而不需重建——本質上是切換作用中的版本。
這比在 production「現場修」安全,尤其當你還不確定事故原因時。
等情況穩定後,再從容地找出原因、補測試、修程式, 最後推出下一個版本。
步驟 4:把經驗寫進文件
任何嚴重事故都值得更新內部 README:
- 把某個「少了就會炸」的 env 變數加入必填清單;
- 記錄觸發錯誤的具體情境(例如「匯入使用了錯誤大小寫的檔名」);
- 寫下這次快速修好的簡短步驟。
這看起來無聊,但幾個月後你會感謝過去的自己。
7. 程式碼中的一些實用做法
接著把 playbook 裡的幾個步驟落到我們的教學應用(ChatGPT App)的程式碼上。
統一的設定模組
前面寫了簡單的 env 驗證器。可以再補強,區分不同環境:
// app/lib/config.ts
type NodeEnv = "development" | "test" | "production";
const nodeEnv = (process.env.NODE_ENV || "development") as NodeEnv;
const requiredBase = ["OPENAI_API_KEY"] as const;
const requiredProd = ["MCP_BASE_URL"] as const;
function ensure(keys: readonly string[]) {
for (const key of keys) {
if (!process.env[key]) {
throw new Error(`Missing env var ${key} for NODE_ENV=${nodeEnv}`);
}
}
}
ensure(requiredBase);
if (nodeEnv === "production") {
ensure(requiredProd);
}
export const config = {
nodeEnv,
openaiApiKey: process.env.OPENAI_API_KEY!,
mcpBaseUrl: process.env.MCP_BASE_URL ?? "http://localhost:3000/mcp",
};
這種模組能在 production 少了必要變數時立刻亮紅燈。
記錄收到的 MCP 請求
對 MCP handler 很有用的一層簡單包裝:
// app/lib/mcp-logger.ts
export function logMcpRequest(body: unknown) {
console.log("[mcp] request", {
time: new Date().toISOString(),
// 不記錄敏感資料
keys: typeof body === "object" && body !== null
? Object.keys(body as Record<string, unknown>)
: typeof body,
});
}
並在 app/mcp/route.ts 使用:
import { logMcpRequest } from "@/app/lib/mcp-logger";
export async function POST(req: NextRequest) {
try {
const body = await req.json();
logMcpRequest(body);
const result = await handleMcpRequest(body);
return NextResponse.json(result);
} catch (error) {
console.error("MCP route error", error);
return NextResponse.json({ error: "Internal error" }, { status: 500 });
}
}
你會在日誌看到 ChatGPT 到底送了什麼:至少從鍵值 ("jsonrpc"、"method"、"params") 就比較容易判斷是哪一個呼叫在掉。
簡單的 MCP 端點健康檢查
有時候做一個類似「healthcheck」的 route handler 對 MCP 伺服器很有幫助, ChatGPT 不會直接叫它,但你可以快速在瀏覽器開啟,確認 伺服器活著、且看得到自己的 env 變數:
// app/api/health/route.ts
import { NextResponse } from "next/server";
import { config } from "@/app/lib/config";
export async function GET() {
return NextResponse.json({
status: "ok",
env: config.nodeEnv,
hasOpenAiKey: !!config.openaiApiKey,
});
}
如果 https://myapp.vercel.app/api/health 回傳 status: "ok", 代表至少到你的 Node 程式碼的基本管線是健康的。
8. 部署與除錯的常見錯誤
錯誤一:從不在本機跑 npm run build。
當開發者從不在本機跑 build,他會直到上了 Vercel 才知道 Node 版本不相容、路徑問題或 TS 錯誤。 這會拉長「壞了 → 修好」的迴圈,因為每次嘗試都是一次新部署。 養成在推到 main 前跑 npm run build 的習慣能省下大量時間(另見第 2 節與步驟 6.1 的本機 npm run build)。
錯誤二:祕密只放在 .env.local。
專案在作者機器上完美運作,但在 production 因為 process.env.OPENAI_API_KEY === undefined 而崩潰。 原因很單純:忘了在 Vercel 設定 env 變數(有時甚至名字也不同)。 尤其常忘了分別設定 Development/Preview/Production,然後驚訝為何 staging 與 prod 表現不同(詳見 3.1、4.3 與 7.1)。
錯誤三:把祕密放進 NEXT_PUBLIC_*。
在 Next.js 中,只要有 NEXT_PUBLIC_ 前綴,變數就會進到瀏覽器 bundle。 如果你粗心把 API 金鑰命名為 NEXT_PUBLIC_OPENAI_API_KEY, 它就會送到使用者瀏覽器,能被開發者工具取出。這是絕對不行的。 公開的應該只限於安全的值(像是功能旗標的 ID,而不是 token)。
錯誤四:忽略 Vercel 日誌,只想「用 ChatGPT 修好」。
有時開發者看到聊天室裡的 "Error talking to app",就花幾個小時改提示詞、改工具描述、 在 Dev Mode 東改西改,卻一次也不看 serverless 日誌。 而那裡往往有清楚的錯誤:"Missing env var"、"Cannot find module", 或某個工具的 stack trace。好的工程師會先看日誌,再來和模型爭論。
錯誤五:混淆 Dev Mode 與 production App。
第一次在 Vercel 成功部署後,很容易忘了 Dev Mode 可能仍指向舊通道或 preview URL。 結果你以為在測 production,實際上卻在和早該刪掉的本機分支對話。 反過來也可能:以為在測草稿修改,ChatGPT 卻在打正式端點。 要定期檢查 App 與 Dev Mode 設定中的 URL(另見 4.1 關於 Dev Mode 與 production)。
錯誤六:以為在 Vercel 改 env 變數會「即時生效」。
有些同學在 Vercel 面板改了變數值,就立刻去 ChatGPT 驗證結果。 但 runtime 仍在用舊值,因為沒有 redeploy。 任何 env 變數的變更都需要新的部署,否則函式不會看到更新(詳見 3.1)。
錯誤七:缺少簡單的回退策略。
事故當下很想直接把修正推到 main。 但這只會再增加一個可能壞掉的部署,使用者同時還在受苦。 更穩當的習慣是:遇到嚴重錯誤先回退到前一次成功部署, 在分支修好再發新版。 Vercel 已經提供很順手的介面了,善用它吧。
GO TO FULL VERSION