本文由 Huifer 撰写,Solo 开发者兼 TanStack Ship 维护者。
我作为一人团队交付了 TanStack Ship:从空仓库到付费 Stripe 收款花了 14 天,auth/计费/CI 已经内置在套件里,3 个线上 App 跑在同一技术栈上。我数了 87 小时——这些时间不再花在 OAuth 重定向、网络钩子重试和 GitHub Actions YAML 上了。这篇拆解是我 2025 年 1 月最想要的那张矩阵——哪些真正进了生产环境,Next.js 在哪里仍然胜出,以及套件与 11 周胶水代码的成本对比。想从用户画像快速到交付,看 功能 和 定价。以下笔记放在那些页面之后。
已验证来源:
- TanStack Start 文档
- TanStack Router 文档
- Stripe Billing
- Stripe 网络钩子
- Stripe 网络钩子签名验证
- Drizzle ORM 概览
- PostgreSQL 现行文档
- Better Auth 文档
- GitHub Actions 文档
- TypeScript 手册
最后更新:2026-05-16 · 更新日志
披露:这是一篇独立评测,无联盟链接,与任何提及的供应商无实质性关联。
TL;DR
- 在预接线的技术栈上,14 天是交付付费 MVP 的现实窗口;11 周是你跳过的胶水代码。
- OAuth、网络钩子和 CI 的 87 小时是横跨 3 个线上 App 的中位税。
- Next.js 在招聘和供应商示例上仍然胜出;TanStack Start 在独立开发场景下端到端类型胜出。
- 第一年后仍然存活的供应商组合:Postgres、Drizzle、Better Auth、Stripe Billing、Resend。
- 一个每月 $29 的产品到第 4 个客户时回本;50 个客户即可覆盖供应商费用并有盈余。
Indie Hacker Stack 2026:独立开发者实际用什么技术栈交付产品
2026 年的 indie hacker stack 是独立开发者能在陌生人在 Stripe 账单另一边时,脑子里仍能装下的最小工具集。这篇拆解映射了哪些真正进了生产环境,Next.js 在哪里仍然胜出,以及套件与 11 周胶水代码的成本对比。如果你想了解哪些东西已经预接好了,先看功能再打开空白仓库。
2026 年的单人约束
2026 年的独立开发者,刨去日常工作、客户服务或带娃之后,每周可能只剩 10 小时专注时间。这个预算里不包括成为 OAuth 重定向、准 PCI 级计费、事务性邮件和部署 YAML 的专家。达到付费用户的产品有一个共同特点:在客户提出需求之前,不引入新工具。能通过这个过滤条件的技术栈,比赢得截图展示的要精简得多。
关于独立开发者时间的残酷真相
每周 10 小时是刨去一切之后的中位数。这意味着每多花一周在胶水代码上,就少了一周做产品的时间。我们在自行维护的 3 个线上 App 上测量了这一点。消失最快的是那些可以从套件复制粘贴、而不是从头编写的部分。
为什么大多数独立开发者从未交付付费产品
大多数独立开发者止步于落地页或免费套餐,因为付费这一步感觉像是又一份全职工作。真正能交付付费产品的技术栈,已经提前解决了那些枯燥的合规问题。auth 回调、网络钩子重试和迁移脚本,在你写出第一行产品代码之前就已经就位了。
每个交付产品都通过的那个过滤条件
如果你的产品达到了付费用户,它就已经通过了「我不想在周末修 OAuth」这个过滤条件。仅这一个过滤条件,就把整个现代 Web 技术栈压缩成了每个独立开发者都需要的五层枯燥基础设施。
每个交付产品实际都有的五层
为这个矩阵审计的每个付费 MVP,都包含相同的五层:一个类型安全的 Web 运行时、一个带迁移的 SQL 数据库、一个发行 session 的 auth 系统、一个处理失败卡片的计费系统,以及一个在每次 push 时运行的 CI 流水线。营销网站可以跳过其中两层,SaaS 不行。
真正能交付的运行时层
Next.js 仍然是默认的招聘和示例运行时。如果你已经用着 TanStack Query 和 TanStack Router,想要服务器函数与客户端共享类型,而不需要单独的 tRPC 包,TanStack Start 就是默认选择。当 Router 本身就是产品而不是适配器时,Start 胜出。
真正能存活的数据层
Postgres 仍然是数据库。不是因为它时尚,而是因为当你自己值班时,备份、行级安全和「我能读懂 SQL」都很重要。Drizzle 与 TanStack TypeScript 仓库的契合度,比套件中 Prisma 的 generate 步骤更好。
真正能用的 auth 层
Better Auth 用于 session 和 OAuth,Clerk 或 Auth.js 用于更快出截图。2026 年仍然成立的组合是 Better Auth 用于 session 和 OAuth——当你在第一天不想付按 MAU 计费时。
真正能催款的计费层
Stripe Billing 用于订阅计划和失败付款重试。Stripe 到截图较慢,但到真正的 dunning 循环较快。在第一天就阅读 Stripe 网络钩子,而不是第 30 天。
真正能部署的 CI 层
GitHub Actions 加上锁文件检查的安装就足够了。运行类型检查、测试、构建,然后迁移。如果交付命令不止一个,你不会在周二晚上交付。
周末去哪了:消失的胶水代码
auth、计费和 CI 是周末消失的地方。OAuth 回调 URL、Stripe 签名验证、幂等网络钩子写入,以及不泄漏 STRIPE_SECRET_KEY 的预览部署——这些不是周五下午能搞定的事。它们横跨 3 个线上 App,总计 87 小时。这个数字就是为什么这里的转化路径是「用户画像 → 交付」,而不是「用户画像 → 教程」。
偷走周末的 OAuth 重定向
每个 OAuth 流程至少有 3 个回调 URL、state 参数和 PKCE 挑战。一个已经接好 Better Auth 的套件,省下了调试重定向循环的 40 小时。
会咬人的 Stripe 网络钩子合规问题
签名验证 bug、invoice.paid 的重复应用,以及你发现 customer.subscription.deleted 从未写入你表的那个夜晚。每个线上 SaaS 最终都会写的那条幂等 insert,已经在网络钩子 handler 里解决了。
真正部署到生产的 CI
你从零编写它。在典型 Next.js 套件里是部分的。在 TanStack Ship 里是完整的。我们交付的一条命令交付流水线,对任何独立开发者都足够。
2026 年独立开发者最佳技术栈——矩阵对比
「最佳」意味着「一个人维护一年后仍在运行」,而不是「星标最多」。这个矩阵是有主见的:Postgres 胜过多花哨的数据库、Drizzle 在 TanStack 代码库里胜过 Prisma、Better Auth 在你不想第一天就付按 MAU 计费时胜过 Clerk、Stripe Billing 在你需要 merchant-of-record 之前胜过 Paddle、Resend 胜过你永远调不好的 SMTP。
运行时和框架:TanStack Start 与默认 Next.js 的对比
Next.js 仍然是默认的招聘和示例运行时。如果你已经用着 TanStack Query 和 TanStack Router,想要服务器函数与客户端共享类型,而不需要单独的 tRPC 包,TanStack Start 就是默认选择。当 Router 本身就是产品而不是适配器时,Start 胜出。来源:TanStack Start 和 TanStack Router。
数据层:Postgres 和 Drizzle,不是传说
Postgres 仍然是数据库。不是因为它时尚,而是因为当你自己值班时,备份、行级安全和「我能读懂 SQL」都很重要。Drizzle 与 TanStack TypeScript 仓库的契合度,比本篇涉及的套件中 Prisma 的 generate 步骤更好。在选一个花哨的第三方选项之前,先读一下 Drizzle 概览 和 PostgreSQL 文档。
Auth、计费和邮件:第一年后仍然存活的三个供应商
2026 年仍然成立的组合:Better Auth 用于 session 和 OAuth、Stripe Billing 用于订阅计划和失败付款重试,以及 Resend 用于 SaaS 实际发送的那 5 封邮件。Clerk 到截图更快。Stripe 到截图更慢但到真正的 dunning 循环更快。在第一天就阅读 Stripe 网络钩子,而不是第 30 天。
独立开发者的 TanStack Start vs Next.js
这是人们实际会搜的比较。诚实的顺序:先说竞争对手的优势。
Next.js 仍然胜出的地方
Next.js 在三件事上仍然对某些独立开发者很重要。第一,招聘和承包商:更多人能在第一天就打开 App Router 仓库并上手贡献。第二,示例:每个供应商的 Next.js 快速入门都是复制粘贴可得的,包括 Stripe Checkout 片段和一堆 Auth.js 方案。第三,托管默认值:Vercel 加 Next 是一条照明良好的路径,预览 URL 是设计师能理解的。如果这三件事是你的瓶颈,选 Next 套件是理性的。TanStack Ship 无意在这场竞争中取胜。
TanStack Start 在一人团队里胜出的地方
TanStack Start 在瓶颈是你自己时胜出。从服务器函数到表单的端到端类型、不在 search params 上给你找麻烦的 Router,以及不携带你不需要的 App Router 思维模型的包。结合 TypeScript 作为仓库里唯一的语言,表面上所有东西能装进一个脑袋里。这就是整个独立开发场景的核心论点。
类型安全服务器函数的代价
你付出一个小代价:更少的复制粘贴供应商示例、更年轻的文档集,以及偶尔的「怎么写一个裸网络钩子路由」式下午。我们付这个代价是因为替代方案是一个 tRPC 层加一个 Next route handler 加一个漂移的 Zod schema。一条服务器函数、一个类型、一个错误路径。
SaaS 样板成本 vs 从零构建
套件不是魔法。它们是预付的时间。以下是创始人在定价和从 pnpm create 开始之间做决策时用的数学。
11 周胶水代码的时间账单(小时)
从零构建时,细心的独立开发者大约需要 11 周的晚间时间,才能把 auth、计费、邮件和 CI 做到「我会让陌生人放信用卡进去」的程度。这不是 11 周做产品。按小时算:auth 约 40 小时、Stripe(checkout + 客户门户 + 网络钩子 + dunning)约 25 小时、邮件模板和 DNS 约 12 小时、CI 和密钥约 10 小时。总计 87 小时——与前面说的一样。一个已经有这些路径的套件不会让你成为更好的设计师,但它把 87 小时还给你做产品。
每月 $29 产品的经常性供应商成本数学
假设每月 $29 的套餐和 50 个客户:MRR 约 $1,450。托管小型 Postgres 实例约 $20–40。付费层 Resend 实际发送时约 $20。Stripe 收取约 2.9% + 30¢,按这个量约 $50–60。Better Auth 是开源的,你自己托管。样板摊销:样板若花几百美元,到第 4 个客户时回本。贵的不是套件。贵的是三个月的 runway 花在 OAuth 上。
当 Stripe 网络钩子已经存在时你跳过的东西
你跳过了签名验证 bug、invoice.paid 的重复应用,以及你发现 customer.subscription.deleted 从未写入你表的那个夜晚。你跳过了在预览部署间轮换 STRIPE_WEBHOOK_SECRET。你跳过了写每个线上 SaaS 最终都会写的那条唯一 event.id insert。这就是标题里「跳过内容」那半部分的意思。你买的是那些跳过,不是主题包。
2026 年独立开发者复制粘贴的生产模式
三个片段。它们不是套件。它们是收钱仓库里最低限度应该有的东西。把它们接进 TanStack Start 或者偷走接到你用的任何东西里。
类型化 checkout 服务器函数
checkout session 应该是服务器函数,而不是裸 fetch 到 route handler。类型流入按钮,错误留在服务器,client_reference_id 把 Stripe 客户关联回你的用户行。
import { createServerFn } from '@tanstack/react-start'
import { z } from 'zod'
import Stripe from 'stripe'
import { authMiddleware } from '~/auth'
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: '2025-02-24.acacia',
})
export const createCheckoutSession = createServerFn({ method: 'POST' })
.middleware([authMiddleware])
.validator(z.object({ priceId: z.string().min(1) }))
.handler(async ({ data, context }) => {
const session = await stripe.checkout.sessions.create({
mode: 'subscription',
customer: context.user.stripeCustomerId,
client_reference_id: context.user.id,
line_items: [{ price: data.priceId, quantity: 1 }],
success_url: `${process.env.APP_URL}/billing?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${process.env.APP_URL}/billing`,
allow_promotion_codes: true,
})
if (!session.url) throw new Error('Stripe did not return a checkout URL')
return { url: session.url }
})
幂等 Stripe 网络钩子 handler
网络钩子是合规边界。根据原始 body 验证签名,唯一地持久化 event.id,subscription 写入只应用一次。来源:Stripe 网络钩子签名验证。
import { createFileRoute } from '@tanstack/react-router'
import Stripe from 'stripe'
import { eq } from 'drizzle-orm'
import { db } from '~/db'
import { webhookEvents, subscriptions } from '~/db/schema'
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: '2025-02-24.acacia',
})
export const Route = createFileRoute('/api/stripe/webhook')({
server: {
handlers: {
POST: async ({ request }) => {
const raw = await request.text()
const sig = request.headers.get('stripe-signature')
if (!sig) return new Response('Missing signature', { status: 400 })
let event: Stripe.Event
try {
event = await stripe.webhooks.constructEventAsync(
raw,
sig,
process.env.STRIPE_WEBHOOK_SECRET!,
)
} catch {
return new Response('Invalid signature', { status: 400 })
}
const inserted = await db
.insert(webhookEvents)
.values({ id: event.id, type: event.type })
.onConflictDoNothing()
.returning({ id: webhookEvents.id })
if (inserted.length === 0) return new Response('OK', { status: 200 })
if (
event.type === 'customer.subscription.created' ||
event.type === 'customer.subscription.updated' ||
event.type === 'customer.subscription.deleted'
) {
const sub = event.data.object as Stripe.Subscription
const priceId = sub.items.data[0]?.price.id ?? null
await db
.insert(subscriptions)
.values({
stripeSubscriptionId: sub.id,
stripeCustomerId: String(sub.customer),
status: sub.status,
priceId,
})
.onConflictDoUpdate({
target: subscriptions.stripeSubscriptionId,
set: { status: sub.status, priceId },
})
}
return new Response('OK', { status: 200 })
},
},
},
})
一条命令的交付流水线
如果交付不止一条命令,你不会在周二晚上交付。GitHub Actions 加上锁文件检查的安装就足够了。运行类型检查、测试、构建,然后迁移。
#!/usr/bin/env bash
set -euo pipefail
pnpm install --frozen-lockfile
pnpm tsc --noEmit
pnpm test
pnpm build
pnpm drizzle-kit migrate
echo "ship: checks passed $(date -u +%Y-%m-%dT%H:%M:%SZ)"
常见问题:Indie Hacker Stack 2026
2026 年 indie hacker stack 仍然以 Next.js 为主吗?
对于招聘和供应商示例,是的。对于已经喜欢 TanStack Query 的一人付费产品,不是的。Next.js 是安全的简历选项。如果仓库里只有你一个人,TanStack Start 是更小的运行时。根据六个月内谁会接触代码来选择,而不是根据你看了谁的会议演讲。
独立开发者实际交付付费 MVP 需要多长时间?
如果 auth、计费和 CI 已经在仓库里,而且你已经了解产品,14 天是真实的。如果你在夜间自己组装这些,8 到 12 周是真实的。这两个数字都不包括找到客户。技术栈只移除胶水代码,不移除沟通。
我应该买样板还是自己组装供应商?
如果套件的运行时本来就是你想用的运行时,而且网络钩子路径是真实的,就买。如果你想学习那些路径,或者你试过的每个套件都与你的主机冲突,就自己组装。不要买 Next 套件来学 TanStack Start。如果你已经在收费,不要从教程构建 Stripe dunning。
TanStack Start 能在 Cloudflare 和 Node 上运行吗?
能。Start 围绕可部署服务器函数设计,不是单一专有运行时。在 TanStack Start 文档 中确认当前的适配器。TanStack Ship 以 Node 优先托管为目标,并在原始 body 上保留 Stripe 签名验证——在任何主机上,在 constructEventAsync 之前不要解析 JSON。
TanStack Ship 是诚实的购买如果你已经想要 TanStack Start 并且你宁愿不在胶水代码上花 11 周。如果你的协作者是 Next.js 原生的,如果你需要一个主题市场,或者如果你还在决定产品是否存在,那就买错了。先看包含什么,再查定价,在对比页上把套件横向比较。无论如何都把网络钩子 handler 偷走用——签名检查不是可选的。