~/VibeHandbook
免費 PDF

17 · 01

案例研究 1:一個多語言落地頁 + 支付頁

想法

一位朋友在賣一份售價 29 美元、面向移居韓國的外籍人士的 PDF 指南。她想要一個單頁,全球載入都快,會說英語、韓語和中文,並且能在她完全不碰伺服器的情況下收取銀行卡支付。

Spec

在寫任何程式碼之前,我們把 spec 控制在了一段話:

Build a single-page marketing site for a $29 digital product.
Three languages (en/ko/zh), auto-detected from the browser but
switchable with a toggle. One "Buy" button that opens Stripe
Checkout and redirects to a thank-you page on success. No backend
server I have to maintain. Must load fast from anywhere.

注意這段 spec 沒有說什麼:沒有框架,沒有設計系統,沒有分析統計,沒有郵件收集。這其中每一項都本可以是一個合理的功能,而每一項也都會拖慢首次上線。一段話 spec 的紀律,大半是把東西留在外面的紀律。

技術棧

"沒有我要維護的後端"加上"全球都快",直接指向了在邊緣 CDN(Content Delivery Network,內容分發網路——把你的檔案複製成多份存放在世界各地的資料中心裡,於是每位訪客都從就近的一份載入)上的靜態託管,外加一個用於唯一需要金鑰的事情——建立 Stripe ——的微型 serverless 函式。我們選擇了在 Cloudflare Pages 上的一個靜態站點,配上一個單獨的 Pages Function。整個架構一句話就能裝下——一個靜態頁呼叫一個函式、那個函式再跟 Stripe 對話——而正是這份簡單,讓它後來變得可除錯。

整個系統就是三個盒子,而其中只有中間那個會接觸到金鑰:

 ┌─────────────┐  POST /api/checkout  ┌──────────────┐  金鑰        ┌────────┐
 │ 靜態頁面    │─────────────────────▶│ PAGES        │─────────────▶│ STRIPE │
 │ (HTML/JS)   │◀─────────────────────┤ FUNCTION     │◀─────────────┤        │
 │ en/ko/zh    │   結賬 URL            │ (唯一處理    │  會話 URL    └────────┘
 └─────────────┘                       │  金鑰的一側) │
       從 CDN 邊緣提供                 └──────────────┘

關鍵的 prompt

我們從寬泛處起步,讓 AI 提議結構:

Scaffold a static landing page (plain HTML/CSS/JS, no framework)
with a language toggle for en/ko/zh. Store the copy in a single
JS object keyed by language. Detect the default from
navigator.language. Keep it to one index.html plus one main.js.

然後是支付路徑,金鑰就住在這裡:

Add a Cloudflare Pages Function at /api/checkout that creates a
Stripe Checkout session for a single $29 product and returns the
redirect URL. Read STRIPE_SECRET_KEY from the environment, never
hardcode it. The Buy button should POST to this function and then
window.location to the returned URL.

"never hardcode it"這一句配得上它的位置。任由它按預設行事,AI 會很樂意把一個佔位金鑰內聯進去,好讓示例"跑起來",而佔位金鑰往往會變成一個真上線的真金鑰。在 prompt 裡大聲說一次,比在評審裡抓出來要便宜。

障礙

第一次線上測試失敗了:Buy 按鈕毫無反應,瀏覽器控制台顯示一個 CORS(Cross-Origin Resource Sharing,跨源資源共享——瀏覽器關於哪些站點被允許呼叫哪些站點的規則)報錯。我們沒有去猜。我們把確切的報錯粘了回去:

Clicking Buy logs: "Access to fetch at '/api/checkout' blocked
by CORS policy." The function and page are on the same domain.
Here's the function code: [pasted]. What's actually wrong?

AI 立刻發現了它:這個函式確實在以 (JavaScript Object Notation,JavaScript 物件表示法——程式之間用來交換結構化資料的一種簡單、易讀的文本格式)形式返回 Stripe ,但 fetch 在函式的 OPTIONS 預檢(preflight)被處理之前就發起了,因為我們誤把靜態頁和這個函式部署到了兩個不同的 Pages 專案裡。真正的修復是部署拓撲,而不是程式碼。我們把函式移進了同一個專案的 /functions 目錄,報錯就消失了。

這個教訓值得多停留一下:症狀在程式碼裡(一個來自 fetch 的 CORS 報錯),但病因在基礎設施裡(兩個專案而不是一個)。如果我們沒有把配置粘上去,只讓 AI"修這個 CORS 報錯",它就會硬塞上 Access-Control-Allow-Origin 頭——一個能編譯通過、把症狀糊住、卻讓真 bug 繼續活著的修復。把字面的報錯連同它周圍的上下文一起粘上去,並且如果第一個貌似合理的解釋跟你的實際配置對不上,就別接受它。

上線

我們把正式的 Stripe key 作為加密的環境變數加進了 Pages 控制面板(絕不進倉庫),把她域名的 (Domain Name System,域名系統,網際網路的地址簿,把一個名字對映到一臺伺服器)指向了 Pages,並用一張銀行卡跑了一次真實的 29 美元測試購買,然後退了款。我們還特意走了一遍失敗路徑——一張會被拒的測試卡——以確認她不會被扣費,並且看到的是一條像樣的訊息而不是一片空白。一個下午就上線了。她當晚就做成了第一筆銷售。

想離線閱讀?

免費下載整本書的 PDF 或 EPUB。