为什么不用现成框架
看过一圈静态博客生成器(Hexo、Hugo、Astro、Next.js),总觉得太重:本地要装 Node 环境,写一篇文章要跑 build,部署一堆文件,想改个样式还要翻主题文档。
最终想要的是:
- 写文章 = 在浏览器后台粘贴 Markdown,点发布
- 访问 = 边缘节点直接吐 HTML,毫秒级
- 数据库 = 不用自己管服务器
- 图片 = 不用自己管存储
- 成本 = 趋近于零
Cloudflare 的 Workers + D1 + R2 组合刚好满足。Workers 每天 10 万次免费请求,D1 5GB 存储,R2 流量免费(不收出口费),个人博客根本用不完。
技术选型
最终方案是 Worker 手写模板渲染 + 边缘缓存,不上 SSR/SSG 框架:
- 后端:单个 Worker 脚本,处理路由、API、管理面板
- 数据库:D1(SQLite 语法),存文章、标签、分类、设置、媒体元数据
- 文件存储:R2,存上传的图片
- 前端:Worker 直接返回渲染好的 HTML,配合
Cache-Control: s-maxage让 Cloudflare CDN 缓存 - Markdown 渲染:marked,在 Worker 端编译成 HTML
这样每次请求:命中缓存就直接返回静态 HTML,没命中才查 D1 渲染——既有 SSG 的速度,又不用本地 build。
数据库表结构
建了八张表:
posts:文章(标题、slug、Markdown 正文、HTML 缓存、封面、发布时间)categories:分类tags:标签post_tags:文章-标签多对多media:媒体库(文件名、R2 key、mime、大小、上传时间)settings:站点配置(站名、副标题、SEO、自定义 CSS)reactions:点赞(文章 ID + emoji 类型 + 计数)admins:管理员账号
实现的功能
前台
- 时间轴首页,最新文章在最上面,滑到底自动加载旧文章(Intersection Observer)
- 文章独立页,带目录和 emoji 点赞
- 标签页、归档页、RSS、sitemap
- 完整 SEO:title、description、Open Graph、JSON-LD
后台
- 密码登录(HttpOnly Cookie)
- 文章编辑器(Markdown,实时预览)
- 分类标签管理
- 媒体库:上传图片到 R2,网格缩略图、复制链接、删除
- 站点设置:站名、副标题、SEO、自定义 CSS 直接覆盖
- 点赞数据查看
设计风格
最终选了锤子笔记那种纸感拟物风:
- 暖纸色背景
#f4f1ea,带细微噪点纹理 - 米色卡片
#fffdf8,14px 圆角,柔和投影 - 橙红强调色
#c8503c - 橙色圆点时间轴,虚线分隔
- 拟物胶囊点赞按钮(渐变 + 内阴影)
- 深色代码块
没有用图标库,所有装饰都是 CSS 画的。
踩过的坑
1. 缓存函数类型不匹配
一开始写了个 cacheable() 包装函数想统一加 Cache-Control 头,但内部渲染函数返回的是字符串,直接当 Response 传给缓存 API 导致首页返回空 body(Content-Length: 0)。改成先 new Response(html) 再包装就好了。
2. Wrangler 静态资源配置坑
想把 favicon 放项目根目录通过 [assets] 配置暴露,结果请求一直超时。最后改成直接在 Worker 代码里加路由,SVG 内联字符串、PNG 转 base64 解码返回——小文件这样最稳,还省一次静态资源部署。
3. R2 媒体索引
参考了一个 R2 文件管理 Worker,它把文件列表存在 R2 里一个 file-list.txt。我改成把元数据写进 D1,这样后台媒体库能直接查 SQL,按时间排序、搜索都方便,删文件时同时删 R2 object 和 D1 记录。
成本
上线跑了一周:
- Workers 请求:每天几百次,远低于免费额度
- D1:几 MB 数据
- R2:几张图,几 MB
- 月成本:0 元
想做还没做的
- 自定义域名绑定后自动更新 og:image 的绝对 URL
- 文章草稿/定时发布
- 简单的访问统计
部署在 Cloudflare 边缘网络,数据库是 D1,图片存在 R2,没有服务器,没有构建步骤。