速度背后看不见的代价:如果今天让我重建我的AI SaaS产品,我会做出哪些改变
快速开发让你感觉势不可挡。周末冲刺,周五完成原型,周日上线。这就是我开发 Learnflow AI 的全部方法。
表面上看?它奏效了。
用户注册顺利。语音会话进行得很流畅。技术栈(Next.js + Convex + Kinde + Vapi)运行良好。
但几周后,问题开始显现。
用户界面中没有,功能集中也没有。
但本质上。
在我后端代码中预设的假设里,在我为了实现“演示”效果而硬编码的流程里,在我几乎没有为特殊情况、升级、重新认证和回访用户预留空间的地方。
这是我在发布MVP之前最希望看到的一篇文章。
速度并不重要,重要的是你放弃了什么。
起源:周末速成人工智能学习之旅
这个想法很简单:
让任何人都能创建感觉定制化且响应迅速的语音AI导师。
堆
- Vapi:语音转文字 + 代理逻辑
- Convex:后端数据库 + 实时变更
- Kinde:身份验证 + 托管定价页面
- Next.js 应用路由:前端 + 路由
48小时内,我就让它运行起来了:
- 报名
- 选择方案(通过 Kinde)
- 降落在仪表板上
- 点击“创建导师”
- 点击“开始会话”
它奏效了。基本奏效了。
我始料未及的事
追求“立竿见影”是有代价的。
以下是逐渐出现的盲点:
1. 入职状态未持久化
我设置了一个引导用户创建第一个导师的步骤说明。它只显示一次。但如果他们刷新页面或稍后回来查看呢?
他们从零开始。
2. 升级逻辑过于脱离实际
Kinde托管了我的价格表,套餐信息也保存到了用户元数据中。但是应用内的逻辑需要手动读取和检查这些信息。我遇到了升级提示出现不及时,甚至根本不出现的问题。
3. 会话跟踪不具备模块化特性
我追踪了会话开始次数并扣除了积分……但并非总是如此。重试流程、连接断开或用户切换设备都会导致一些特殊情况下的问题。
我的解决方法:逐层修复
1. 通过 Convex 进行入职状态跟踪
我没有使用本地状态或仅限前端的逻辑,而是将入职步骤跟踪移到了后端。
export const setOnboardingStep = mutation({
args: { userId: v.id("users"), step: v.string() },
handler: async (ctx, args) => {
await ctx.db.patch(args.userId, { onboardingStep: args.step });
},
});
现在,无论用户从哪里登录,我都可以:
- 简历入职
- 根据他们最后出现的步骤进行分支逻辑
- 在合适的时机触发升级提示
图表:入职流程前后对比
2. 信用执行作为中间件
前:
- 我在按钮点击事件处理程序中手动检查了用户积分。
- 诸如重复呼叫或会话中断等极端情况未涵盖在内。
现在:
- 我将信用检查封装在一个专门的突变体中
- 该变异会在任何会话开始之前运行
export const canStartSession = query({
args: { userId: v.id("users") },
handler: async (ctx, args) => {
const user = await ctx.db.get(args.userId);
const isPro = user?.plan === "pro";
const hasCredits = user?.credits > 0;
return isPro || hasCredits;
},
});
用于前端:
const allowed = await api.sessions.canStartSession({ userId });
if (!allowed) return showUpgradeModal();
图示:信用审核流程
3. 模块化会话跟踪
旧模式:
startSession刚刚更新了演职员表- 但它没有存储元数据:哪个导师,何时,从哪个设备。
新模式:
export const addSession = mutation({
args: {
userId: v.id("users"),
companionId: v.id("companions"),
timestamp: v.optional(v.string()),
},
handler: async (ctx, args) => {
await ctx.db.insert("sessions", {
userId: args.userId,
companionId: args.companionId,
});
},
});
这使我能够:
- 查看每个用户的历史记录
- 根据行为触发升级提示
- 可视化每位导师的使用情况
我采用的更优架构模式
功能标志
if (user.plan === "pro")我现在改用:
function hasFeature(user, feature) {
if (user.plan === "pro") return true;
return freePlanFeatures.includes(feature);
}
更清洁。更易于维护。集中式管理。
事件钩子
我希望行动与反应之间能有更好的分离。
我没有在每次会话启动失败后进行硬编码,而是showUpgradeModal()使用了事件发射器模式:
events.on("session-blocked", () => {
showUpgradeModal();
});
这意味着我以后可以替换用户界面反应,而无需修改业务逻辑。
可组合逻辑:信用 + 计划
与其将计划逻辑分散在几十个地方,我现在将其整合在一起。
function canUseFeature(user, feature) {
if (user.plan === "pro") return true;
if (!user.credits || user.credits <= 0) return false;
return freePlanFeatures.includes(feature);
}
金德:哪些方法奏效,哪些无效
✅ 哪些方法有效
- 托管定价页面 = 即时流程
- 元数据同步(会话中的计划信息)
- 轻松切换免费版和专业版套餐
⚠️ 需要改进的地方
- Kindle 元数据不是实时更新的(切换套餐后需要手动同步)。
- 没有内置的使用强制执行机制
- 我需要通过凸查询来实现自己的门控。
不过,Kinde已经很好地处理了身份验证层。我只需要构建强制执行层即可。
如果从今天开始,我会做不同的选择
- 设计署名权从第一天起就生效。
- 用户不看文档
- 如果使用情况不可见,就不会有人升级。
- 在后端跟踪新用户注册和会话状态
- 仅前端状态很脆弱
- 使升级逻辑响应式而非静态。
- 根据用户的操作而非他们所在的页面来触发提示。
- 模块化信用检查
- 永远不要仅仅依赖前端逻辑。
- 尽早规划基于角色的用户界面
- 通过分级计划来控制使用频率有助于防止过度使用。
最后想说的话
MVP的速度很棒。
但每一次速度提升都会带来一些弊端,而这些弊端最终都需要你来解决。
那不是失败,创业公司就是这样运作的。
如果你和我几周前一样:
- 快速启动
- 努力保持苗条身材
- 不确定定价逻辑应该深入到什么程度。
记住:
快速发货,但也要为未来的自己留出空间。
使信用系统、升级提示和会话强制执行可组合。
使新用户引导状态持久化。
并确保二次用户无需从零开始。
有疑问或想发布自己的 AI MVP 吗?
欢迎留言或私信我。我正在公开开发 Learnflow AI,它由Kinde、Convex、Vapi 以及类似这样的课程提供支持。
文章来源:https://dev.to/sholajgede/the-unseen-cost-of-speed-what-id-change-if-i-rebuilt-my-ai-saas-today-455m