一个魁北克的独立开发者,用Next.js 16搭了个本地版"58到家",服务器账单是0。不是"接近免费",是字面意义上的零——Supabase免费档、Vercel免费档、Stripe按抽成付费。系统上线后,每个服务需求最多卖给3个承包商,第3人付款瞬间锁单。并发控制没做好就是灾难,他花了最多时间调试的,就是这三行代码里的竞态条件。
这不是技术博客常见的"我做了个Demo",是真实承包商、真实付款、真实纠纷处理的生产环境。
北美本地服务市场被Thumbtack和HomeAdvisor垄断多年,模式很重:承包商先交月费买会员,再竞价抢单。魁北克开发者Marc-Antoine(化名,原文未披露全名)发现,小承包商讨厌订阅——现金流紧张的季节性工种,宁愿按效果付费。
他的解法:Pay-per-lead(按线索付费)。房主填需求表单,系统生成"线索",承包商按需购买。不买不花钱,买了没谈成也认栽。简单到近乎粗暴,但现金流模型立刻跑正。
技术栈选得极狠:Next.js 16(应用路由)、Supabase(数据库+实时+认证)、Stripe(支付)。全部服务免费档覆盖当前业务量,"零托管成本"不是修辞,是账面上的真实数字。
核心交易逻辑用Supabase行级安全(RLS)+ 原子操作兜底。代码片段显示,购买前查询`lead_purchases`表计数,≥3则返回409冲突。但真正的难点是并发:两个承包商同时读到count=2,同时写入,结果变成4个购买记录。他最终用数据库唯一约束+应用层重试解决,"简单代码背后是被坑过三次的教训"。
线索收集表单分四步:服务类型→位置+动态问题→预算+时间线→联系方式。第三步的"动态问题"是坑点——选"屋顶维修"要问坡度,选"室内油漆"要问是否空置,选"除雪"要问车道长度。
实现上,服务端组件处理`generateMetadata`做法语SEO,客户端用`useLang()`切换英法双语。魁北克市场的特殊性:法律要求商业网站必须支持法语,英语是加分项而非默认。
管理员后台有个实时访客计数器,用Supabase Realtime的Presence通道实现。代码显示,订阅`brancheqc-presence`频道,同步事件触发时统计在线状态键值对数量。零额外基础设施,"被严重低估的功能",他原话。
但Presence通道有个隐藏成本:免费档同时在线用户有限制。他没说具体数字,只提到"目前够用"——这是所有"零成本"架构的共同脚注:免费档的边界就是业务增长的硬天花板。
支付流程用Stripe Checkout,webhook监听`checkout.session.completed`。陷阱在环境隔离:live模式和test模式的签名密钥不同,通过API创建webhook(而非Dashboard)才能在响应体里拿到密钥。他踩过的坑:本地测试用test密钥,部署后忘了换live密钥,导致生产环境支付回调全部失败。
更隐蔽的是幂等性处理。代码显示,webhook处理器先查询`lead_purchases`表,确认该session未处理过才执行业务逻辑。Stripe官方建议用`idempotency_key`,但他选择了数据库唯一约束——"更简单,更可见"。
退款和取消逻辑同样依赖状态机。承包商取消购买时,系统重新开放购买槽位,允许第4个承包商进入(如果原购买者是前3人之一)。状态流转的边界条件测试,占了他单元测试代码量的40%。
冷启动阶段的最大敌人:空荡感。没有历史订单,没有实时动态,新访客会觉得"这网站死了"。他做了个`SocialProofToast`组件,轮播右下角弹窗:"XX区的Y承包商刚购买了Z线索"。
数据来源是`/api/recent-leads`接口。有真实数据时显示真实数据,没有时 fallback 到随机承包商+随机区域组合。关键原则:从不硬编码假数字,但"创造性地呈现真实活动"。
这是一种产品伦理的灰色地带——信息真实,但组合方式制造虚假繁荣。他承认这"有点滑头",但反驳说:"比那些显示'已有10万人购买'的假计数器诚实多了。"
限流策略同样极简:内存Map存储IP计数,5次/分钟限制线索提交,3次/分钟限制承包商注册。没有Redis,"这个规模不需要"。Map的内存泄漏风险?他设置了`resetTime`过期清理,但承认"重启会丢数据,接受这个代价"。
从14升到16时,他被动态路由参数的类型变化坑了一把。`params`从同步对象变成`Promise<{id: string}>`,必须await。编译不报错,运行时直接崩溃,"迁移文档里写了,但我没看"。
这是React Server Components生态的缩影:框架迭代快,breaking change藏在细节里。他的应对是锁定版本号,非必要不升级——"生产环境不是技术尝鲜的地方"。
部署在Vercel,边缘节点覆盖北美。魁北克用户平均首字节时间(TTFB)<100ms,但他注意到一个异常:部分承包商反馈页面"偶尔卡顿"。排查后发现是Supabase免费档的数据库连接池限制,并发高时排队等待。解决方案:给数据库操作加客户端超时,超时后提示"请重试"——"不是优雅降级,是诚实报错"。
目前系统运行状态:真实承包商入驻,真实Stripe流水,真实客服邮件处理退款纠纷。他没披露具体收入数字,只提到"够付房租,不够招人"。
这个案例的启示或许在于:技术选型可以极简化,但业务逻辑的复杂度不会消失——它只是从基础设施账单转移到了代码里的竞态条件、状态机和边界 case 处理。零托管成本是真实的,但维护这些"简单代码"所需的心智成本,从未被计入任何账单。
最后一个细节:他的`SocialProofToast`组件里,随机 fallback 的承包商名字是从真实数据库里抽的,区域也是真实存在的魁北克市镇。他说这是"诚实的底线"——如果访客真的去查,那些人和地方都存在,只是那个时间点未必发生了那笔交易。这种精确控制的模糊性,算不算欺骗?他的产品现在每天还在产生新的真实交易,这个问题,可能只有访客自己能回答。
热门跟贴