1. 「隨機」通道的問題
當你第一次啟動 ngrok http 3000 或快速的 Cloudflare Quick Tunnel,感覺就像魔法一樣:唰——你的 http://localhost:3000 變成了 https://random-1234.tunnelprovider.com。把這個 URL 複製到 ChatGPT 的 Dev Mode,GPT 就會開心地載入你的 App。
然後你重啟通道……又得到一個新網域。ChatGPT 裡 Dev‑應用的舊 URL 瞬間變成「壞掉的連結」,GPT 也老實地顯示「App unavailable」,於是你又回到設定頁,改 URL、按下 Save、等它更新,然後默默地厭世整個技術棧。
偶爾「晚上玩一下」還能忍。但當你:
- 每天都在持續開發 App;
- 想給同事/主管看看中間版本;
- 同時還要設 staging 和 production,
每次遇到新的隨機 URL 都要重連 Dev Mode,這就變成純粹的折磨。
此外,如果你的應用裡已經加了與網域有關的東西(例如 OAuth redirect URI 或 webhook),每個新的 URL 也會把它們一起搞壞。於是出現連鎖反應:換了通道——就得去改 App 的設定、OAuth 供應商上的 redirect‑URL,還有 webhook 接收端的設定。
這就引出本講的核心觀念:穩定的 dev‑URL 不是奢侈,而是維持開發者心理健康的必要工具。
洞見
ChatGPT 對你的應用有非常嚴格的逾時限制,這點很容易被低估。MCP tool call 有時間上限:最多 2 分鐘——超過之後平台就會直接判定呼叫失敗,即使你的伺服器還在做事。
對於應用的註冊(Store 或 Dev Mode),限制更嚴:ChatGPT 讀取你的 manifest、resources 和工具描述大概只有 20 秒。如果在這段時間內你的 MCP 伺服器還沒初始化好、沒把 tools/resources 等回傳,App 註冊就會因逾時而失敗。
建議:所有繁重的初始化都應該發生在你去 Dev Mode 或 Store 之前。預熱資料庫連線、載入大型設定、延遲快取——這些最好事先完成,例如先透過 MCP Jam 或一個內部腳本打一次伺服器。對平台而言,MCP 伺服器必須是「溫的」,回應要在幾秒之內,而不是在註冊期間才「醒來」。
2. 什麼是「進階版」通道
我們來釐清一下,「進階版」通道跟你在課程一開始跑的有什麼不同。
早期模式(模組 2)大概長這樣:
# ngrok 範例
ngrok http 3000
# 會得到: https://random-abc123.ngrok-free.app
你把這個一次性的 URL 填到 Dev Mode。下一次啟動 ngrok 時,URL 又變了,而 ChatGPT 的設定也就過時了。
在「進階」做法中,你會有:
- 靜態子網域(來自通道供應商或你自己的網域);
- 同一個網域永遠轉發到你的 localhost:3000;
- 你可以重啟通道、電腦、路由器,但 URL 都保持不變。
這類靜態子網域可用於,例如:
- ngrok——每個帳號提供免費的 static domain;
- Cloudflare Tunnel——透過命名通道並綁定自己的網域。
而 ChatGPT 的 Dev Mode 應用就設定成這一個 URL,之後就不再吵你了。
形式上,我們的「進階版」通道需要滿足這些條件:
- 穩定、固定的公開 HTTPS 網域;
- 有效的 TLS 憑證(供應商會幫我們處理);
- 一個設定,描述「把 https://dev.yourdomain.com 的所有流量,轉到 http://localhost:3000」;
- 可選——基本的安全措施(至少不要把 URL 貼到 Stack Overflow 上曝光)。
3. 設定穩定的 dev‑URL:Cloudflare Tunnel 範例
在課程中我們推薦 Cloudflare Tunnel 作為主要工具,因為它同時適用於開發與更嚴肅的場景。你在模組 2 已經看過基本設定,現在把它「擰緊」到永久的 dev‑URL。
假設我們有一個教學用應用 GiftGenius,希望有穩定的 URL:giftgenius-dev.yourdomain.com。
最小步驟(簡化,與 Cloudflare UI 解耦):
- 把網域綁定到你的 Cloudflare 帳號(經由他們的後台,一次性)。
- 在本機安裝 cloudflared 並登入。
brew install cloudflare/cloudflare/cloudflared # macOS
cloudflared login # 將開啟瀏覽器以完成授權
3. 建立命名通道:
cloudflared tunnel create giftgenius-dev
4. 在 ~/.cloudflared/config.yml 中設定路由:
tunnel: giftgenius-dev
credentials-file: /Users/you/.cloudflared/giftgenius-dev.json
ingress:
- hostname: giftgenius-dev.yourdomain.com
service: http://localhost:3000 # 我們的 Next.js 開發伺服器
- service: http_status:404
5. 啟動通道:
cloudflared tunnel run giftgenius-dev
現在,只要 npm run dev 和 cloudflared tunnel run 在執行,你的本機 Next.js 就能透過穩定的 URL https://giftgenius-dev.yourdomain.com 對外提供。而在 ChatGPT 的 Dev Mode 設定中,你就填這個位址。
這如何與我們的應用程式銜接
如果你在瀏覽器中打開你在 ChatGPT 連接 Dev‑應用時輸入的應用 URL:
https://giftgenius-dev.yourdomain.com/mcp
你會看到一個回應(錯誤)——類似這樣:
{"jsonrpc":"2.0","error":{"code":-32000,"message":"Method not allowed."},"id":null}
這完全正常,因為 /mcp 端點不預期 GET 請求。應用的其他部分——小工具、MCP 端點 /mcp、API 路由——也都走同一條通道,你不需要每次記住新網域。
4. 替代方案:ngrok 的穩定子網域
如果你已經習慣 ngrok,也可以用類似的方式「進階化」,改用 static domain。自 2023 年起,ngrok 即使在免費方案也提供一個靜態子網域,例如 myapp-dev.ngrok-free.app。
最小設定:
# ~/.config/ngrok/ngrok.yml
authtoken: <你的 token>
tunnels:
giftgenius-dev:
addr: 3000
proto: http
domain: giftgenius-dev.ngrok-free.app
啟動:
ngrok start giftgenius-dev
結果是 https://giftgenius-dev.ngrok-free.app 會是固定的,你就把它提供給 ChatGPT 的 Dev Mode 作為應用的基底 URL。
理念相同:
- 不再有任何「隨機」位址;
- 只有通道的內部狀態(啟動/停止)在變,網域不變;
- 不需要重綁 Dev Mode。
Cloudflare 與 ngrok 在這個意義上就像不同口味的冰淇淋。有的人喜歡自有網域與較細的 DNS 控制(Cloudflare),也有人偏好「寫好 YAML 就搞定」(ngrok)。在本課兩種做法都可行,關鍵只有一個——穩定的 URL。
5. 示意圖:ChatGPT Dev Mode ↔ 通道 ↔ 本機技術棧
為了稍微形式化一點,我們畫張圖。
flowchart TD
ChatGPT["ChatGPT (Dev Mode)"]
AppCfg["Dev App(設定:https://giftgenius-dev...)"]
Tunnel["Cloudflare/ngrok 通道(giftgenius-dev...)"]
Next["Next.js 開發伺服器 localhost:3000 + MCP handler"]
ChatGPT --> AppCfg
AppCfg -->|"設定中填入 https://giftgenius-dev.../.well-known/openai-app"| Tunnel
Tunnel -->|"HTTPS → HTTP 代理"| Next
ChatGPT 永遠不知道你筆電上在跑什麼。對它而言只有一個 HTTPS 端點。這個端點背後到底是 Vercel、本機通道、或 Kubernetes——由你決定。而在這一講,我們關注的是本機開發時,這個 HTTPS 端點的穩定性。
接下來要做到的是,讓我們的應用內部也把這個位址視為唯一的「真相來源」,而不是散落在硬編碼字串裡——下一節就來處理這件事。
6. 程式中的環境變數與 baseURL
為了讓一切可預期,建議在 Next.js 程式裡定義一次「應用對外的基底 URL」,之後一律依賴它。
例如,在我們的 GiftGenius 應用中,在 app/lib/config.ts 建立:
// app/lib/config.ts
export const baseUrl =
process.env.NEXT_PUBLIC_APP_URL ?? "http://localhost:3000"; // fallback
export const mcpEndpoint = `${baseUrl}/mcp`; // MCP 伺服器的 URL
然後在開發用的 .env.local 指定:
NEXT_PUBLIC_APP_URL=https://giftgenius-dev.yourdomain.com
這樣一來:
- 在小工具與任何連結中,你都只使用 baseUrl;
- 對 ChatGPT Dev Mode 與瀏覽器而言,一切保持一致;
- 如果你改天遷到 Vercel 的 staging,網域換成 https://giftgenius-staging.vercel.app, 只要改環境變數即可。
這對以下情境尤其重要:
- callback‑URL(例如 OAuth、webhook 處理器);
- 你在小工具中顯示給使用者的連結(透過 openExternal 的「在瀏覽器開啟」按鈕);
- 應用程式邏輯中任何需要絕對 URL 的地方。
雖然我們目前只談 dev‑URL,但「baseUrl 單一真相來源」這個架構觀念同樣適用,之後也能平順搬到 staging/production。
7. 在 ChatGPT Dev Mode 中更新 URL
好,我們已經有漂亮的穩定網域了。那在 Dev Mode 中要怎麼用?
邏輯如下:
- 在 Dev‑應用的設定中,先填一次根 URL:https://giftgenius-dev.yourdomain.com/
- ChatGPT 會透過它去抓 manifest(.well-known/openai-app), 接著也用同一個根去連 MCP(/mcp)、靜態資源等。
- 若你只改程式碼(React 小工具、MCP handlers、樣式),完全不需要改 URL。 只要通道有啟動、Next.js 伺服器有回應即可。
- 如果你改了網域本身(比較少見,例如從 ngrok 換到 Cloudflare),那就進 Dev Mode 把 endpoint 改一次。
某些情況下 ChatGPT 會快取 manifest,內容不一定立即更新。Dev Mode 介面通常有「Reload configuration / Refresh App」之類的按鈕;最糟的情況下,關閉再用相同 URL 重新連一次 App 也能解決。
重點:只要你不改 URL,Dev Mode 就會自動「抓到」新版程式。對 App 而言,網域才是主要的觸發點,而不是 commit‑hash。
8. 在 Dev Mode 中切換 dev/staging/prod
穩定的 dev 網域只是第一步。為了避免專案擴張後被一堆 URL 淹沒,最好一開始就理解 dev 通道如何融入整體環境(dev/staging/prod)與 Dev Mode。雖然 staging 與 prod 比較屬於下一堂 Vercel 的主題,但 Dev Mode 現在就能搭配多個環境使用。
先看一張有助理解的表格:
| 環境 | 基底 URL | 程式在哪裡跑 |
|---|---|---|
| Local | |
本機 Next.js + 透過通道的 MCP |
| Staging | |
Vercel Preview/staging 部署 |
| Prod | |
Vercel Production |
與 Dev Mode 的搭配有兩種做法。
第一種——一個 Dev‑App,但你偶爾去改它的 URL 來測 staging 或 prod(要小心)。這在早期還可以,但很容易搞混:今天測本機、明天測 staging、後天忘了切回來,結果用 Dev‑App 把請求打到 prod。
第二種——更健康:多個 Dev‑應用,各自綁定明確的環境:
- GiftGenius Dev → giftgenius-dev.yourdomain.com;
- GiftGenius Staging → giftgenius-staging.vercel.app;
- GiftGenius(正式版,透過 Store)→ giftgenius.vercel.app。
在本講中我們先一步步把 dev‑URL 整頓好。下一講你會看到如何把 Vercel 與 preview 部署合理地對應到 staging/production。
9. 團隊合作:多位開發者與一個通道
當只有一個 dev 網域、也只有一位內向開發者時,通道就是你個人的好朋友。但一旦進到團隊專案,通道與環境就會互相交錯,此時要避免上演「搶一個子網域之戰」。
想像兩位開發者決定共用同一個靜態子網域,例如 giftgenius-dev.ngrok-free.app。兩人都在自己電腦上啟動 ngrok start giftgenius-dev。最好的情況是一個通道起不來(網域衝突);最糟的是你們會輪流「蓋過」彼此的連線,ChatGPT 時而連到甲、時而連到乙。
這裡有幾種策略。
最簡單——個人化的 dev 網域:
- alex.dev.giftgenius.app;
- maria.dev.giftgenius.app。
並且每個人在 ChatGPT 中都有自己的 Dev‑App,例如 GiftGenius Dev (Alex) 與 GiftGenius Dev (Maria)。如此一來各自安穩地跑本機,不互相干擾。
更「團隊化」的做法——共用 staging 端點:
- 每位開發者都有個人 dev 通道(用於個人除錯)。
- 另外有一個 Vercel 上的 staging,feature 分支合併後部署;共用的 Dev‑App GiftGenius Staging 指向它。
這在真實團隊裡很常見:
- 功能先在本機誕生,透過個人通道除錯;
- 送出 pull request 並合併後,大家在 staging 一起測試(不需要通道,直接用 Vercel URL)。
10. dev 通道的安全性(簡短不恐慌)
通道是把你的本機伺服器拉到網際網路上的方便方式。但網際網路多由機器人、掃描器,以及喜歡檢查你是否忘了密碼 admin/admin 的人組成。
在 dev 階段就值得注意的基本事項:
- 通道會讓外界能存取該埠上的所有服務;不要連資料庫後台、phpMyAdmin、或「我的無密碼測試 CRM」也一起曝露;
- 不要把通道的 URL 公開貼在開放的倉庫或聊天群;
- 不工作時請關閉通道(筆電也偶爾關機——它也需要休息)。
更嚴格的作法,例如 Basic Auth、檢查特殊標頭、或在 URL 中帶 token,會在安全性模組中再談。現在只要記住一件事:通道是開發工具,不是受保護的伺服器。正式環境會在像 Vercel 這樣的正規主機上運行,並用不同的保護機制——我們會在下一講抵達那裡。
11. 實作:為我們的應用設定穩定的 dev‑URL
現在從理論與提醒進入實作:把這些綁到我們的 Next.js 教學應用(Apps SDK 範本)。
假設專案結構如下:
apps/
web/ # Next.js App + 小工具
mcp-server/ # (可選)獨立 MCP,或 web 裡的 /mcp handler
實務上你可以直接把 MCP 放在 Next.js,例如 app/api/mcp/route.ts,原則相同。
步驟 1. 修改 .env.local
加入穩定的 dev 通道路徑:
NEXT_PUBLIC_APP_URL=https://giftgenius-dev.yourdomain.com
在開發程式碼中,我們已經從這個環境變數使用了 baseUrl(見上)。如果還沒有,現在就是抽出來的時候。
步驟 2. 啟動 dev 伺服器與通道
cd apps/web
npm run dev # Next.js 在 localhost:3000
# 另外一個終端機
cloudflared tunnel run giftgenius-dev
在瀏覽器中確認 https://giftgenius-dev.yourdomain.com 能打開並顯示你的 App。
步驟 3. 在 ChatGPT Dev Mode 中連接
在 ChatGPT 介面(開發者區):
- 建立或編輯 GiftGenius Dev;
- 在 URL/Endpoint 欄位填入 https://giftgenius-dev.yourdomain.com/;
- 儲存。
之後 ChatGPT 會透過 /.well-known/openai-app 讀取 manifest,接著就會以這個網域來啟動你的 App。
此時你可以:
- 更改小工具、MCP handlers、樣式;
- 重啟 npm run dev;
- 重啟 cloudflared tunnel run giftgenius-dev;
但只要網域不變,就再也不用去改 Dev‑App 設定。
12. 程式邏輯中的樣子:openExternal 範例
把例子和我們先前的講解串起來:在小工具加一個「在瀏覽器開啟完整介面」的按鈕,也使用那個穩定的 dev‑URL。
假設我們有一個小工具的 React 元件 GiftWidget:
// app/components/GiftWidget.tsx
"use client";
import { baseUrl } from "../lib/config"; // 從 env 取得 baseUrl
export function GiftWidget() {
const handleOpenFull = () => {
window.openai.openExternal({
// 在新分頁開啟應用頁面
url: `${baseUrl}/full`,
label: "開啟完整介面",
});
};
return (
<div>
<button onClick={handleOpenFull}>
完整模式
</button>
</div>
);
}
如果 NEXT_PUBLIC_APP_URL 指向通道,那麼:
- 在本機開發時會開啟 https://giftgenius-dev.yourdomain.com/full;
- 部署到 staging 後會開啟 https://giftgenius-staging.vercel.app/full;
- 在 prod 時就是正式網域。
再次強調——網域只有一個真相來源:換環境,不改程式碼。
13. 迷你策略:用「進階」心態看待通道
把一切收斂成簡單的心智模型:
- 通道只是一條臨時的導線,把你的筆電接到穩定的公開網域;
- ChatGPT Dev Mode 只認網域,不在乎你的程式實際跑在哪;
- 你越少改網域,就越少在 ChatGPT 與 OAuth 供應商的設定中浪費時間;
- dev 通道只是你整體環境地圖中的一行,旁邊還有 staging(Vercel preview)與 prod(Vercel production)。
下一講會示範如何把這條導線換成 Vercel 上的正式託管,並把它和 Git 分支、preview 部署、以及正式環境串起來。
14. 使用「進階版」通道時的常見錯誤
錯誤 №1:「我已經設定了靜態網域,卻還在用隨機 URL」。
有時開發者會先做出漂亮的 giftgenius-dev.yourdomain.com,但出於習慣,仍然直接跑 ngrok http 3000 而不使用設定檔。結果是 ChatGPT 指向一個網域,而程式在另一個網域後面跑。如果你已經有穩定的 dev‑URL——就只用它,並用設定檔(命名通道/設定檔案)來啟動通道。
錯誤 №2:把 localhost:3000 硬寫死在程式碼裡。
常見於在 React 元件或 MCP handler 裡寫 fetch("http://localhost:3000/api/...")。在本機可能還能用,但在 Dev Mode、尤其 staging/prod 會立刻壞掉。永遠把基底 URL 抽到設定裡(baseUrl、NEXT_PUBLIC_APP_URL),並在所有需要絕對連結的地方使用它。
錯誤 №3:老是去改 Dev Mode 的 URL,而不是用穩定通道。
如果你常想「算了我再改一次設定裡的 URL 就好」——這就是警訊。設定好 ngrok/Cloudflare 的靜態子網域只需 10–15 分鐘,卻能在開發過程中省下好幾小時。
錯誤 №4:整個團隊共用一個靜態網域,卻沒有規則。
兩位開發者、同一個網域 giftgenius-dev.ngrok-free.app,兩個人想啟動通道就啟動。結果就是通道衝突、Dev Mode 回應「神祕消失」、以及「在我機器上是好的」式的除錯。團隊要嘛每人一個 dev 網域,要嘛用真實主機架一個 staging 網域。
錯誤 №5:把通道當作「幾乎是正式環境」。
有時有人會想:「既然我有透過通道的穩定 HTTPS 網域,不如讓真正的使用者/金流都走它」。這是在找苦吃:筆電關機——應用就掛;網路斷線——一樣;而安全性頂多是象徵性的。通道是 dev 工具。正式流量應該走 Vercel 等進階基礎設施——我們會在下一講抵達那裡。
錯誤 №6:忘了同步環境變數與 Dev Mode。
常見情況是改了 NEXT_PUBLIC_APP_URL(在 .env.local),卻忘記同步改 Dev Mode 的 URL(或反過來)。結果小工具產生的連結指向一個網域,而 ChatGPT 連的是另一個。維護一張簡單的表——「環境 ↔ 網域 ↔ ChatGPT 中的 App」——並在變更時更新,遠比瞎猜「現在到底哪個 URL 才是真的」要省時省心。
GO TO FULL VERSION