我用 Qoder CN 给自己写了个博客
这篇是本站第一篇文章,那就用它说清楚一件事:这个博客的绝大部分代码不是我写的,是 Qoder CN 写的。 但要什么、能不能算做完、哪一步不对劲,全是我在判断。下面按时间顺序记一遍,包括我在坑里干等的那些时刻。
先给几个数字,免得写成感想文:
- 从第一个文件到线上可访问:2026-09-14 到 09-20,七天
- 49 个提交,业务代码加测试 3650 行,81 个测试(其中一部分会真的启动一个本地 Worker)
- 服务器费用 ¥0/月,域名
fanaoyu.cn¥38/年(首年免费)
一、我到底不满意什么
之前试过几条路,都卡在同一个地方——写的意愿被流程磨掉了:
- Notion / 语雀:写得爽,但域名、样式、数据结构都不是我的,想给某篇文章单独加个目录锚点都做不到。
- WordPress:功能够,但要养一台机器,还要操心插件更新和安全。我不想当运维。
- Hugo / VitePress:便宜省心,但没有我真正想要的三样——打开网页就能改完发布、中文全文搜索、图片自己上传管理。每次为了发一篇要先找编辑器、commit、push,这个动作本身就劝退我。
所以我给自己定的目标很具体:一个动态的个人博客,不花服务器钱,国内读者能打开,我在手机上打开后台就能写完发出去。
二、我怎么用 Qoder CN 做这件事
我把它当"能读需求、能自己写、能自己测"的工程师用,而不是当补全工具。节奏是我事后总结出来的,对我这种不天天写前端的人来说很关键:
- 先让它写设计文档,逼我把需求说清楚。 最后那份设计文档 200 行,包括:只用 Cloudflare 免费额度、前台必须服务端渲染、D1 是唯一数据来源、中文搜索怎么做、图片存哪。很多含糊的地方是在写文档时暴露的,不是写代码时。
- 再让它把文档拆成任务清单(22 个任务、4826 行计划),每个任务都写明"验收标准是什么、怎么测"。我按清单一条条码着走,这样"做完了没"永远有据可依,而不是它说完成就算完成。
- 要求它先写测试再写实现,每个任务做完先自查再提交。这条约束直接决定了我敢不敢让它一次做二十几个任务。
- 最后我自己在浏览器里验收。 好几处问题只有真点一下才看得见,我会在坑里讲。
技术栈的选择基本是它给的方案我点头:Cloudflare Workers 上放一个 Worker,前台用 Hono + JSX 服务端直出,后台是 Vue 3 + Vite 构建出来的单页应用当静态资源,数据全在 D1,图片在 R2,登录限流用 KV,定时发布靠每分钟一次 Cron。
我唯一强硬要求的架构原则是:D1 是唯一真相。我见过太多"Markdown 文件 + 数据库双写"的做法,一旦不同步就是永久脏数据。这条决定了后来"改文案不用部署"那类功能怎么设计。
三、中文搜索:我以为很难,结果是一张表
D1 没有现成的全文检索,所以思路是自己建一张倒排表:
CREATE TABLE search_index (
term TEXT NOT NULL,
post_id INTEGER NOT NULL,
weight REAL NOT NULL,
PRIMARY KEY (term, post_id)
);
打分规则我很满意,因为它简单到我能自己解释:标题命中一次 10 分、摘要 5 分、正文 1 分,每类字段都有词频上限(10 / 5 / 10),防止一篇文章把某个词刷三十次就霸榜。查询时先按交集取,取不到再退化成并集——搜"部署 踩坑"应该优先给同时命中两个词的,而不是一堆只提了"部署"的长文。
分词它先接了 jieba,我上线后实测却是二元切分(bigram):jieba 的 wasm 要作为静态资源交付并显式初始化,那一步没做,运行时就静默退回 bigram;而本地测试环境能直接加载 Node 版的包,于是测试全绿,线上却是另一套引擎。这件事给我的教训比功能本身值钱——测试通过不代表线上真的在用你以为的那条路径。现在的处理是把 TOKENIZER=bigram 明确写进配置,让配置和事实一致;jieba 分支留着,将来做完整再启用。
顺带一个我完全想不到的细节:正文渲染要过 HTML 消毒,而消毒器默认会把标题上的 id 属性剥掉,于是目录锚点全部失效。这种坑不是懂不懂安全的问题,是没人会被提醒。
四、后台体验:我最在意的部分
因为最终是我自己用它写作,所以要求都围绕"少一步是一步":
- 编辑器 10 秒防抖自动存草稿,我不用记得点保存;
- 图片粘贴、拖拽、按钮三种上传方式,上传后自动插入
/img/...,图片存在 R2,将来换域名不用改任何文章; - 每次保存留版本快照,版本页可以任选两版对比 diff 并一键回滚;回滚前会先把"被丢掉的那一版"再存一份——我要求过这一点,因为手滑的成本必须是可逆的;
- 定时发布:设个时间,Cron 每分钟翻状态,到点文章出现且立刻可搜。
五、我陪它踩的十个坑
这些是"我以为做完了、结果没有"的清单。前三个都发生在部署阶段——真正难的从来不是写功能,是让它在真实环境里跑起来。
1. 导入脚本的 --remote 从来没写到线上。 我让它把存量文章导进线上库,它返回"成功",但我打开站点一看是空的。查下来:wrangler 的这个 API 会一声不吭地在本地模拟数据库,报的还是 no such table 这种误导性错误,跟认证完全无关。改法是远端操作一律走 CLI,落库后还要再校验一次主键没漂。结论:对自动化步骤,我要看到"远端确实有这条数据"的证据,不接受退出码。
2. 把真实资源 id 填进配置之后,本地开发和测试全挂了。 登录报 500、no such table: users。原因是本地模拟环境按数据库 id 分文件存状态,--env preview 一加就等于换到一个空库。修完还在 README 里写死了规矩:本地不许带 --env。
3. 新加的表必须先在线上建好,再部署读它的代码。 顺序反了就是整站 500。这个我提前想到了,所以让它每次改数据库都明确两步走。
4. 一句"id 必须是正整数"把我送进后台。 我点「+ 新文章」弹这个错。真相是那条路由根本没有 id 参数,前端把 undefined 转成 NaN 发给了接口。我只有在浏览器里真点了一下才发现,接口测试全过。
5. R2 不是开箱可用。 建桶直接失败,提示要先在控制台开通。这种"平台侧一次性开关"最容易漏。
6. 改 Worker 名字之后,所有人都被踢下线。 密钥是绑在"脚本名 + 环境"上的,改名等于换了个脚本,必须重设一遍。我当时以为哪里坏了,其实是这个。
7. 绑自定义域名:custom_domains 这个键不存在。 它按印象写了配置,部署"成功"但域名一条都没建。正确写法是路由里标 custom_domain: true,而唯一判据是部署输出里有没有 fanaoyu.cn (custom domain) 这一行。现在我都让对方把判据写进 README。
8. 缓存会让测试悄悄变成假通过。 加完 60 秒边缘缓存,那条"回滚后前台应恢复原文"的测试其实读的是自己刚暖起来的缓存——它通过,但不再证明任何事。改成带登录态读(登录本来就绕过缓存)才回到真实回源。这条是我最意外的:性能优化会腐蚀你自己的验收手段。
9. Windows 上的琐碎摩擦。 npx 拉不起来、命令行开关名字不对、包管理器的严格依赖布局要求把间接依赖显式声明、保留字让某条脚本命令失效。这类问题的特征是"每次都要重新查一遍",所以我要求它把解法写进 README 而不是记住在对话里。
10. 我这边到 GitHub 的网络会抽。 写这篇文章时 git push 连续失败两次(连接被重置),但站点完全正常——因为文章是直接进数据库的,发布不依赖推送。这种"两条独立链路"的设计无意中帮我兜住了一次网络问题。
六、免费额度这件事,我把它当设计约束
截至现在(2026-09):Workers 每天 10 万次请求;D1 每天 500 万行读 / 10 万行写、总共 5 GB;KV 每天 10 万次读 / 1 千次写。要注意 从 2026-09-01 开始 D1 的免费额度是硬执行——超了直接报错,不是降速。
所以省行读不是洁癖,是可用性问题:列表只查需要的列、首页一次查询带出标签、归档按年月分组而不是循环查库、只有标题/摘要/正文真变了才重建索引,最后再给前台套一层 60 秒边缘缓存。效果:热状态下回源约 110–150 毫秒,命中缓存约 65 毫秒。这篇长文导入时写了约 2000 条索引记录,量级我心里有数了。
七、国内访问:这段是我自己的弯路
功能全通之后,我才发现真正的问题:别人打不开。*.workers.dev 在大陆是被按域名阻断的——也就是说改 Worker 名、改账号子域,一点用都没有。
中间我认真评估了三条看似聪明的路,全都不成立:
- Gitee Pages:官方帮助页现在标题就是《Gitee Pages Pro(功能已下线)》,2024 年 5 月起停服没恢复。而且我一开始搞混了一件事:代码托管在哪,跟网站跑在哪,是两回事。
- GitHub Pages:只能放静态文件(后台、搜索、数据库全没了),而
*.github.io在国内同样常年受污染和间歇阻断。 - 其他海外免费托管(Vercel / Netlify / Pages.dev 之类):都是"平台共享域名",命运相同。
真正管用的只有一件事:把"共享域名"换成我自己的域名。于是买了 fanaoyu.cn(阿里云,首年 ¥0、次年 ¥38;.cn 必须过实名认证,否则解析不生效),Cloudflare 加站点后把 NS 改成 Cloudflare 给的两个地址,再把域名绑到 Worker——DNS 记录和 HTTPS 证书都是自动的,Cloudflare 侧不收费。
现在:正式站 https://fanaoyu.cn,沙盒 https://preview.fanaoyu.cn,旧地址保留当兜底。但我把话说全,免得别人踩:能打开 ≠ 快。免费线路是海外的,首开一般 1–3 秒,个别运营商和时段会抽。国内要秒开只有一条路——ICP 备案 + 国内主机,而这意味着 D1 / KV / R2 / Cron 全要换实现,等于重写。所以我的决定是:先用三个月,忍不了再谈备案。域名不用换,主机随时可换。
八、现在我的日常用法
pnpm dev # 本地跑一遍看效果
pnpm test # 81 个测试(会起真 Worker)
pnpm ship # 类型检查 + 测试 + 构建 + 部署正式站
git push # 最省事:GitHub Actions 自动跑回归并上线
写作本身完全在后台:https://fanaoyu.cn/admin/ 登录就写,站点标题、副标题、关于页也都在后台改(存在数据库里,不用部署)。只有存量长文的 Markdown 源文件我留在仓库的 content/ 目录,用导入脚本进库。
九、还没解决的,我摊开说
- 改密码不会立刻让已登录的旧会话失效(最长活到 7 天 token 过期)——我知道,暂时接受;
- 第一个管理员账号只能靠命令行建,后台里没有"再加一个管理员";
- 图片上传前没有压缩管线,得我自己先压;
- 只有"Markdown 导入进库",没有反向导出(真要做,我就让它写个导出脚本);
- 评论、访问统计都没做,暂时也不需要;
- jieba 分词的 wasm 交付还没做,做完才能谈搜索质量再上一层。
十、给同样想让 AI 替你做站点的人几句实在话
- 先想清楚"读者从哪打开"。我被
workers.dev卡住的时间,比写功能的时间还长,而它跟代码毫无关系。 - 别接受"它说完成了"。要一样能证伪的东西:远端数据库里那条记录、部署输出里那一行
custom domain、浏览器里真点一次的效果。 - 让它先写设计文档和任务清单,你的含糊会在文档阶段暴露,在代码阶段只会变成债务。
- 要求它把坑写进文档和测试,而不是记在对话里。对话会消失,README 和断言不会。
- 验收标准要包含"失败长什么样"。比如我要求 404 页面绝不能进缓存——否则刚发布的文章会被一次缓存的 404 卡住一分钟。这类规则只有你事先想到,才有人替你实现。
如果你在国内也遇到"我的站点别人打不开",别怀疑代码,先看域名。这是我这七天里最贵的一课。