我和朋友 Geert 一起联合创办了 Global Pet Sitter。两个开发者,加起来超过 10 年的房屋看护经验,试图打造一个透明且社区驱动的平台。一开始只是个简单的想法,说实话,最后变成了我人生中最有收获的学习经历之一
为什么要做这个
我们自己做了好几年的房屋看护。用过 TrustedHousesitters 和 Nomador 等平台。说实话,我们越用越烦
问题不断积累:
- 费用越来越离谱 - TrustedHousesitters 在年费之外又加了每次看护 12 美元的预订费。感觉就是在薅社区的羊毛
- 客服根本不回复 - 邮件石沉大海,电话客服砍了,只剩聊天机器人。看护期间出了问题,你只能自己扛
- 评价系统形同虚设 - 虚假评价即使有证据也删不掉。房主或看护人行为不当也没人管
- 几乎没有真正的审核 - 背景调查是自愿的,还要看护人自己掏钱。平台即使收到盗窃或宠物被忽视的举报,也基本不采取行动
我们在 Trustpilot 和 Sitejabber 上反复看到同样的投诉。TrustedHousesitters 在 Sitejabber 上超过 1000 条评价只有 2.7/5 分。人们描述了盗窃、财产损坏、临时取消,以及一个「调查了但拒绝采取行动」的平台
一个本来以社区为核心的东西变得过度商业化了。我们想,也许我们能做得更好
目标很明确:打造一个透明的平台,让宠物主人和看护人可以无障碍地对接。听起来很简单对吧?哈哈
技术栈
我们选了 Next.js 16(App Router + Turbopack)、Convex 做实时后端、Clerk 做认证。样式用 Tailwind,因为人生苦短,没必要从零写 CSS
为什么选 Convex?开箱即用的实时更新。看护人申请或消息进来,你立刻就能看到。不用配 websocket,不用轮询,没那么多烦心事。Schema(数据模式)用 TypeScript 定义,查询类型安全,部署一键搞定
// 看看 Convex 的查询有多清爽Export const getApplicationsByAssignment = query({args: { assignmentId: v.id("sitAssignments") },handler: async (ctx, args) => {const applications = await ctx.db.query("applications").withIndex("by_sit_assignment", (q) =>q.eq("sitAssignmentId", args.assignmentId)).filter((q) => q.eq(q.field("deletedAt"), undefined)).collect();return applications;},});
信任就是一切
当你要别人帮忙照顾宠物时,你需要极大的信任。宠物是家人。没人会随随便便把自家狗交给网上一个陌生人
我们在验证、评价和安全功能上花的时间远超最初计划。隐私控制变成了重中之重。说实话我们应该从一开始就把这个放在第一位。信任不是后加的功能——它是整个产品的基础
盲评系统
有个我挺满意的功能:盲评(Blind Reviews)。双方独立提交评价,在两人都提交之前(或 14 天过后),谁都看不到对方的评价。防止报复性差评,保持评价的诚实度
Const isReviewVisible = (review: { visibleAt?: number }): boolean => {if (!review.visibleAt) return false;return review.visibleAt <= Date.now();};
逻辑很简单,但它彻底改变了整个评价的氛围
从其他平台导入评价
新平台面临的一个难题:有经验的看护人已经在其他网站上积累了评价。他们不想从零开始。所以我们做了一个评价导入系统
流程是这样的:
- 用户关联他们在其他平台(TrustedHousesitters、Nomador 等)的个人资料
- 我们通过让用户在个人简介中添加唯一验证码来确认是本人
- 验证通过后,用户可以上传评价截图
- GPT-4 Vision 自动提取评价数据
Export const extractReviewFromScreenshot = action({args: { imageUrl: v.string() },handler: async (_, args) => {const response = await openai.chat.completions.create({model: VISION_MODEL,messages: [{role: "user",content: [{ type: "text", text: "Extract the review information:" },{ type: "image_url", image_url: { url: args.imageUrl } },],}],response_format: { type: "json_object" },});// 返回: reviewerName, reviewText, overallRating, reviewDate},});
验证的部分很有意思。我们生成一个唯一验证码比如 gps-verify-abc123,用户把它加到个人简介里,然后我们抓取页面检查验证码是否存在
Const codeFound = html.includes(profile.verificationCode);If (codeFound) {await updateProfileStatus({ status: "verified" });}
简单但有效。防止有人冒领别人的评价
双边市场真的太难了
你需要宠物主人和宠物看护人。但宠物主人没有看护人不会来,看护人没有宠物主人也不会来。经典的鸡生蛋蛋生鸡问题
最后我们专注于打造对双方都有实际价值的功能——地图搜索让你能找到附近的看护任务,双重账户让你可以同时当看护人和宠物主人。这些小细节让整个体验更顺畅
地图搜索的坑
地图搜索听起来简单,真正做的时候就知道有多坑了。我们用 LocationIQ 做地理编码(比 Google 便宜太多),还不得不为现有用户做数据回填
Export const backfillSitterCoordinates = action({args: {batchSize: v.optional(v.number()),delayMs: v.optional(v.number()), // 限速保护},handler: async (ctx, args) => {const sitters = await ctx.runQuery(internal.geocoding.getSittersNeedingGeocoding,{ limit: batchSize });// 分批处理以避免触发频率限制for (const sitter of sitters) {// 地理编码并更新...await new Promise(resolve => setTimeout(resolve, delayMs));}},});
缩小地图时我们还用了 supercluster 来做标记聚合。不然图钉太多地图根本没法看
倾听改变一切
最大的教训之一:倾听你的用户。我们最近根据反馈完全改变了平台的宣传方式。之前描述 Global Pet Sitter 的方式根本打动不了人,所以我们重写了
每一条投诉背后都藏着一个功能需求。每一个困惑的用户都在告诉你产品哪里做得不好。当你开始这样看问题的时候,会发现这其实挺酷的
到处都是软删除
我们很早就学到一个教训:永远不要真正删除数据。所有东西都用 deletedAt 字段做软删除(Soft Delete)。出了问题(一定会出的),你可以恢复。用户问「我的列表去哪了?」,你也能给出答案
.filter((q) => q.eq(q.field("deletedAt"), undefined))
这个模式在我们的代码库里到处都是。每次都要记着加确实有点烦,但它能帮你省下无数头疼的问题
我们做了什么
回顾一下我们构建的东西:
- 地图搜索 - 通过标记聚合可视化查找附近的看护任务
- 双重账户 - 用同一个账号同时当看护人和宠物主人
- 隐私控制 - 你决定分享哪些信息
- 实时消息 - 不用刷新,消息直接出现
- 盲评系统 - 不用担心报复,诚实评价
- 评价导入 - 用 AI 提取,把你在其他平台的口碑带过来
- 早期用户奖励 - 因为第一批用户选择了相信我们
我们正在做 iPhone 应用。快了 :)
技术不是最难的部分
我花了大量时间纠结技术栈、架构、性能。结果最难的是让人们关注你的产品
世界上最好的代码,如果没人用,也毫无意义。我真希望早点明白这个道理
话说回来,选对了工具(Convex、Clerk、Next.js)让开发体验足够顺畅,我们才能把精力放在产品本身而不是跟基础设施较劲
我会再来一次吗
绝对会。即使有那些熬夜和怀疑自己的时刻。和你信任的人从零开始做一个东西,能教会你很多其他方式学不到的东西
也许这才是真正的教训。产品可能成功也可能失败,但你走出来的时候,一定比进去的时候懂得更多
Stay Updated
Get notified about new posts on automation, productivity tips, indie hacking, and web3.
No spam, ever. Unsubscribe anytime.



