
我花了将近四个月时间把一个从零开始的SaaS产品完整跑通上线——注册、登录、付费、核心功能、后台管理、邮件通知、用户数据看板全链路闭环。整个过程没写一行手敲的前端HTML/CSS/JS没碰过Express或Django的路由配置没手动建过数据库表结构连部署脚本都是AI生成后微调的。标题里说“Only AI”不是营销话术是实打实的操作路径所有代码由AI生成所有架构决策由AI辅助验证所有文档和测试用例由AI反向推导补全。关键词就三个AI原生开发、零手写代码SaaS、真实生产级落地。这不是概念演示也不是Demo玩具它现在每天有真实用户在用月订阅收入稳定在$1,200左右后台日志显示平均响应延迟187ms错误率0.37%。适合两类人细读一类是技术背景但想快速验证商业想法的产品人另一类是刚学完基础编程、正卡在“学了Python却做不出东西”瓶颈的开发者。你不需要会React全家桶也不需要懂Kubernetes但得愿意把“写代码”这件事重新理解为“精准表达意图持续校准输出”。下面我会像带徒弟一样把每一步怎么走、为什么这么走、哪里踩了坑、哪些AI提示词真正管用全部摊开讲透。1. 项目整体设计与思路拆解1.1 为什么必须放弃“AI写代码复制粘贴”的幻想很多人试过让AI写个Todo App生成50行代码跑起来能增删改查就以为“AI开发”已成现实。但SaaS不是单页应用它是一套活的系统用户注册时邮箱要验证验证链接要带签名token支付成功后要触发Webhook更新用户状态用户升级套餐时旧权限要即时失效后台管理员要能查到某用户过去三个月的所有操作日志。这些环节环环相扣任何一个断点都会导致整条链路崩塌。我最初也犯了这个错——让Claude写了个“带Stripe集成的订阅页面”它生成了前端按钮和后端路由但漏掉了关键三件事没验证Webhook签名导致恶意请求可伪造付款成功没设置数据库事务用户付了钱但权限没更新没加幂等性处理重复推送导致用户被扣两次费。结果上线第三天一个测试用户故意重放了三次Webhook账户连升三级还多收了$297。这让我彻底明白AI不缺语法能力缺的是系统性约束意识。真正的AI原生开发不是让AI当码农而是让它当“架构翻译官”——把业务规则比如“用户付费后24小时内必须开通高级功能”精准转译成带边界条件、异常分支、安全校验的可执行逻辑。所以整个项目的顶层设计我只做了三件事第一用Mermaid语法但实际没画图只用文字描述写清数据流向——用户→Auth服务→Billing服务→Feature Gate→Usage Tracker第二给每个服务定义明确的输入契约Input Contract和输出契约Output Contract比如Billing服务的输入必须包含user_id、plan_id、payment_method_id输出必须返回status、effective_at、next_billing_date第三强制所有AI生成内容必须附带“契约验证注释”即在代码块上方用注释说明“此函数接收的payment_method_id已通过Stripe API v3.2.1校验格式且经数据库foreign key约束存在”。这三条看似简单却是后续所有环节不翻车的基石。没有这层抽象AI生成的代码再漂亮也只是沙上城堡。1.2 技术栈选型为什么选Vercel Supabase Stripe Resend而不是Next.js Prisma Stripe Nodemailer很多人看到“全AI开发”第一反应是堆最新框架T3 Stack、TurboRepo、Drizzle ORM……但我刻意避开了所有需要深度配置的工具链。原因很实在AI对“约定优于配置”的框架理解极差。比如Prisma它要求你先写schema.prisma文件定义模型再运行prisma generate生成客户端最后在代码里import { prisma } from prisma/client。但当我让GPT-4写一个“根据用户ID查询最近3条订单”的函数时它9次里有7次会直接写prisma.order.findMany()却忘了加await或者漏掉where条件更别说处理prisma.disconnect()的连接池释放。而Supabase的PostgREST API是纯HTTP接口AI对RESTful风格的理解稳定得多——它知道GET /rest/v1/orders?select*user_ideq.123这种写法也知道要加Authorization头。同理Vercel的Serverless Functions天然符合“单职责函数”范式AI生成的handler函数结构清晰export default async function handler(req, res) {...}不像Next.js的getServerSideProps需要处理context、req、res三重对象嵌套AI经常混淆req.query和req.body。Stripe和Resend的选择更是血泪教训。早期我用Nodemailer配SMTP让AI写“发送密码重置邮件”功能。它生成的代码逻辑没问题但每次部署都要手动填SMTP_HOST、SMTP_PORT、SMTP_USER这些环境变量而AI在生成.env.example时总会把SMTP_PORT写成“587”这是TLS端口却忘了在代码里加transporter.verify()做连接预检——结果上线后邮件全发不出排查了六小时才发现是端口和加密方式不匹配。换成Resend后AI只需生成一行代码await resend.emails.send({ to: user.email, subject: Reset your password, html: template })所有认证、队列、重试、退信处理全由Resend托管。Stripe同理它的Webhook签名验证逻辑极其严格需用HMAC-SHA256比对payloadsecrettimestampAI自己实现极易出错但Stripe官方SDK里verifyWebhookSig()函数调用简单明确AI几乎不会写错。提示AI原生开发的第一铁律——选工具不是看它多酷而是看它的API是否满足“三低”低认知负荷AI能一眼看懂参数含义、低配置耦合不依赖本地config文件或复杂初始化、低异常分支错误类型少且明确如StripeError vs GenericError。1.3 架构分层为什么坚持“前端-网关-服务”三层而非BFF或Monolith市面上很多AI生成项目直接搞Monolith一个Next.js App包揽所有事。这在Demo阶段很爽但一旦用户量上来问题立刻暴露。我曾让Claude生成一个“用户仪表盘”它把usage统计、账单预览、支持工单列表全塞进同一个getServerSideProps里。结果上线后发现只要工单API慢了200ms整个仪表盘加载就卡住因为Next.js默认串行执行所有数据获取。后来我强制重构为三层前端只负责展示用SWR缓存数据网关层Vercel Serverless Function统一聚合多个服务响应每个服务Auth、Billing、Analytics独立部署、独立扩缩容。这个决策不是凭空来的而是源于一次真实故障某天Stripe API临时抖动Billing服务超时但Analytics服务完全正常。如果还是Monolith所有用户仪表盘都打不开改成三层后只有账单模块显示“加载中”其他功能照常使用。具体到AI协作上分层让提示词更聚焦。比如让AI写Analytics服务我给的指令是“你是一个独立的Usage Tracking服务只接收POST /api/v1/track事件输入JSON必须含event_typepage_view/feature_used/error_thrown、user_id、timestampISO 8601、metadata任意键值对。输出必须是201 Created或400 Bad Request禁止返回HTML或重定向。用Supabase client插入public.usage_events表表结构已定义id UUID PK, event_type TEXT NOT NULL, user_id UUID NOT NULL, timestamp TIMESTAMPTZ NOT NULL, metadata JSONB”。这段提示词里锁死了输入格式、输出状态码、数据库操作范围、甚至表名和字段类型——AI没法自由发挥反而产出更可靠。反观Monolith提示词往往变成“帮我做一个用户仪表盘要好看要快要显示各种数据”结果AI给你生成一堆Tailwind CSS类名和假数据离真实可用差十万八千里。2. 核心细节解析与实操要点2.1 Prompt Engineering真正管用的三类提示词模板AI不是万能的但用对提示词它就是你的超级副驾驶。我总结出三类在SaaS开发中反复验证有效的提示词结构不是网上泛泛而谈的“角色任务格式”而是紧扣工程落地的硬核模板。第一类叫契约驱动型提示词Contract-Driven Prompt专治AI乱写接口。典型场景生成Stripe Webhook处理器。错误示范“写一个处理Stripe webhook的函数”。正确写法你是一名资深SaaS后端工程师正在为生产环境编写Stripe Webhook处理器。 【输入契约】 - HTTP方法POST - 路径/api/webhook/stripe - 请求头必须含Stripe-Signature字符串、Content-Type: application/json - 请求体原始JSON payload未解析 【输出契约】 - 成功时返回200 OK正文为空 - 失败时返回400 Bad Request签名无效或500 Internal Error数据库异常 - 必须调用stripe.webhooks.constructEvent(payload, sig, endpoint_secret)验证签名 - 验证通过后根据event.type分发至对应处理器checkout.session.completed → handleCheckout, invoice.payment_succeeded → handlePayment 【约束】 - 禁止在handler内直接操作数据库所有业务逻辑必须委托给handleXXX函数 - 所有handleXXX函数必须返回Promisevoid并用try/catch包裹 - 必须在finally块中调用supabase.from(webhook_logs).insert(...)记录原始payload和处理结果这个提示词把AI的自由度压缩到最小但它产出的代码第一次就能通过Stripe官方Webhook测试工具的全部校验。第二类叫上下文锚定型提示词Context-Anchored Prompt解决AI“失忆”问题。AI在长对话中容易忘记前文约定比如你刚定义好数据库表结构下一轮让它写查询函数它可能又造个新表名。我的解法是在每条提示词开头强制插入当前项目上下文快照【当前项目上下文】 - 数据库SupabaseProject Ref: xyz123anon key: ey...A - 用户表public.users字段id UUID PK, email TEXT UNIQUE, created_at TIMESTAMPTZ - 订阅表public.subscriptions字段id UUID PK, user_id UUID FK→users.id, plan_id TEXT, status TEXT IN (active,canceled), current_period_end TIMESTAMPTZ - 已确认所有SQL查询必须用Supabase client禁止raw SQL这个快照不是摆设我实测过没有它AI生成的查询语句里30%会用错表名或字段加上后准确率升到92%。关键是这个快照要动态更新——每当AI生成新表或新字段我就把它追加进去形成“上下文雪球”。第三类叫防御性生成型提示词Defensive-Generation Prompt专防AI埋雷。比如生成密码重置流程AI总爱写“生成随机token存数据库发邮件用户点击链接后删token”。但生产环境必须考虑token有效期、单次使用、防暴力破解、泄露应急。我的提示词会这样写你正在编写生产级密码重置功能必须满足OWASP ASVS 4.0.3标准 - token必须用crypto.randomBytes(32).toString(hex)生成非Math.random - token必须存入reset_tokens表字段token_hash TEXT PKbcrypt哈希值非明文, user_id UUID FK, expires_at TIMESTAMPTZ, used BOOLEAN DEFAULT false - 发送邮件前必须检查同一user_id在过去1小时内已发送token数 ≤ 3防爆破 - 重置链接格式https://app.example.com/reset?tokenabc123token必须在URL中传输禁止放在query string外的任何位置 - 验证token时必须同时检查未过期、未使用、hash比对成功、同一IP地址24小时内失败≤5次用redis计数器这类提示词看起来啰嗦但它把安全合规要求直接编译成代码约束比后期Code Review扫漏洞高效十倍。2.2 数据库设计为什么用Supabase的Row Level SecurityRLS代替手写权限中间件传统做法是在每个API路由里写if (user.role admin) { ... }但AI生成这种逻辑极不可靠——它要么漏判要么过度授权。我选择Supabase RLS不是因为它多先进而是因为它的策略定义是声明式的、可测试的、且AI能精准生成。RLS策略本质是一条SQL WHERE条件比如“用户只能读自己的订阅记录”策略就是CREATE POLICY Users can read own subscriptions ON public.subscriptions FOR SELECT USING (auth.uid() user_id);我把这条SQL作为提示词的一部分喂给AI“生成一个RLS策略确保用户只能修改自己创建的工单tickets表且不能修改status字段为closed除非是管理员”。AI输出CREATE POLICY Users can update own tickets ON public.tickets FOR UPDATE USING (auth.uid() user_id) WITH CHECK (auth.uid() user_id AND (status ! closed OR EXISTS ( SELECT 1 FROM public.users WHERE id auth.uid() AND role admin )));你看AI不仅写了基础条件还嵌套了子查询处理管理员特例。更重要的是RLS策略可以独立测试我写了个测试脚本用不同用户JWT调用Supabase API验证策略是否生效。这比在代码里写一堆if-else然后祈祷AI没漏掉分支靠谱太多。实操中我发现两个关键技巧第一所有RLS策略必须命名规范比如Users can read own X、Admins can manage all YAI对这种固定模式识别率极高第二策略必须配合Supabase的auth.uid()函数而不是自己解析JWT——因为Supabase SDK自动注入auth contextAI不用操心token解析逻辑自然减少出错。我曾对比过手写权限中间件的APIAI生成后平均要修3.7处bug用RLS的APIAI生成后只需微调1处通常是字段名大小写。2.3 前端交互为什么放弃React组件生成改用AI写HTMLHTMXAlpine.js很多人迷信AI能生成完美React组件但我试过27次没有一次能直接上线。问题出在状态管理上AI写的useState逻辑经常在异步操作如API调用后忘记重置loading状态或者在表单提交后没清空input值导致UI和state不一致。更麻烦的是React生态的约定太重——useEffect依赖数组、自定义Hook命名规范、Suspense边界……AI就像个没背单词就上考场的学生全靠蒙。我的破局点是回归HTML原生能力用HTMX超文本标记扩展和Alpine.js。HTMX让HTML元素自带AJAX行为比如一个按钮button hx-post/api/subscribe hx-target#pricing hx-swapinnerHTML Subscribe Now /buttonAI生成这种代码毫无压力因为它就是HTML属性没有状态概念。Alpine.js则提供轻量级响应式比如div x-data{ open: false } button clickopen !openToggle/button div x-showopenContent/div /divAI对x-data、click、x-show这些指令的理解非常稳定因为它本质是HTML属性简单布尔逻辑不像React的JSX需要编译、虚拟DOM diff、生命周期钩子。这套组合的好处是前端代码可读性极高非程序员也能维护。我让一位做市场运营的同事改文案她打开HTML文件找到标签直接改文字保存上传立刻生效——不用装Node、不用npm run dev、不用理解props和state。而React项目她改完一行字得等CI/CD跑完、Vercel部署、CDN刷新整个流程5分钟起步。在MVP验证阶段这种“所见即所得”的效率比技术炫酷重要十倍。注意HTMX和Alpine.js不是银弹。它们不适合复杂富交互应用如Figma式编辑器但对于SaaS的常规界面仪表盘、表单、列表、模态框它们让AI生成的前端代码首次运行成功率从38%提升到91%。关键在于你要接受“前端声明式HTML”的思维而不是“前端状态驱动组件”。3. 实操过程与核心环节实现3.1 从0到1如何用AI生成第一个可部署的Vercel Serverless Function我们以最核心的“用户注册”功能为例走一遍完整实操。这不是理论是我当天的屏幕录像逐帧还原。第一步定义输入输出契约耗时2分钟 我打开Notion新建一页写下【注册API契约】 - 方法POST /api/auth/register - 输入JSON { email: string, password: string, name?: string } - 输出201 Created { user: { id, email, name }, token: string }或400/409/500 - 约束email必须唯一密码强度≥8位含大小写字母数字token有效期24h第二步生成Supabase Auth策略耗时1分钟 在Supabase控制台SQL Editor里粘贴AI生成的命令-- AI生成启用Email登录禁用Phone和OAuth ALTER TABLE auth.users DISABLE ROW LEVEL SECURITY; -- AI生成设置RLS用户只能读自己信息 CREATE POLICY Users can read own data ON auth.users FOR SELECT USING (auth.uid() id);第三步写提示词生成函数耗时5分钟 提示词如下已脱敏你是一个Vercel Serverless Function开发者为SaaS编写注册端点。 【环境】 - Supabase Project Ref: xyz123, anon key: ey...A, service_role key: s...v - 使用supabase/supabase-js v2.39.7 【逻辑】 1. 解析req.body校验email格式含和域名、password长度和字符集 2. 调用supabase.auth.signUp({ email, password, options: { data: { name } } }) 3. 如果signUp成功用service_role key查询auth.users表获取user.id和user.raw_user_meta_data 4. 生成JWT token使用crypto.createSign(sha256).update(user.id).sign(service_key) 5. 返回{ user: { id, email, name }, token } 【错误处理】 - email已存在 → 409 Conflict - 密码不合规 → 400 Bad Request - Supabase调用失败 → 500 Internal Error 【注意】 - 禁止在client端暴露service_role key必须用Vercel环境变量SUPABASE_SERVICE_ROLE_KEY - token必须用HS256算法密钥来自环境变量JWT_SECRET第四步AI输出代码GPT-4耗时40秒 它生成了完整的TypeScript函数包括import语句、类型定义、try/catch结构。我只做了两处修改把JWT生成逻辑从crypto改为jsonwebtoken因为Vercel不支持crypto模块以及把Supabase client初始化移到函数外避免冷启动重复初始化。整个函数共87行无语法错误。第五步本地测试耗时3分钟 我用curl模拟请求curl -X POST http://localhost:3000/api/auth/register \ -H Content-Type: application/json \ -d {email:testexample.com,password:Abc12345,name:Test User}返回201数据正确写入Supabasetoken可被后续API验证。整个过程从契约定义到可运行代码不到15分钟。第六步部署与监控耗时2分钟 git add/commit/pushVercel自动部署。我在Vercel Logs里设置告警当/api/auth/register出现500错误时微信通知我。上线后第一小时收到3条告警——全是密码强度不达标用户输12345678证明RLS和校验逻辑生效。没有一次是代码崩溃。这个案例说明AI原生开发的核心不是“让AI写更多”而是“让人定义更严”。你花在契约设计上的每一分钟都能省下十倍的Debug时间。3.2 支付集成Stripe Webhook的AI生成与生产级验证支付是SaaS的生命线也是AI最容易翻车的环节。我用Stripe Webhook为例展示如何让AI产出生产可用代码。首先我创建了一个专用的Prompt模板命名为“Stripe Webhook Generator v2.1”里面固化了所有安全要求【Stripe Webhook Generator v2.1】 你正在为生产环境生成Stripe Webhook处理器。 【强制要求】 1. 必须使用stripe.webhooks.constructEvent()验证签名endpoint_secret从环境变量STRIPE_WEBHOOK_SECRET读取 2. constructEvent必须包裹在try/catch中捕获WebhookVerificationError 3. event.data.object必须用zod schema校验结构提供schema定义 4. 每个event.type必须映射到独立handler函数函数名格式handle${EventType}如handleInvoicePaymentSucceeded 5. 所有handler必须返回Promisevoid并在finally块中记录log到Supabase webhook_logs表 【Zod Schema】 const invoiceSchema z.object({ id: z.string(), customer: z.string(), status: z.enum([paid, uncollectible, void]), amount_paid: z.number(), period_start: z.number(), period_end: z.number() });然后我让AI生成handleInvoicePaymentSucceeded函数。它输出export async function handleInvoicePaymentSucceeded(event: Stripe.Event) { const invoice invoiceSchema.parse(event.data.object); try { // 更新用户订阅状态 await supabase .from(subscriptions) .update({ status: active, current_period_end: new Date(invoice.period_end * 1000) }) .eq(customer_id, invoice.customer); // 同步用量配额 await syncUsageQuota(invoice.customer); } catch (error) { console.error(Failed to handle invoice payment:, error); throw error; } finally { await logWebhookEvent(event, success); } }这段代码的关键在于它没有假设invoice.customer是用户ID而是用了customer_id字段——因为Stripe的customer ID格式是cus_abc123而我的subscriptions表里存的是UUID所以我必须在syncUsageQuota里做映射。AI没写这步但我在提示词里加了“必须同步用量配额”它就生成了调用。我补上syncUsageQuota函数逻辑是查stripe.customers表用cus_abc123拿到email再查users表得UUID。生产验证环节我做了三件事用Stripe CLI本地监听Webhook触发真实payment succeeded事件在Vercel Logs里确认handler执行日志和SQL UPDATE语句登录Supabase查subscriptions表确认status和current_period_end已更新。整个过程AI生成的代码占70%我补的业务逻辑占30%但如果没有那70%的骨架我自己写要2小时还可能漏掉签名验证。3.3 邮件模板AI生成HTML邮件的避坑指南邮件是用户触达的关键通道但HTML邮件是出了名的坑Gmail不支持flexboxOutlook不支持CSS Grid移动端宽度计算诡异。AI生成的现代CSS邮件90%在真实邮箱里显示错乱。我的解法是用AI生成语义化HTML结构再用MJML邮件专用标记语言转译。MJML会自动处理兼容性比如把转成table布局。提示词示例你是一个邮件模板工程师为SaaS生成密码重置邮件。 【要求】 - 输出纯HTML不包含head或body只输出邮件主体div - 结构必须含Logo图片src/logo.png、欢迎语h1、重置按钮a标签href{{reset_url}}、有效期说明p、联系邮箱p - 文字必须用内联style禁止class或外部CSS - 宽度固定为600px居中显示 - 按钮必须是a背景色#3b82f6白色文字padding 12px 24pxborder-radius 4px - 所有图片必须有alt属性文字必须可选中禁用user-select:none 【变量】 - {{reset_url}}重置链接绝对URL - {{expires_in}}有效期如24 hoursAI生成后我粘贴到MJML Live Editorhttps://mjml.io/try-it-live它实时转译成兼容所有邮箱的HTML。我再把转译结果丢给AI“把这个MJML转译后的HTML提取所有内联style合并到对应标签上删除所有注释和空行”。最终得到一份Gmail/Outlook/iOS Mail全兼容的邮件HTML。这个流程的关键洞察是AI擅长生成语义结构不擅长处理浏览器兼容性而MJML是专门解决兼容性的工具。把AI的强项和专用工具的强项组合效果远超单点突破。4. 常见问题与排查技巧实录4.1 典型问题速查表AI生成代码的7大高频故障点故障现象根本原因排查技巧我的修复方案API返回500日志显示Cannot read property xxx of undefinedAI生成的解构赋值未加空值检查如const { data } response但response可能是null在Vercel Logs里搜索undefined定位到报错行用console.log打印response全量结构在所有解构前加if (!response) return;或用可选链?.data数据库写入失败Supabase返回403 ForbiddenRLS策略未启用或策略条件写错如用user_id auth.uid()但表里字段是user_uuid进Supabase SQL Editor执行SELECT * FROM public.users WHERE id auth.uid()看是否返回数据检查策略是否ENABLED用Supabase控制台的RLS Policies页逐条验证策略状态和SQLStripe Webhook验证失败报Signature verification failedAI把endpoint_secret写成明文或环境变量名拼错如STRIPE_WEBHOOK_SECRET写成STRIPE_WEBHOOK_SECRECT用curl -X POST -H Stripe-Signature: xxx -d {} https://your.vercel.app/api/webhook/stripe看是否报相同错误检查Vercel环境变量面板在Vercel环境变量里用Reveal value确认secret值正确在代码里加console.log(Secret length:, secret.length)HTMX请求返回完整HTML页面而非片段AI生成的hx-target指向了不存在的ID或服务器返回了200但Content-Type不是text/html浏览器Network Tab里看Response确认是否含!DOCTYPE检查hx-target值是否匹配页面中真实ID用document.getElementById(target-id)在Console里验证ID存在确保API返回纯HTML片段无 标签Alpine.js状态不更新x-showopen始终为falseAI写了x-data{ open: false }但没写clickopen true或事件绑定在错误元素上在Console里执行$0.__x.$data查看当前元素data状态用Alpine Devtools浏览器插件调试把x-data放到最外层所有交互元素用事件绑定到它邮件在Gmail显示空白Outlook显示乱码AI生成的HTML用了CSS Grid或flex或未设置用https://putsmail.com/测试邮件渲染检查AI生成的HTML是否含