围绕 Vercel,搭一套独立开发者的交付套件
俊宁 · 2026-11-01
做 TaleFrame 时,最早的需求是一款英文小说与漫剧 App。客户端明确采用原生 Android,先做小说,漫剧保留入口并显示 Coming Soon。很快,问题从页面转到了运营:没有专门维护的服务器,小说列表由谁管理?更新一章,是否还要重新打包 App?内容多起来,存储费用会不会失控?
现在,管理员可以在网页后台编辑小说和章节,保存草稿,再单独发布。App 启动或手动刷新时检查目录版本,阅读时下载相应章节;已经缓存的正文可以离线继续读。这条链路已经接通,后台部署在 Vercel,客户端仍是 Kotlin 与 Jetpack Compose。
做到这里,它确实有了“独立开发者开发套件”的形态:一套可以复用的技术组合、发布机制和验收方法。不过,TaleFrame 还没有整理成开箱即用的模板或 SDK。本文讨论的是已经用于这个项目的交付方案,以及哪些部分值得带到下一个小产品里。
一句话总结
对以内容和常规数据操作为主的小产品,可以让 Vercel 承载管理后台与 API,用托管 Postgres 保存业务数据,用 Blob 保存图片,再由原生客户端负责阅读和缓存。
先把更新、回退、恢复和费用观察做完整,按实际负载增加基础设施。
这次实践最有价值的判断,是没有因为“小说很多”就提前拆出一套文件分发系统。先测量,发现当前文本规模仍然很小,于是保留了更容易维护的实现。
一个后台项目,连接三个不同的职责
TaleFrame 的服务端采用一个 Next.js 项目。管理员页面与 API 放在一起,共享输入校验、内容模型和发布逻辑。部署时也只有一个服务端项目需要处理。
| 部分 | 当前选择 | 负责什么 |
|---|---|---|
| 原生客户端 | Kotlin、Jetpack Compose | 小说列表、阅读器、书架、本地进度与缓存 |
| 管理后台与 API | Next.js,部署到 Vercel | 管理员登录、内容编辑、发布、目录与章节接口 |
| 数据库 | Neon Postgres | 小说信息、章节草稿、正文版本、发布快照 |
| 图片存储 | Vercel Blob | 封面文件及其公开访问地址 |
| 发布与运行 | Vercel 部署能力 | 服务端构建、部署、请求处理及用量查看 |
这里的 Postgres 由 Neon 提供。Vercel 是后台与接口的运行平台,也提供 Blob;数据库和文件存储各自承担持久化职责。围绕 Vercel 组织交付,不意味着所有东西都属于同一个计费产品。Vercel 存储说明
这样的组合保留了客户端选择权。App 通过 HTTPS 读取目录、正文与图片,服务端使用 Next.js 不会把 Android 变成跨端应用。将来增加其他客户端,可以复用内容接口;各端交互仍然需要分别实现。
这一阶段也没有引入 Redis、消息队列、独立搜索服务或付费 CMS。当前需求主要是一个管理员维护书库,读者查询和阅读内容。一个数据库加一个后台项目已经能完成这些操作,新增服务暂时没有解决可验证的问题。
4,105 章到底有多大:先测量,再决定存在哪里
最初担心小说体积,是因为“几十本书、几千章”听起来很多。但章节数不能直接换算成存储费用。
2026 年 11 月 1 日,TaleFrame 完成一次性内容导入后,记录了下面这组数据。MB 按十进制计算;数据库大小是当时的快照,后续编辑与发布会改变它。
| 测量对象 | 本次结果 | 统计范围 |
|---|---|---|
| 小说与章节 | 92 本、4,105 章 | 已导入书库 |
| 原始章节正文 | 约 22.8 MB | UTF-8 文本字节总量 |
| 数据库实际占用 | 约 42.7 MB | 包含表、索引等,不只是正文 |
| 原始封面 | 约 21.9 MB | 92 张图片 |
| 优化后的当前封面 | 约 7.5 MB | 对其中 14 张较大的封面做压缩 |
导入时还逐章比较了原内容与数据库正文的 SHA-256,4,105 章全部一致。存储测量因此对应一批完整迁移的内容,而非删减后的样本。
这组结果支持一个很具体的选择:在当前规模下,把正文继续放在 Postgres,能够省去数据库与对象存储之间的额外一致性维护。编辑草稿、生成发布版本、回退和备份,都能围绕同一份数据完成。
也可以将正文存成对象文件,数据库只保留索引。但这样需要额外处理上传成功而数据库更新失败、快照引用了不存在的对象、旧对象清理与回退发生冲突等情况。它可能适合更大的内容库,当前还没有足够收益抵消这些工作。
封面则值得先处理。图片上传后统一解码、限制尺寸并转换成 WebP,比让几 MB 的图片直接进入小说列表更符合移动端需要。不过,上表的变化描述的是当前封面资源的体积:历史原图仍保留在 Blob 中供旧版本回退,并没有因为换上新图就自动消失,不能把下载量节省写成存储量已经减少。
内容更新需要一个明确的发布动作
把章节直接写进线上正在读取的表,表面上也能更新。但管理员可能只改完半本书,读者就开始读到一半新、一半旧的内容。TaleFrame 因此区分了草稿与发布版本。
一轮更新按下面的顺序完成:
- 管理员修改小说信息或章节,先保存草稿。读者继续访问原发布版本。
- 点击发布后,服务端为章节正文计算哈希,将发生变化的正文写入版本表。
- 生成包含书籍信息、章节顺序和正文版本引用的发布快照。
- 在同一个数据库事务里切换当前发布版本,并递增全局目录版本。
- App 下次检查目录时获得新快照,阅读对应章节时请求新正文。
正文版本按“章节 ID 与内容哈希”去重。只修改一章时,其余章节可以复用已有正文版本;发布快照保存引用,无需每次复制整本正文。当前保留最近五次发布,回退通过切换已发布快照完成,也不会覆盖编辑中的草稿。
可以用一个很小的例子理解这个协议。下面是结构示意,字段值不是线上数据:
{
"version": "18",
"books": [{
"id": "book-id",
"chapters": [{
"id": "chapter-id",
"number": 1,
"sha256": "content-hash",
"asset": "/api/v1/chapters/chapter-id/content-hash"
}]
}]
}
目录回答的是“当前发布了哪些内容、各章节对应哪个版本”。正文接口再按需返回内容。这样,App 不必为了判断是否更新而先下载整本小说。
下架也需要明确语义:服务端可以移除公开目录,并拒绝后续正文读取;已经到达 CDN 的响应可能在缓存期内继续可用,设备上已有的离线内容也无法靠下架动作远程抹除。当前接口缓存窗口为 60 秒。这是内容分发需要接受并向运营说明的边界。
App 的在线更新,更新的是内容
TaleFrame 采用目录版本检查与按章缓存。这里的“在线更新”指书目和正文更新,不包含 APK 升级、动态下发可执行代码或绕过应用商店更新。
目录接口使用 ETag。App 带着上一次的标记请求,如果目录未变,服务端返回 304;如果发生变化,客户端先解析和校验新目录,再替换本地文件。解析失败或网络中断时,保留上一份可用目录。
正文下载后会核对 SHA-256,通过后才进入缓存。缓存键包含正文版本,同一章节被修订后不会误读旧文件。当前缓存上限为 100 MB,按最近读取情况淘汰旧内容;这个数值是本项目的选择,不是阅读 App 的通用标准。
另一个容易漏掉的细节是正在阅读的书。后台刷新目录时,已经打开的阅读会话保持自己的快照,避免章节顺序在阅读中途突然变化。新的入口再使用新目录。
在线版本的安装包不再携带整套历史小说资源,因此首次打开需要网络。之后能否离线阅读,取决于相应章节是否已经缓存。这样的产品行为比笼统地写“支持离线”更准确。
成本可控,要同时看固定费用与使用量
本项目沿用了已有的 Vercel Pro 计划,数据库采用 Neon Free,没有新增付费订阅或部署席位。这描述的是本次新增资源的情况,不能据此得出从零开始也没有固定成本。
以下价格与额度核对于 2026 年 11 月 1 日,用于说明成本结构,实际开通时应重新核对官方页面。
| 项目 | 当时的公开规则 | 对独立开发者的影响 |
|---|---|---|
| Vercel Pro | 每月 20 美元平台费,含一个部署席位及每月 20 美元使用额度 | 平台费构成起步成本;使用额度不等于账单硬上限 |
| Neon Free | 每项目 1 GB 存储、每月 100 CU-hours | 存储够用仍需观察计算消耗,不能只比较数据库大小 |
| Vercel Blob | 存储按量计费,同时涉及操作与传输规则 | 费用不能只按文件体积估算,还要看读取次数、缓存与传输 |
规则来源:Vercel Pro、Neon Pricing、Vercel Blob Pricing。数据库服务有自己的计费规则,不应把 Vercel 使用额度当成所有外部服务的通用抵扣。
项目里的成本控制先落实在几个具体动作上:正文按章下载,目录未变时不重复传输;封面在上传时压缩;发布正文去重并限制历史版本数量;限制输入大小;观察数据库计算、函数调用、流量和存储增长。缓存通常能减少重复工作,但命中率与最终账单仍要在实际流量下观察。
预算设置也需要读清楚。Vercel 的 On-Demand Budget 本身不会停止用量;只有启用相应暂停动作,达到阈值后才会暂停团队所有项目的生产部署。检查存在几分钟延迟,暂停后存储费用仍可能继续产生。因此,它不能被描述成精确、即时、覆盖所有服务的账单硬上限。Spend Management
TaleFrame 所在的团队还承载其他项目,这次没有为了一个新 App 修改全团队暂停策略。若要启用,必须先确认其他项目可以接受同时停服。成本控制需要和服务可用性一起决策。
托管平台省下运维,应用仍要对自己的数据负责
使用托管服务后,可以少维护服务器和运行环境,但内容正确性、安全边界和恢复方式仍然由应用负责。TaleFrame 首轮补上的能力,都对应具体的失败场景。
| 失败场景 | 当前处理 |
|---|---|
| 未登录的人调用管理接口 | 管理 API 校验管理员会话,公开内容接口只读 |
| 登录被反复尝试 | 数据库记录尝试次数并限制频率 |
| 两个编辑页面覆盖彼此内容 | 编辑版本检查,过期提交返回冲突 |
| 上传文件伪装成图片 | 校验类型并实际解码,限制大小和像素数量 |
| 内容发布错误 | 回退到保留的发布快照 |
| 数据库需要重建 | 导出内容备份,恢复工具拒绝覆盖非空数据库 |
| 密钥泄漏到客户端 | 数据库与 Blob 凭证只存在服务端环境中 |
这仍然是单管理员后台。读者账号、会员权限、支付、跨设备阅读进度尚未实现。需要这些能力时,应重新设计身份与授权模型,不能把管理员密码机制直接扩展成面向所有读者的账号系统。
验收一条链路,比分别打开两个页面更有用
服务端构建通过、App 能显示小说,都不足以证明在线更新已经接通。这次验证里最关键的一条,是在浏览器后台编辑并发布新正文,然后让 App 刷新,确认它读取到新版本。
除此之外,还检查了草稿隔离、发布与回退、匿名管理访问被拒绝、正文哈希,以及断网后保留目录和已缓存章节。备份也实际恢复到了另一份空 Postgres 数据库,而非只确认导出文件存在。
正式服务回归在 Android 15 模拟器上完成,七项设备测试通过;这说明当时的客户端与已部署服务能够配合工作。它不代表真实用户规模下的稳定性:目前没有实体手机或不同国家、运营商网络验收,本次联网验证使用了现有代理环境,App 也尚未上架应用商店。
服务端已经通过 Vercel CLI 部署,TaleFrame 源码当时仍在本地,尚未公开,也没有接通 Git 自动部署。因此,这篇文章提供的是方案与已验证机制,暂时没有一个可供读者直接克隆的完整套件仓库。
哪些部分值得带到下一个产品
如果下一个项目仍然是内容阅读、目录展示、轻量工具或常规数据管理,我会优先复用这套组合:一个后台项目、一份明确的数据模型、独立的发布状态、可校验的客户端缓存,以及能够实际恢复的备份。
如果功能只有几页静态内容,则可以更轻,不必先配置数据库和管理后台。反过来,如果主要负载变成长时间计算、大规模实时连接或视频处理,就应按该负载重新选择运行方式,不能因为已有 Vercel 项目就把所有任务塞进去。
这也是 TaleFrame 暂时保留 Coming Soon 的原因。漫剧会增加视频存储、转码、播放与分发问题,小说正文的成本测量无法替视频业务作判断。先把小说的更新与阅读做好,等实际需要承载视频时,再为视频选择服务。
对独立开发者而言,这套方案最值得复用的成果,是一个能持续维护的小产品:内容可以更新,错误可以回退,数据可以恢复,费用有地方观察。是否需要再加一个服务,应当由这些环节中已经出现的问题来决定。
封面为 AI 生成的场景插画,并非 TaleFrame 的界面截图。
本文使用 writting-skill 辅助写作,配图使用 better-imagegen 生成。项目已开源,欢迎在 GitHub 点个 Star。