~/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。