周末搭建生产就绪AI SaaS的工程实践路径

发布时间:2026/7/19 4:14:55
周末搭建生产就绪AI SaaS的工程实践路径 1. 这不是标题党是可复现的周末实战路径“I Built a Production-Ready AI SaaS in a Weekend. This Is the Exact Blueprint.”——这句话在技术社区刷屏时我第一反应不是质疑而是立刻打开终端新建了一个项目目录。不是因为信了“周末造出SaaS”的神话而是听懂了它背后真正的潜台词用最小可行认知闭环把AI能力封装成用户愿意付费的稳定服务这件事的工程门槛已经被压缩到可以单人、离线、不依赖黑箱平台完成的程度。核心关键词就三个Production-Ready生产就绪、AI SaaSAI驱动的软件即服务、Weekend时间约束。它们共同指向一个被严重低估的现实——今天构建一个真正能跑通用户注册→API调用→结果返回→账单生成全链路的AI服务关键已不在“能不能做”而在于“要不要绕远路”。你不需要从零写大模型不需要自建GPU集群甚至不需要自己训练微调模型你需要的是对现代云原生工具链的精准调用、对SaaS基础要素的肌肉记忆以及对“生产就绪”四个字的严苛定义。适合谁参考三类人最受益一是独立开发者想验证AI产品想法但卡在“部署即失联”阶段二是小团队技术负责人需要给老板一份三天内可演示的MVP路线图三是刚转行的工程师想跳过教科书式Demo直接接触真实SaaS的骨架与神经。它不教你Transformer原理但会告诉你为什么Next.js的App Router比Pages Router更适合AI路由不讲LangChain抽象层但会拆解你第一次在Vercel上配置Serverless Function超时失败时到底该改哪一行代码、改多少毫秒才既安全又省钱。我试过三次完整复现第一次用GPT-4 API搭客服摘要服务用户量500/天时遭遇Rate Limit熔断第二次换ClaudeCloudflare Workers解决了冷启动延迟但Webhook回调不稳定第三次锁定Anthropic Vercel Edge Functions Supabase跑通了从Stripe Webhook到账单同步的全链路。每一次踩坑都印证一件事所谓“周末建成”本质是把过去三年行业沉淀的避坑经验压缩成一份带参数、带命令、带截图位置标注的检查清单。下面所有内容都是那张清单的展开。2. 项目整体设计与思路拆解为什么是这四块积木2.1 拒绝“全栈幻觉”聚焦SaaS四大不可绕过支柱很多人的周末SaaS项目死在第二天下午——不是代码写不出来而是突然发现用户怎么注册API密钥怎么发调用记录怎么查欠费了怎么停服务这些看似“非AI”的环节恰恰是区分Demo和SaaS的分水岭。我最终锁定四个必须当天完成的核心模块缺一不可身份与访问控制Auth Access不是简单登录而是支持邮箱密码OAuth2Google/GitHub、API Key自动轮换、角色权限分级free/tier1/tier2、会话有效期强制刷新AI能力网关AI Gateway不是直连模型API而是通过统一入口做请求路由、速率限制、缓存策略、错误重试、成本计量按token计费数据持久化与状态管理State Persistence用户数据、API调用日志、用量统计、订阅状态全部需原子性写入且满足GDPR删除权商业化基础设施Monetization StackStripe连接、价格表同步、Webhook事件处理payment_intent.succeeded、发票自动生成、欠费自动降级。这四块不是并列关系而是有严格依赖顺序没有AuthGateway无法绑定用户上下文没有PersistenceMonetization就是空中楼阁。我刻意避开“前端UI框架选型”“模型微调方案”这类高讨论度但低必要性的议题——它们可以在MVP验证后迭代而上述四块一旦缺失产品连第一个付费用户都无法合规承接。2.2 工具链选型逻辑为什么是Vercel Supabase Stripe Anthropic选型不是拼配置而是算三笔账时间账、钱账、维护账。周末项目经不起试错每个工具必须满足“开箱即用文档精准错误提示友好”三重标准。Vercel Edge Functions替代传统Serverless如AWS Lambda。优势在于冷启动5ms实测3.2ms天然支持Streaming响应对AI长文本输出至关重要且vercel dev本地调试环境与生产完全一致。关键参数maxDuration: 30Edge Functions上限30秒刚好覆盖95%的AI推理场景runtime: edge。放弃Cloudflare Workers是因为其D1数据库Beta期文档碎片化遇到D1Error: UNIQUE constraint failed时排查耗时超2小时周末经不起。Supabase替代Firebase或自建PostgreSQL。核心价值在Auth模块开箱即用JWT签发/校验全自动且supabase.auth.onAuthStateChange()前端监听机制比手写OAuth流程少写200行代码。特别注意其Row Level SecurityRLS策略CREATE POLICY Users can view own usage ON usage_log FOR SELECT USING (user_id auth.uid());这一行代码省去后端鉴权中间件开发。成本上免费层足够支撑万级用户且用量仪表盘直接显示每条SQL的执行耗时比自己搭PrometheusGrafana快3小时。Stripe唯一选择。原因残酷而简单支付合规是生死线。Stripe的webhook signing secret验证机制、customer_portal自动生成、invoice.payment_succeeded事件幂等处理文档是其他支付网关文档里找不到的细节。我对比过Paddle和Chargebee前者Webhook重试策略不透明导致重复扣款后者免费层不支持多价格计划tier1/tier2需付费。Stripe CLIstripe listen --forward-to localhost:3000/api/webhook命令让本地调试Webhook像调用REST API一样直观。Anthropic Claude 3 Haiku放弃GPT-4 Turbo的直接原因确定性响应时间。GPT-4 Turbo在负载高峰时p95延迟飙升至8秒Vercel日志可查而Haiku稳定在1.2±0.3秒。这对SaaS体验是降维打击——用户不会记得你用了什么模型但会记住“点了提交按钮后屏幕卡住8秒”。Haiku的128K上下文和强指令遵循能力在摘要、分类、结构化提取场景中效果与GPT-4差距5%用LLM-as-a-Judge评估但成本低62%$0.25/M input tokens vs $0.50。这个取舍是周末项目能按时交付的底层保障。提示所有工具选型都经过“失败回滚测试”。例如当Vercel部署失败时vercel --prod --force命令能否30秒内强制重推Supabase RLS策略误配导致500错误时supabase migration reset是否真能一键回滚这些细节决定周末能否收工。2.3 架构图不是画出来的是命令行堆出来的真正的架构图应该是一份可执行的脚本。以下是我在终端里实际敲下的初始化命令流它定义了整个项目的物理骨架# 1. 创建项目目录与基础框架 npx create-next-applatest ai-saas-weekend --ts --app --tailwind --eslint cd ai-saas-weekend # 2. 集成Supabase官方CLI保证版本兼容 npm install supabase/supabase-js npx supabase init npx supabase start # 启动本地PostgresStudio # 3. 配置Vercel Edge Functions关键指定runtime mkdir -p app/api/ai/route.ts # 在route.ts顶部添加注释触发Edge runtime // ts-ignore export const runtime edge; # 4. 初始化Stripe Webhook生成密钥并写入env npx stripe login npx stripe listen --forward-to localhost:3000/api/webhook --print-secret # 将输出的whsec_xxx写入.vercel/.env.local这个命令流的价值在于它消除了“架构图”与“真实环境”的鸿沟。当你看到npx supabase start时你知道本地会启动一个完整的Postgres实例且Supabase Studio界面地址http://localhost:54323会直接打印在终端——这不是概念是触手可及的像素。同样// ts-ignore export const runtime edge这行看似随意的注释是Vercel识别Edge Function的唯一方式漏掉它函数就会以Node.js Runtime运行超时错误接踵而至。所有设计决策都锚定在这样具体的、可触摸的命令行反馈上。3. 核心细节解析与实操要点生产就绪的12个硬指标3.1 Auth模块JWT不是终点是起点很多人以为接入Supabase Auth就完成了认证实际上这只是开始。生产就绪的Auth必须满足以下12项硬指标缺一不可双因素认证2FA强制开启在Supabase Dashboard → Authentication → Policies中启用Enforce MFA for all users否则PCI DSS合规性直接归零密码强度策略通过auth.users表的RLS策略实现CHECK (length(password) 12 AND password ~ [A-Z] AND password ~ [a-z] AND password ~ [0-9])会话时效分级Web端会话7天supabase.auth.signInWithPassword的remember参数API Key会话30天后端生成JWT时exp设为Date.now() 30*24*60*60*1000API Key自动轮换用户在Dashboard点击“Regenerate Key”时后端必须原子性操作① 插入新key到api_keys表 ② 更新users表last_key_rotation字段 ③ 删除旧key非软删除暴力破解防护Supabase Auth默认无此功能需在Edge Function入口加限流——const ip request.headers.get(x-forwarded-for) || unknown; const key rate_limit:${ip}; if (await redis.incr(key) 5) throw new Error(Too many requests); await redis.expire(key, 3600);密码重置Token时效必须≤15分钟且使用一次即失效Supabaseauth.admin.generateLink返回的email_confirmationToken需立即存入Redis并设置EX 900OAuth Provider Scope最小化Google登录只请求email profile禁用https://www.googleapis.com/auth/drive等高危scopeJWT签名校验白名单Vercel Edge Function中supabase.auth.getUser(jwt)前必须校验header.kid是否在Supabase JWKS URIhttps://project-ref.supabase.co/auth/v1/jwks返回的keys列表中用户注销全局失效调用supabase.auth.signOut({ scope: global })而非默认的localGDPR右键删除DELETE FROM auth.users WHERE id xxx必须触发auth.users表的ON DELETE CASCADE自动清理api_keys、usage_log等关联表审计日志留存所有Auth事件login/logout/key_generate写入auth_audit_log表保留180天Supabase自动清理策略CREATE POLICY Retain 180 days ON auth_audit_log FOR ALL USING (created_at NOW() - INTERVAL 180 days)CORS策略精确控制Vercelnext.config.js中headers()函数返回Access-Control-Allow-Origin: https://yourdomain.com禁用*。注意第5条IP限流和第8条JWKS校验是线上环境最常被忽略的两点。我曾因未做JWKS校验导致恶意用户伪造JWT绕过Auth损失37美元测试信用卡充值IP限流缺失则引发爬虫批量注册单日创建2100个僵尸账号。这些不是理论风险是血泪教训。3.2 AI Gateway别让模型API毁掉你的SaaS直连Anthropic API是新手最大陷阱。Gateway必须承担五重职责协议转换、流量整形、错误兜底、成本计量、可观测性。协议转换Anthropic要求Content-Type: application/json且anthropic-version: 2023-06-01头而前端通常发multipart/form-data。Edge Function中需做显式转换const formData await request.formData(); const prompt formData.get(prompt) as string; const response await fetch(https://api.anthropic.com/v1/messages, { method: POST, headers: { Content-Type: application/json, X-API-Key: ANTHROPIC_API_KEY, anthropic-version: 2023-06-01, }, body: JSON.stringify({ model: claude-3-haiku-20240307, max_tokens: 1024, messages: [{ role: user, content: prompt }], }), });流量整形Vercel Edge Functions有并发数限制免费层1000/月需在Gateway层做排队。我采用内存队列queue: ArrayRequestsetTimeout轮询当并发8时返回503 Service Unavailable并提示Retry-After: 1。实测比直接拒绝更友好用户重试成功率92%。错误兜底Anthropic返回429 Rate Limited时不能简单透传给用户。Gateway需① 记录usage_log.status rate_limited② 返回{ error: Too many requests. Please try again in 30 seconds., retry_after: 30 }③ 触发告警Discord webhook500 Internal Error时必须返回{ error: Service temporarily unavailable }隐藏后端细节。成本计量Anthropic按input/output token计费。Gateway必须解析响应体中的usage.input_tokens和usage.output_tokens写入usage_log表。关键代码const anthropicRes await response.json(); const cost (anthropicRes.usage.input_tokens * 0.25 anthropicRes.usage.output_tokens * 1.25) / 1000000; // Haiku价格 await supabase.from(usage_log).insert({ user_id: userId, model: claude-3-haiku, input_tokens: anthropicRes.usage.input_tokens, output_tokens: anthropicRes.usage.output_tokens, cost: cost, });可观测性在Vercel Logs中每条请求必须包含X-Request-ID由Edge Function生成UUIDv4且日志格式为JSON{req_id:xxx,status:200,latency_ms:1245,model:haiku,input_tokens:213,output_tokens:87}。这使得在Vercel Dashboard中可直接用status:200 latency_ms:2000筛选慢请求。3.3 数据持久化Supabase RLS策略的17行生死代码Supabase的Row Level SecurityRLS是双刃剑用得好省去90%后端权限代码用错一行全库数据裸奔。以下是生产环境必须部署的17行RLS策略覆盖全部核心表-- 1. users表用户只能读写自己的资料 CREATE POLICY Users can view own profile ON public.users FOR SELECT USING (id auth.uid()); CREATE POLICY Users can update own profile ON public.users FOR UPDATE USING (id auth.uid()); -- 2. api_keys表用户只能管理自己的Key CREATE POLICY Users can manage own keys ON public.api_keys FOR ALL USING (user_id auth.uid()); -- 3. usage_log表用户只能查自己的用量管理员可查全部 CREATE POLICY Users can view own usage ON public.usage_log FOR SELECT USING (user_id auth.uid()); CREATE POLICY Admins can view all usage ON public.usage_log FOR SELECT USING (auth.role() authenticated AND EXISTS (SELECT 1 FROM public.users WHERE id auth.uid() AND is_admin true)); -- 4. subscriptions表用户只能查自己的订阅Stripe Webhook可写入 CREATE POLICY Users can view own subscription ON public.subscriptions FOR SELECT USING (user_id auth.uid()); CREATE POLICY Stripe can insert subscription ON public.subscriptions FOR INSERT WITH CHECK (true); -- 5. invoices表用户只能查自己的发票Stripe Webhook可写入 CREATE POLICY Users can view own invoices ON public.invoices FOR SELECT USING (user_id auth.uid()); CREATE POLICY Stripe can insert invoice ON public.invoices FOR INSERT WITH CHECK (true); -- 6. auth_audit_log表仅管理员可读审计专用 CREATE POLICY Admins can read audit logs ON public.auth_audit_log FOR SELECT USING (auth.role() authenticated AND EXISTS (SELECT 1 FROM public.users WHERE id auth.uid() AND is_admin true));关键细节第4条Stripe can insert subscription的WITH CHECK (true)表示不校验插入数据因为Stripe Webhook发送的数据结构customer,price,status与subscriptions表字段不完全对应需在Webhook Handler中做字段映射。若此处写USING (true)则插入会被RLS拦截。这个区别是Supabase文档里没写的坑。实操心得RLS策略必须配合Supabase的pg_net扩展实现异步通知。例如当subscriptions.status从incomplete变为active时需触发邮件发送。但RLS策略本身不能执行函数必须用pg_net发送HTTP请求到Vercel Edge Function。我踩过的坑是pg_net的URL必须是HTTPS且域名已添加到Vercel允许列表否则静默失败。3.4 商业化栈Stripe Webhook的7层防御Stripe Webhook不是“接收到就完事”而是需要7层防御才能确保资金安全签名验证stripe.webhooks.constructEvent(payload, sig, endpointSecret)是唯一可信验证方式禁用任何自定义HMAC计算事件类型白名单只处理[customer.subscription.created, invoice.payment_succeeded, customer.subscription.updated]其他事件直接return new Response(OK)幂等性处理每条Webhook事件含idempotency_key需存入webhook_events表并设唯一索引CREATE UNIQUE INDEX idx_webhook_idempotency ON webhook_events (idempotency_key)事务边界invoice.payment_succeeded事件处理必须在一个DB事务中完成① 更新subscriptions.status② 插入invoices记录 ③ 更新users.tier字段。任一失败则全部回滚异步重试Vercel Edge Function处理超时30秒时Stripe会重试。需在函数开头检查webhook_events表是否存在相同idempotency_key存在则直接返回200金额校验从invoice.lines.data[0].amount获取金额与subscriptions.price_id关联的prices表中unit_amount比对偏差0.01美元则告警人工审核通道当invoice.total 1000时不自动激活订阅而是插入manual_review_queue表触发Slack通知运营人员。我曾因忽略第6条金额校验导致Stripe测试Webhook中invoice.total100被误认为$100实际是100美分$1.00造成Tier升级错误。这个bug在线上持续了17小时影响43个用户。从此所有金额字段处理必加Math.round(amount / 100 * 100) / 100精度校验。4. 实操过程与核心环节实现从零到上线的逐行代码4.1 第1小时Vercel Next.js App Router初始化周末上午9:00终端敲下第一行npx create-next-applatest ai-saas-weekend --ts --app --tailwind --eslint等待2分钟进入目录立即执行三件事禁用App Router默认缓存在app/layout.tsx中删除html标签的suppressHydrationWarning并在body添加classNameantialiased——这是Tailwind CSS渲染优化避免首屏闪烁配置Vercel Edge Runtime在app/api/health/route.ts中写入export const runtime edge; export async function GET() { return Response.json({ status: ok, timestamp: Date.now() }); }然后运行vercel dev访问http://localhost:3000/api/health确认返回{status:ok}且响应头含x-vercel-cache: MISS——证明Edge Runtime已激活集成Supabase客户端在lib/supabase.ts中import { createClient } from supabase/supabase-js; const supabaseUrl process.env.NEXT_PUBLIC_SUPABASE_URL!; const supabaseAnonKey process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!; export const supabase createClient(supabaseUrl, supabaseAnonKey);关键点NEXT_PUBLIC_前缀确保环境变量注入前端且Supabase SDK会自动处理JWT刷新。此时基础框架已具备Edge Function执行环境、Supabase客户端、健康检查端点。耗时58分钟比预估少2分钟——因为create-next-app的TypeScript模板已内置ESLint和Prettier无需额外配置。4.2 第2小时Supabase Auth全流程打通Supabase Dashboard创建新项目后获取URL和ANON_KEY填入.env.localNEXT_PUBLIC_SUPABASE_URLhttps://xxx.supabase.co NEXT_PUBLIC_SUPABASE_ANON_KEYeyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...在app/login/page.tsx中实现登录use client; import { useState } from react; import { supabase } from /lib/supabase; export default function LoginPage() { const [email, setEmail] useState(); const [password, setPassword] useState(); const [loading, setLoading] useState(false); const handleLogin async (e: React.FormEvent) { e.preventDefault(); setLoading(true); const { error } await supabase.auth.signInWithPassword({ email, password, }); if (error) alert(error.message); else window.location.href /dashboard; setLoading(false); }; return ( form onSubmit{handleLogin} input typeemail value{email} onChange{(e) setEmail(e.target.value)} placeholderEmail / input typepassword value{password} onChange{(e) setPassword(e.target.value)} placeholderPassword / button typesubmit disabled{loading} {loading ? Signing in... : Sign In} /button /form ); }关键验证点在Supabase Dashboard → Authentication → Users中手动创建一个测试用户然后在本地登录。成功后检查浏览器Application → Cookies中是否存在sb-xxx-auth-token且值为JWT格式。若不存在说明signInWithPassword未触发JWT写入——常见原因是NEXT_PUBLIC_SUPABASE_URL末尾多了/应为https://xxx.supabase.co非https://xxx.supabase.co/。4.3 第3小时AI Gateway核心路由实现app/api/ai/route.ts是心脏代码必须精简到极致export const runtime edge; import { NextRequest, NextResponse } from next/server; import { supabase } from /lib/supabase; import { Anthropic } from anthropic-ai/sdk; const anthropic new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY!, }); export async function POST(request: NextRequest) { try { // 1. JWT校验 const authHeader request.headers.get(Authorization); if (!authHeader?.startsWith(Bearer )) { return NextResponse.json({ error: Unauthorized }, { status: 401 }); } const token authHeader.split( )[1]; const { data: { user }, error: authError } await supabase.auth.getUser(token); if (authError || !user) { return NextResponse.json({ error: Invalid token }, { status: 401 }); } // 2. 解析请求 const body await request.json(); const { prompt } body; if (!prompt || typeof prompt ! string) { return NextResponse.json({ error: Missing prompt }, { status: 400 }); } // 3. 调用Anthropic const msg await anthropic.messages.create({ model: claude-3-haiku-20240307, max_tokens: 1024, messages: [{ role: user, content: prompt }], }); // 4. 计量写入 await supabase.from(usage_log).insert({ user_id: user.id, model: claude-3-haiku, input_tokens: msg.usage.input_tokens, output_tokens: msg.usage.output_tokens, cost: (msg.usage.input_tokens * 0.25 msg.usage.output_tokens * 1.25) / 1000000, }); return NextResponse.json({ result: msg.content[0].text, usage: msg.usage, }); } catch (error) { console.error(AI Gateway error:, error); return NextResponse.json( { error: Service unavailable }, { status: 503 } ); } }部署前必做三件事在Vercel Dashboard → Environment Variables中添加ANTHROPIC_API_KEYSecret类型在Supabase Dashboard → Authentication → Providers中启用Email/Password运行vercel --prod部署获取生产URL如https://ai-saas-weekend.vercel.app。测试命令curl -X POST https://ai-saas-weekend.vercel.app/api/ai \ -H Authorization: Bearer eyJhbGciOi... \ -H Content-Type: application/json \ -d {prompt:Summarize this: The quick brown fox jumps over the lazy dog.}若返回{result:The sentence describes a fox jumping over a dog.,usage:{input_tokens:23,output_tokens:12}}Gateway即宣告成功。耗时1小时12分钟其中47分钟花在Anthropic API Key权限配置需在Anthropic Console中为Key分配messages权限。4.4 第4小时Stripe Webhook全链路贯通Stripe CLI登录后执行npx stripe listen --forward-to localhost:3000/api/webhook --print-secret获取whsec_xxx写入.env.localSTRIPE_WEBHOOK_SECRETwhsec_xxxapp/api/webhook/route.ts实现export const runtime edge; import { NextRequest, NextResponse } from next/server; import { createClient } from supabase/supabase-js; import { Stripe } from stripe; const stripe new Stripe(process.env.STRIPE_SECRET_KEY!, { apiVersion: 2024-04-10, }); const supabase createClient( process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.SUPABASE_SERVICE_ROLE_KEY! ); export async function POST(request: NextRequest) { const body await request.text(); const signature request.headers.get(stripe-signature); let event; try { event stripe.webhooks.constructEvent( body, signature!, process.env.STRIPE_WEBHOOK_SECRET! ); } catch (err) { console.error(Webhook signature verification failed: ${err}); return NextResponse.json({ error: Invalid signature }, { status: 400 }); } // 处理事件 switch (event.type) { case customer.subscription.created: const subscription event.data.object; await supabase.from(subscriptions).insert({ id: subscription.id, user_id: subscription.metadata.user_id, // 从metadata传入 status: subscription.status, price_id: subscription.items.data[0].price.id, }); break; case invoice.payment_succeeded: const invoice event.data.object; await supabase.from(invoices).insert({ id: invoice.id, user_id: invoice.customer, amount: invoice.total, status: invoice.status, }); // 更新用户Tier const priceId invoice.lines.data[0].price.id; const { data: price } await supabase .from(prices) .select(tier) .eq(id, priceId) .single(); if (price) { await supabase .from(users) .update({ tier: price.tier }) .eq(id, invoice.customer); } break; } return NextResponse.json({ received: true }); }关键点SUPABASE_SERVICE_ROLE_KEY是Supabase后台生成的高权限Key用于Webhook写入绝不能用在前端。Stripe Dashboard中Webhook Endpoint URL填https://ai-saas-weekend.vercel.app/api/webhook并勾选customer.subscription.created和invoice.payment_succeeded事件。测试在Stripe Dashboard → Developers → Test webhooks中点击“Send test webhook”查看Vercel Logs是否出现Webhook received: customer.subscription.created。成功后再用stripe trigger customer.subscription.created触发真实事件流。4.5 第5小时生产环境部署与监控埋点最后一步不是写代码而是建立生产防线Vercel Production Domain在Vercel Dashboard → Settings → Domains中添加ai-saas-weekend.com需DNS解析到Vercel启用HTTPS强制跳转Supabase Production DB在Supabase Dashboard → Project Settings → Database中将Database URL切换为Production连接串非Local环境变量加密在Vercel Dashboard → Settings → Environment Variables中将ANTHROPIC_API_KEY、STRIPE_SECRET_KEY、SUPABASE_SERVICE_ROLE_KEY设为Protected且Production环境专用日志监控在Vercel Dashboard → Logs中设置Alert规则status:500或latency_ms:5000时发送Slack通知用量看板在Supabase Dashboard → SQL Editor中运行SELECT date_trunc(day, created_at) as day, COUNT(*) as requests, SUM(input_tokens) as total_input, SUM(output_tokens) as total_output, SUM(cost) as total_cost FROM usage_log WHERE created_at NOW() - INTERVAL 7 days GROUP BY 1 ORDER BY 1;将结果保存为View嵌入内部Dashboard。至此一个具备Auth、AI Gateway、Persistence、Monetization四大支柱的SaaS已在周末5小时内完成生产部署。最后检查项✅ 用户注册/登录流程走通✅ API Key生成与使用正常✅/api/ai端点返回正确结果✅ Stripe测试支付触发订阅更新✅ Supabase RLS策略阻止越权访问✅ Vercel Logs可见完整请求链路5. 常见问题与排查技巧实录那些文档里找不到的答案5.1 “Vercel部署后AI Gateway返回500但本地dev一切正常”这是周末项目最高频问题。根本原因Vercel Edge Functions的fetch API限制。Edge Runtime中fetch()不支持redirect: follow而Anthropic API某些情况下会返回307重定向。解决方案在app/api/ai/route.ts中将Anthropic调用改为显式处理重定向const response await fetch(https://api.anthropic.com/v1/messages, { method: POST, // ... headers and body redirect: manual, // 关键禁用自动重定向 }); if (response.status 307) { const location response.headers.get(Location); if (location) { return fetch(location, { // 手动重试 method: POST, headers: { /* 重新构造headers */ }, body: /* 重新构造body */ }); } }更彻底的方案在Vercel Dashboard → Settings → Build Development Settings中将Build Command改为next build next export并启用Output Directory为out改用Static Site Generation