CodeGym /課程 /ChatGPT Apps /常見部署錯誤與除錯策略

常見部署錯誤與除錯策略

ChatGPT Apps
等級 7 , 課堂 4
開放

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.tsxwidget.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_KEYMCP_BASE_URL 等, 但在 Vercel 上不是忘了建,就是名字打錯。

例如,你這樣讀:

const apiKey = process.env.OPENAI_API_KEY;

但在 Vercel 設的是 OPENAI_APIKEYOPENAI_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 已經提供很順手的介面了,善用它吧。

1
問卷/小測驗
Debug & Deploy,等級 7,課堂 4
未開放
Debug & Deploy
Environments, Debug & Deploy (Vercel + 隧道)
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION