Caveman:极简静态博客系统,用Markdown与Git掌控数据主权 看到 caveman 这个词你脑海里可能浮现出一个披着兽皮、拿着石斧的原始人。在程序员圈子里这个词其实有两层含义一层是调侃式的“返祖调试法”caveman debugging指用最原始的 print 大法排查问题另一层就是我今天想聊的东西——一个把“返祖”当成设计哲学来做的极简博客系统 Caveman。我第一次看到这个项目时第一反应是“这名字可真敢起”。用到现在大半年我反而觉得没有比这更贴切的名字了。Caveman 的核心思路极其简单文章就是一坨 Markdown 文件站点数据靠 Git 管理构建时把所有文件渲染成静态 HTML最后扔到任何能挂静态页面的地方就算发布。没有数据库没有后台管理界面没有插件市场不用买云主机跑服务也不需要记住一堆运维命令。这篇分享适合谁看如果你已经厌倦了花一晚上调主题、装插件、配数据库到头来一个字都没写的建站过程如果你喜欢用命令行、对纯文本和 Git 有天然好感如果你对“写了十年的博客数据到底在谁手里”这件事有焦虑——那 Caveman 这套思路值得你花十分钟了解一下。对于刚接触静态站点生成器的同学我也尽量把底层原理讲透方便你理解其他同类工具。1. 项目概述——为什么有人想做“穴居人”博客1.1 它到底是个什么东西Caveman 不是一个靠堆功能取胜的项目恰恰相反它以“砍功能”为荣。我把它理解成一个带生成器的纯文本发布工具你按照规定目录放 Markdown它负责变成网站。整个过程中没有谁能“锁死”你的数据。文章在本地备份在 Git 仓库生成出来的静态页面可以随便搬家。举个例子我现在的博客目录结构大概长这样mysite/ ├── config.toml # 站点配置 ├── content/ # 所有文章都在这 │ ├── _index.md # 首页/归档说明 │ ├── hello-world.md # 一篇文章 │ └── tech/ │ └── git-basics.md ├── templates/ # 页面模板 └── public/ # 构建产物发布时只传这个文章就是一个.md文件文件开头用一段 front matter 写标题、日期、标签。构建的时候Caveman 读取这些信息套模板渲染出 HTML。整个体系拆成三块内容层文件、版本层Git、表现层模板和生成的 HTML三层互不干扰。对比一下就更好理解了。WordPress 是动态路线服务器上跑着 PHP 和数据库每次访问页面都要现查数据库、现渲染后台还能在线编辑。Hexo、Hugo 这些静态生成器跟 Caveman 是一个路线但发展到现在主题生态、配置系统已经相当庞大想玩明白也得花一阵子。Caveman 站在“最简单”这一端甚至把“主题”这个概念也砍到近乎没有默认模板丑得很有个性。1.2 为什么“返祖”反而成了优势用个生活类比。现在很多人用手机拍照拍完传网盘、传社交平台、传各种相册 App。结果每次换手机最头疼的就是把照片搬过去有的平台还得下载客户端、联网备份。而一个老摄影师的做法可能是拍完存到存储卡定期拷进硬盘再按年月编文件夹。整个过程笨照片却永远是自己的。Caveman 的思路就是那个老摄影师的文件夹。它的“返祖”体现在四个具体方面数据主权所有内容都是本地文件平台跑路、服务涨价、模板过时都跟你没关系。无锁定性想迁移把content目录拷走就行。Markdown 是通用格式什么工具都能读。极低维护成本没有数据库要备份没有运行时环境要升级没有安全补丁要打。天然适合学习整个系统就那么几个概念搞懂它等于搞懂了静态站点的通用原理。这一点对我这种“三天两头想折腾”的人来说特别重要。以前用动态博客总有种“欠着一屁股技术债”的感觉PHP 版本旧了不行、数据库大了要优化、插件冲突了要排查。现在用 Caveman这些东西全消失了因为我压根没给自己留折腾的空间。1.3 适合谁用、不适合谁用我身边用下来觉得舒服的人基本符合这样的画像会用 Git平时写东西用 Markdown对“在线编辑”没有执念愿意在命令行里敲两三条命令。说白了就是开发者、技术写作者或者是从早期博客时代走过来、习惯本地写作的人。不适合的情况也要说清楚。如果你需要一个所见即所得的编辑器点几下按钮就换主题用手机 App 随手发一段文字那 Caveman 这类静态站点工具会让你抓狂。它没有后台、没有在线编辑器、没有内容管理系统一切都得在本地完成。这不是缺点是定位问题。用错场景再好的工具也是折磨。2. 核心设计思路与底层原理2.1 内容存储纯文本的可靠性Caveman 把“文章是纯文本文件”这件事贯彻得非常彻底。这个决策看起来不起眼实际上是整个系统最关键的基石。纯文本最大的好处是可读、可 diff、可 merge。你打开一个 Markdown 文件看到的就是文章内容本身没有一堆二进制格式裹在里面。Word 文档就不是这样它的排版信息、字体设置、修订记录都埋在复杂的文件结构里你没法用文本编辑器直接看内容更没法用git diff精确看某个人改了哪一行。笔记软件也类似底层存储往往是私有格式或者专门的数据库文件离开了那个 App 就寸步难行。Markdown 本身恰好站在一个好位置既保留了纯文本的朴素又有轻量标记能力。写# 标题、**加粗**、代码上手十分钟就会渲染出来也够用。纯文本还顺手解决了“内容与样式分离”的问题。文章文件里只存放内容本身和少量语义标记页面长什么样由模板决定。想换皮肤风格的时候改模板就行完全不用碰文章文件。我以前用某些博客平台换个主题经常需要手工改文章里的内嵌样式改完一百篇旧文章全是历史遗留的样式垃圾那种痛用过的人才知道。2.2 版本管理Git 就是数据库传统 CMS 的数据库里有文章表、用户表、配置表想备份还得专门的工具导出。Caveman 这套方案里Git 充当了数据库的角色而且是一个“自带时光机”的数据库。Git 的第一个价值是历史记录。我删掉一篇文章、改坏一段文字、或者批量替换出了差错随时可以git log看提交历史用git revert或git checkout找回旧版。在数据库方案里要找回误删的文章往往得翻备份而备份可能是三天前的。Git 让“反悔”变成了几秒钟的事。第二个价值是分支即工作流。我习惯这样草稿放在draft分支写完了再把分支合并进main。想写系列文章就开一个series-xxx分支写到一半不想发了把分支删了就行完全不影响已发布内容。还可以用 Git tag 给文章做快照比如“v2025 版本的文章集”以后想整体回溯某个时点的博客状态很方便。第三是分布式备份。Git 仓库在本地一份推到 Gitee、GitHub 或者自己的服务器上又是一份一个 git 仓库天然就是一套异地备份方案。我再也不用担心服务器挂了文章全没了——服务器上那份只是发布产物真正的资产在 Git 仓库里而 Git 仓库我可以推无数个远程。对比一下就很直观对比维度数据库方案动态博客Git 方案Caveman内容存储数据库表私有格式纯文本文件通用格式备份需要额外工具推送远程仓库即备份版本回滚通常依赖备份时间点Git 每次提交都是时间点多设备协作在线编辑器为主clone / pull / push数据迁移导出格式混乱复制目录即可2.3 发布机制静态生成的威力Caveman 的发布链路简单到有点像“伪需求”你写的是 Markdown最终访问者拿到的是纯 HTML/CSS/JS。中间那一层“构建”是全自动的。构建过程大致分四步扫描content目录下所有 Markdown 文件。解析每篇文章开头的 front matter拿到标题、日期、标签等元信息。把正文从 Markdown 渲染成 HTML填入模板对应位置。按配置的输出目录生成完整站点默认是public文件夹。关键细节在“增量构建”。如果只有一篇文章更新了Caveman 只重新渲染那一个文件而不是把全站几百篇文章全部重新生成一遍。所以即便写了几年构建时间依然能维持在毫秒级。我自己的博客两百多篇文章caveman build跑完基本是一眨眼的功夫体感比很多用了动态缓存的系统还要快。静态产物带来的好处是碾压性的。它只是一堆普通文件你可以扔到 Nginx、Apache、对象存储、GitHub Pages、Vercel甚至一个 U 盘里任何能提供静态 HTTP 服务的地方都能跑。没有服务端进程在等待请求没有数据库连接要维护攻击面几乎为零。这个“没有运行时”的属性才是 Caveman 这类系统最值钱的地方。3. 实操从零跑起一套 Caveman 博客3.1 环境准备与安装在你动手之前先确认手头有这几样东西Git版本管理必须一个趁手的 Markdown 编辑器VS Code、Vim、Typora 都行一个能放静态文件的地方本地先跑通也行发布以后再考虑Caveman 本身是命令行工具安装看个人喜好。以源码方式为例把项目仓库克隆到本地在项目目录里执行构建命令生成可执行文件然后把它放到 PATH 环境变量里。装完之后验证一下caveman --version能正常输出版本号就说明环境OK了。这一步我特意提一下是因为很多人卡在“装好了但命令找不到”上多半是没把安装目录放进 PATHWindows 用户尤其常见。装好后不需要额外的运行时依赖这本身就是 Caveman 省心的体现。3.2 初始化站点与基础配置初始化一个站点非常简单caveman init mysite cd mysite执行完你会得到一个上面那种目录结构此时已经有一个能本地运行的最小站点。打开config.toml大概长这样title 我的穴居博客 author 张三 language zh-CN theme default buildDir public contentDir content每个字段作用都很直白title是站点名author是作者名contentDir和buildDir是刚才说的目录。这里有两个实战建议。第一把构建目录加到 .gitignore。public是生成产物不应该进 Git 历史。如果你的主题或模板文件也能重新生成同样不要纳入版本控制保持仓库里只有“原材料”。第二先在本地跑通再想发布的事。执行caveman serve它会在本地起一个开发服务器并监听文件变化。你改文章、保存浏览器立刻刷新这个即时反馈对写作体验非常关键。等文章写完觉得满意了再执行caveman build生成产物。3.3 创作流程与发布链路新建一篇文章实际上就是在content目录下新建一个.md文件。文件命名我习惯用英文短横线连接比如my-first-post.md这样生成的 URL 也干净。文章开头的 front matter 长这样--- title: 我的第一篇博客 date: 2025-01-20 tags: [随笔, 技术] --- 这里是正文用 Markdown 写就行。注意date字段一定要写准确。Caveman 构建时按这个日期给文章排序而不是看文件修改时间。很多人在这一步埋了坑从旧博客迁移文章时文件修改时间全变成了当天不写date的老文章全部跑到了博客最前面排序一团糟。日常写作流程在我这里是这样的1. git pull # 先把远程仓库的最新状态拉下来 2. 写新文章.md # 在 content 里创建文件写正文 3. caveman serve # 本地预览确认排版没问题 4. git add commit # 保存当前版本 5. caveman build # 生成静态页面 6. git push # 推代码顺便触发服务器自动构建第 6 步很多人会直接跳过改成手动把public目录上传到服务器。早期我也是这么干的用 rsync 同步rsync -avz --delete public/ userserver:/var/www/mysite/后来实在嫌麻烦改成了服务端 Git 钩子自动发布具体细节放到最后一节讲。简单说就是本地git push服务器那边收到代码后自动构建并更新网站。整个发布动作被压缩成一个词push。4. 常见问题与排查实录4.1 日期排序错乱症状新写的文章跑到博客列表最下面或者从旧系统迁移过来的历史文章全部挤到首页。原因绝大多数情况下是排序用了文件的修改时间mtime。文件一旦被复制、迁移、解压mtime 就会变成操作时间。比如你从 WordPress 导出的文章在服务器上重新生成了一遍文件每篇文章的 mtime 都变成了导出当天。解决确保排序只看 front matter 里的date字段。我给存量文章批量补date用的是一个小脚本从文件名里提取日期比如2023-06-01-old-post.md然后写进 front matter。补完之后再构建顺序就全对了。避坑写脚本批量改文件之前先git commit存个档。批量操作万一写错规则随时能回滚。4.2 图片资源路径断裂症状本地预览文章时图片显示正常发布到线上之后图片全部 404。原因最典型的是路径写死了。有人会在文章里写/assets/xxx.png这种绝对路径本地开发服务器把站点的根当作/所以能加载出来线上如果部署在子路径或者用了 CDN 前缀这个绝对路径就指向了错误的位置。解决统一用相对路径。Caveman 构建时会把assets目录原样复制到public文章里直接写assets/xxx.png相对于文章所在 URL 去算。内容目录层级深的话稍微注意一下相对关系或者统一配置站点的baseURL让构建工具处理前缀。避坑图片文件名最好别用中文和空格老服务器和 Windows 环境容易出幺蛾子。我习惯统一小写英文加连字符比如server-rack-diagram.png。4.3 多设备协作的 Git 冲突症状在电脑 A 写完文章没 push到电脑 B 上又改了一篇两边同时在写同一个文件push 的时候提示冲突。原因Git 只会合并互不干扰的改动。同一行的内容被两边都改过Git 无法判断哪个是对的只能停下来让人处理。解决定几个简单规矩比任何工具都好使。同一篇文章尽量在同一台设备上改完再换设备别两头开工。写新文章之前先git pull写完马上git push减少本地滞留时间。批量移动或重命名文件时单独提交不要跟内容修改混在一起。真遇到冲突也不用慌。Git 会把冲突标记写在文件里和之间的部分就是两边各自的内容。用 VS Code 的合并编辑器点几下就能解决命令行里也可以用git mergetool调外部合并工具。我自己的经验是文章草稿阶段的冲突很好解决都是文字层面的差异看一遍就知道哪个是哪个。4.4 自动化部署的那些坑我目前的发布方式是“服务器收代码、自动构建、自动上线”用 Git 的 post-receive 钩子实现的。逻辑不复杂服务器上有个裸仓库收到 push 之后把代码检出到工作目录执行构建命令再把产物同步到网站目录。这里有几个我踩过的坑。第一个是钩子脚本的目录问题。post-receive 钩子执行时当前目录在裸仓库里不是工作目录。脚本里所有相对路径都要小心要么写绝对路径要么在脚本开头先cd到一个固定目录。我第一次就栽在这上面脚本日志里全是 “No such file or directory”。第二个是构建环境的权限问题。服务器上用哪个用户跑钩子这个用户就得有写入工作目录和网站目录的权限。很多人的权限冲突表现为“push 成功了但网站没更新”其实是构建脚本运行时报错被吞掉了。建议在钩子脚本里把日志写到一个固定文件排查的时候先看日志。#!/bin/bash # 服务器裸仓库的 hooks/post-receive LOG/var/log/caveman-deploy.log echo --- deploy start $(date) --- $LOG export PATH/usr/local/bin:$PATH cd /srv/mysite git --git-dir/srv/mysite/.git pull origin main $LOG 21 caveman build $LOG 21 rsync -avz --delete public/ /var/www/html/mysite/ $LOG 21 echo --- deploy end --- $LOG第三个是Caveman 在服务器上没装。构建步骤要在服务器上跑那就意味着服务器上也得有 Caveman 可执行文件。很多人本地构建正常push 到服务器发现网站没变化查半天发现服务器根本没装这个工具。如果不想碰服务器还有更省事的办法用 GitHub Actions 这类 CI 平台推送的时候自动跑构建再把产物传到托管平台。整个流程变成一个“推送即发布”的自动管线本地只要负责写和 push。最后再分享一点个人经验用 Caveman 写了大半年博客之后我最深的体会不是“技术变简单了”而是“写作这件事被重新聚焦了”。以前折腾 WordPress 的时候每个周末都在换主题、调样式、装插件真正动笔写的时间少得可怜。现在整个系统摆在那里想折腾都没什么可折腾的反而每天都能写上几百字。如果你已经被各类建站工具的复杂度劝退过我建议你先别急着搭完整博客就按 Caveman 的流程在本地试一下建一个文件夹写三篇 Markdown跑一次构建看看那堆静态文件被生成出来的一瞬间会找回一种朴素的快乐。数据在自己手里、版本自己掌控、发布一条命令这种踏实感正是“穴居人”式的可靠。