Nghiên cứu tình huống 1: Trang đích đa ngôn ngữ + trang thanh toán
Ý tưởng
Một người bạn bán một cuốn hướng dẫn PDF giá 29 đô la cho những người nước ngoài chuyển đến sống tại Hàn Quốc. Cô ấy muốn một trang duy nhất tải nhanh ở bất kỳ đâu trên thế giới, hiển thị được tiếng Anh, tiếng Hàn và tiếng Trung, và nhận thanh toán bằng thẻ mà không cần cô ấy phải đụng đến một máy chủ (server) nào.
Bản đặc tả
Chúng tôi giữ bản đặc tả gói gọn trong một đoạn văn trước khi viết bất kỳ dòng code nào:
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.
Hãy để ý những gì bản đặc tả không nói đến: không , không hệ thống thiết kế (design system), không phân tích số liệu (analytics), không thu thập email. Mỗi thứ trong số đó đều là một tính năng hợp lý, và mỗi thứ đều sẽ làm chậm lần ra mắt đầu tiên. Kỷ luật của một bản đặc tả một đoạn văn phần lớn chính là kỷ luật của việc biết bỏ bớt.
Stack công nghệ
"Không có nào tôi phải bảo trì" cộng với "nhanh trên toàn thế giới" trỏ thẳng đến việc lưu trữ tĩnh (static hosting) trên một CDN biên (Content Delivery Network — mạng phân phối nội dung, tức là các bản sao tệp của bạn được lưu ở các trung tâm dữ liệu khắp thế giới để mỗi khách truy cập tải từ nơi gần nhất), cộng với một hàm nhỏ xíu cho đúng một việc cần đến một bí mật (): tạo phiên () thanh toán Stripe. Chúng tôi chọn một trang tĩnh trên Cloudflare Pages với một Pages Function duy nhất. Toàn bộ kiến trúc gói gọn trong một câu — một trang tĩnh gọi một hàm, hàm đó nói chuyện với Stripe — và chính sự đơn giản đó giúp việc gỡ lỗi sau này dễ dàng hơn.
Toàn bộ hệ thống chỉ có ba khối, và chỉ khối ở giữa mới từng chạm vào một bí mật:
┌─────────────┐ POST /api/checkout ┌──────────────┐ secret key ┌────────┐
│ STATIC PAGE │─────────────────────▶│ PAGES │─────────────▶│ STRIPE │
│ (HTML/JS) │◀─────────────────────┤ FUNCTION │◀─────────────┤ │
│ en/ko/zh │ checkout URL │ (the only │ session URL └────────┘
└─────────────┘ │ secret-side)│
served from CDN edge └──────────────┘
Những câu lệnh then chốt
Chúng tôi bắt đầu rộng và để AI tự đề xuất cấu trúc:
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.
Sau đó là luồng thanh toán, nơi bí mật đó tồn tại:
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.
Cụm từ "never hardcode it" (đừng bao giờ viết cứng nó vào code) hoàn toàn xứng đáng có mặt ở đó. Nếu để mặc theo mặc định, một AI sẽ vui vẻ đặt một khóa giữ chỗ (placeholder key) ngay trong code để làm cho ví dụ "chạy được", và một khóa giữ chỗ có cách biến thành một khóa thật rồi bị đưa thẳng lên sản phẩm. Nói ra điều đó một lần, ngay trong , rẻ hơn nhiều so với việc phải phát hiện ra nó khi review.
Trở ngại
Lần kiểm thử trực tiếp đầu tiên thất bại: nút Buy không phản hồi gì, và console của trình duyệt hiện lỗi CORS (Cross-Origin Resource Sharing — quy tắc của trình duyệt về việc trang nào được phép gọi trang nào). Chúng tôi không đoán mò. Chúng tôi dán nguyên văn lỗi vào:
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 phát hiện ra ngay lập tức: hàm đang trả về của Stripe dưới dạng (JavaScript Object Notation — một định dạng văn bản đơn giản, dễ đọc mà các chương trình dùng để trao đổi dữ liệu có cấu trúc), nhưng lệnh fetch lại được thực hiện trước khi bước kiểm tra sơ bộ (preflight) OPTIONS của hàm được xử lý, vì chúng tôi đã lỡ triển khai trang tĩnh và hàm vào hai dự án Pages khác nhau. Nguyên nhân thật sự nằm ở cấu trúc triển khai (deployment topology), không phải ở code. Chúng tôi chuyển hàm vào thư mục /functions của cùng một dự án, và lỗi biến mất.
Bài học này đáng để dừng lại suy ngẫm: triệu chứng nằm ở trong code (một lỗi CORS từ một lệnh fetch), nhưng nguyên nhân nằm ở hạ tầng (hai dự án thay vì một). Nếu chúng tôi để AI "sửa lỗi CORS" mà không dán kèm bối cảnh cấu hình, nó hẳn đã gắn thêm các header Access-Control-Allow-Origin — một cách sửa chạy được, che đi triệu chứng, nhưng để lại lỗi thật vẫn còn nguyên đó. Hãy dán nguyên văn lỗi và bối cảnh xung quanh, và đừng chấp nhận lời giải thích hợp lý đầu tiên nếu nó không khớp với cấu hình thực tế của bạn.
Ra mắt
Chúng tôi thêm các khóa Stripe thật dưới dạng biến môi trường đã mã hóa trong bảng điều khiển (dashboard) của Pages (không bao giờ đặt trong kho mã — repo), trỏ ( Name System — hệ thống tên miền, cuốn sổ địa chỉ của internet ánh xạ một tên miền tới một máy chủ) của tên miền cô ấy về Pages, và thực hiện một giao dịch mua thử thật trị giá 29 đô la bằng thẻ, sau đó hoàn tiền lại. Chúng tôi cũng cố tình thử qua luồng thất bại — một thẻ thử bị từ chối — để xác nhận cô ấy không bị tính phí và thấy một thông báo hợp lý thay vì một màn hình trắng. Ra mắt chỉ trong một buổi chiều. Cô ấy có đơn hàng đầu tiên ngay tối hôm đó.