先说我为什么突然想写这个。最近在折腾一个小站点需求就一句话把 Markdown 文件变成能发的静态网页。我第一反应是上 Hugo毕竟生态成熟、主题多但转念一想为了一个二十几篇文章的博客去装一套 Go 工具链、配一堆主题参数实在有点杀鸡用牛刀。翻了一圈想起这个叫caveman的小工具——用 Go 写的极简静态网站生成器核心卖点是零依赖、单文件、纯静态。我花了半小时把旧博客迁了过去体验相当舒服。这篇就把我实际用下来的流程、踩过的坑和关键设计思路整理一遍给想入坑极简静态站的人一个参考。caveman 解决的是很具体的问题你只需要一个“把内容变成网页”的工具不需要复杂的插件体系、不需要 Node 依赖树、不需要数据库更不需要在本地装一套完整开发环境。它适合三类人第一个是写博客但不想折腾前端的人第二个是追求页面打开速度和部署成本的个人站长第三个是想要一份干净、可长期维护的静态站源码的人。如果你是这三类中的一类下面这些内容应该对你有用。1. 项目整体设计与思路拆解1.1 名字背后的极简哲学caveman 翻译过来就是“穴居人”这个名字本身就暗示了工具的设计取向回到最原始、最直接的方式去建站。现代静态站生成器一个比一个功能多Tags、Taxonomy、分页、多语言、短代码……功能全的同时也意味着概念多、文档厚、升级时容易出兼容问题。caveman 相反它只做一件事读取content目录下的 Markdown 文件套上模板输出成dist目录下的静态页面。这种“原始人”式的减法是有意的。我自己折腾过不少生成器最后发现 90% 的站点其实只用到了 10% 的功能剩下的都是“有备无患”的心理安慰。caveman 把这种心理负担直接砍掉了你不需要学习它的全部文档——因为它的文档本身就短到可以一口气看完。对一个内容型小站来说工具越简单你写内容的阻力就越小。1.2 它和主流生成器的本质区别拿 Hugo 来对比可能更直观。Hugo 的定位是“全能型静态站生成器”有几十种输出格式、内置图片处理、多语言、模块系统配置文件的写法复杂到能写一本书。Gatsby 就更不用说了引入 GraphQL、数据层、插件机制一个空站点启动也能拖出一堆依赖。caveman 的定位完全不同维度cavemanHugoGatsby运行方式单文件二进制单文件二进制Node.js 应用依赖数量零零编译依赖但主题依赖分散几百个 npm 包起步内容格式MarkdownMarkdown 多种格式Markdown/JSX模板语言Go 标准模板Go 模板增强版React构建速度毫秒级毫秒级到秒级秒级到分钟级学习成本很低中等较高适合场景个人博客、文档站中大型内容站需要动态交互的站点看上表就明白了caveman 不是要去替代 Hugo而是填补一个空白当你想快速做一个内容稳定的站点、又不想陷入框架学习成本时它就是最顺手的选择。它把静态站的路走得很窄但走得很扎实。1.3 适合场景与不适合场景我用下来觉得 these 场景特别合适个人博客/技术笔记内容以文字为主不需要复杂交互。项目文档/手册层级不深页面数量几十到几百篇。个人主页/产品落地页页面不多重点在内容和加载速度。内部工具说明页不想引入团队学习成本能跑就行。不太建议的场景也有几个如果你的网站有几十种内容类型、需要复杂分类系统、要动态渲染用户相关数据或者你依赖第三方插件生态那用 caveman 会把自己逼疯。它刻意不做插件机制任何扩展都得依赖 Go 模板能力和外部工具配合。这不算缺陷而是工具定位——就像你不会拿一把瑞士军刀去砍树一样。2. 上手实操安装与第一个站点2.1 安装最快路径是直接下二进制caveman 的安装方式有两种。如果你本机有 Go 环境可以直接用命令行安装go install github.com/jesseduffield/cavemanlatest没有 Go 环境也别急可以在项目的 GitHub releases 页面下载对应平台的预编译二进制放在任意目录下配好环境变量就能用。Linux 和 macOS 上还可以用包管理器安装但不同系统命令不同建议以官方文档为准。我推荐直接在 releases 下载原因有二一是快不用拉源码编译二是便于确定版本避免latest拉到一个你自己都没听说过的新版本。安装完记得验证一下caveman --help如果能正常打印出 help 信息说明安装成功。我在 macOS 上遇到过权限不足导致没法执行的情况解决方法也简单对下载的二进制执行chmod x然后放到/usr/local/bin下。2.2 初始化项目结构找一个空目录执行caveman new mysite cd mysite执行后生成的目录结构大概长这样mysite/ ├── config.toml # 站点配置文件 ├── content/ # 存放 Markdown 源文件 │ └── index.md # 首页 ├── layout/ # 模板文件默认主题也会放这 └── public/ # 构建输出目录生成后出现第一次看到这么干净的结构有点不习惯但仔细想想一个静态站本来就只有“内容”和“模板”两件事其余都是中间产物。这个结构的设计意图是让每个文件夹的职责完全单一content里只写内容layout里只调排版public只是构建结果不进版本库也不需要手动维护。2.3 配置 config.toml最关键的几个字段初始的config.toml内容很短核心字段如下title 我的极简站点 author 你的名字 description 一句话介绍你的站点 language zh-CN [params] subtitle 这是副标题一般显示在首页顶部 keywords [静态站, caveman, 博客]title会出现在浏览器标签页和页面顶部author会在文章页显示description特别重要因为很多模板默认会在 HTML 的meta namedescription里插入这个字段直接影响搜索引擎对站点的摘要展示。language建议写成完整语言代码比如中文站写zh-CN不然有些浏览器渲染标题和日期时会用默认语言。我自己还习惯在[params]里加几个自定义字段比如备案号、版权信息、社交链接等因为 Go 模板里可以通过.Site.Params.xxx读取这些值方便在页脚统一渲染。2.4 构建与本地预览新建站点后在根目录执行caveman build这时public目录会出现一堆 HTML 文件。想本地预览执行caveman serve默认会在http://localhost:8080起一个本地服务支持自动刷新。我实测过改完 Markdown 保存浏览器几乎瞬时更新整个链路非常轻快。有一点要注意caveman serve起的是“实时重构建的服务”它会在文件变化时自动重新生成内容所以调试时不需要反复手动执行build。但切到生产环境时请务必用caveman build生成最终文件再部署public目录不要直接拿内建服务当线上服务器用。3. 核心功能实现细节从 Markdown 到完整页面的过程3.1 内容组织与 frontmatter 写法caveman 读取content目录下所有.md文件按目录层级生成对应路径的页面。比如content/posts/hello.md构建后对应/posts/hello/index.html或/posts/hello.html。每篇文章的头部需要一段---包裹的 frontmatter用 TOML 格式声明页面元信息我常用的字段是这些--- title 你好caveman date 2025-01-15 tags [博客, 工具] description 这篇文章介绍 caveman 的基本用法。 draft false ---title和date会决定页面标题和发布时间排序tags如果模板支持会渲染成标签链接。这里要特别注意date的格式最好写成2006-01-02这种完整日期如果你直接写1月15日这种中文字符串构建时日期解析会失败布局里取不到正确时间。Caveman 遵循的标准是 CommonMark也就是最基础、最通用的 Markdown 规范它不默认支持脚注、自定义容器这类扩展语法。如果你的旧文章里用了很多这类扩展语法迁过来时可能得手动改成分段就是了。这不是 bug是设计取舍优先保证解析逻辑简单、输出结果可预测。3.2 模板语言其实就是 Go 标准模板很多人听到“Go 模板”就紧张实际上 caveman 用的是 Go 标准库自带的text/template语法量很克制。最常用的那么几种!DOCTYPE html html lang{{ if .Site.Language }}{{ .Site.Language }}{{ else }}zh-CN{{ end }} head meta charsetutf-8 title{{ .Title }} · {{ .Site.Title }}/title meta namedescription content{{ with .Description }}{{ . }}{{ else }}{{ .Site.Description }}{{ end }} /head body {{ range .Pages }} a href{{ .URL }}{{ .Title }}/a {{ end }} article {{ .Content }} /article /body /html理解这套模板有个关键点不同页面拿到的数据结构不同。首页拿到的通常是一站点的整体信息比如.Site.Title、.Pages这类站点级数据文章页则可以取到.Title、.Date、.Content、.Tags这些页面级字段。拿不到数据的情况是最常见的报错来源我后面问题排查一节会细讲。如果你完全不想碰模板直接删除layout目录自己写的样式文件caveman 内置的默认主题也能用白色背景、简洁排版、代码高亮都有。我试过默认主题直接建站出来的页面干净得像一张白纸非常符合极简定位。3.3 RSS 与分类外小内大的能力边界caveman 虽然没有复杂的分类系统但 RSS 这类常规能力是覆盖到的。默认模板里包含一个rss.xml模板构建时会遍历content里的文章在public下生成一份完整的 RSS 文件。你只需要在页面模板的head里加上link relalternate typeapplication/rssxml href{{ .Site.BaseURL }}/rss.xml title{{ .Site.Title }}之后就能用各种 RSS 阅读器订阅了。模板里也用得上简单分类逻辑比如在首页把文章按tags分组{{ range .Site.Tags }} h3{{ .Name }}/h3 {{ range .Pages }} pa href{{ .URL }}{{ .Title }}/a/p {{ end }} {{ end }}Site.Tags是构建时聚合出的站点级数据结构不需要你手动维护一个“标签列表”文件。这一点设计得很熨帖它没有把分类做成一等公民但给了足够的原始数据剩下的事你可以在模板里自己控制。3.4 构建输出逻辑每个页面都是独立 HTMLcaveman build的输出规则并不神秘content下的每个.md文件生成一个对应路径的 HTML 文件全新生成到public不会做增量缓存。这意味着每次构建都相当于“全量重做”好处是不会出现旧文件残留坏处是如果你有几千篇文章理论上会比支持增量的工具慢一点。但我在一百篇左右的内容量上实测整个构建过程依然在几十毫秒到一两百毫秒之间完全感觉不到延迟。还有一个隐藏特性值得说caveman 会为页面生成.html和/index.html两种路径的处理方式目录形式的链接比如/posts/hello/更利于 URL 的可读性。如果你迁移老站需要注意旧链接的结构必要时在服务器端做 301 重定向别把搜索引擎已经收录的 URL 直接丢掉。4. 部署与自动化让静态站跑起来4.1 最省事的部署方式任意静态托管平台因为构建结果就是纯 HTML/CSS/JS/图片caveman 生成的站点可以扔到任何能托管静态文件的平台。我用过的几种都验证过对象存储/静态托管直接把public目录上传到类似腾讯云 COS、阿里云 OSS 的静态网站托管能力上配好 Bucket 和访问权限就能出站。平台内置的自动构建很多代码托管平台支持从仓库拉代码后自动执行构建命令指定构建命令为caveman build、产物目录为public推送代码到主干分支就会自动重新生成并发布。传统虚拟主机把public目录整体上传到网站的根目录即可不需要任何后端环境。如果你用平台自动构建记得在项目根目录放一个说明文件告诉平台如何装 caveman。以常见 CI 环境为例构建脚本可以写# 下载 caveman 二进制 curl -L -o /usr/local/bin/caveman https://github.com/jesseduffield/caveman/releases/download/v0.10.0/caveman-linux-amd64 chmod x /usr/local/bin/caveman # 执行构建 caveman build不同平台装组件的方式略有差别但思路完全一致先保证caveman命令存在再执行build最后部署产物目录。4.2 本地脚本增量与检查的搭档虽然caveman build是全量构建但本地我一般会写一个简单的build.sh脚本来做三件事清理旧产物、重新构建、检查页面数量。#!/bin/bash set -e rm -rf public caveman build echo 页面数量: $(find public -name *.html | wc -l)这个脚本的意义在于把“构建”这个动作变成一个可重复执行的流程。当你换了台电脑、隔了几个月再回来时一条./build.sh就搞定所有事不用回忆自己当初是怎么构建的。4.3 资源引用与性能优化静态站性能的核心瓶颈通常不在构建工具而在页面加载的资源体积。caveman 本身不处理图片压缩、CSS/JS 合并这些事所以我的建议是图片统一走外部图床或对象存储内容里引用绝对 URL不要把大图塞进content目录。CSS 尽量手写精简版一个几百 KB 的博客没必要引一个 100KB 的 UI 框架。代码高亮用轻量方案如果模板默认支持 highlight.js就只引它一个脚本不要顺带引一个完整主题包。实测下来一个只用默认主题、手工优化过图片的 caveman 站点首屏加载经常能控制在几十 KB 级别这带给用户的体验是任何重型框架都比不上的。5. 常见问题与排查技巧实录5.1 模板里取不到数据先看页面类型我遇到过的最频繁的问题是在自定义模板里写了一段{{ .Pages }}结果首页正常、文章页直接空白甚至构建报错。原因在于.Pages是站点首页才有的聚合字段文章页的数据结构里根本没有这个键。排查思路就一句话先想清楚“当前这个模板会被哪些页面使用”再去查对应页面有没有那个字段。首页模板能用的字段不一定会出现在文章页文章页能用的.Tags也不一定在首页出现。如果确实需要跨页面共享数据就把它放到config.toml的[params]区域用.Site.Params.xxx去读取。5.2 中文路径和文件名兼容性一开始我把文章命名为《使用手册-最终版-2025.md》这样带着中文、空格、特殊字符的文件名构建出来以后页面 URL 变成一串转码后的字符浏览器里看着非常别扭。后来统一改成简短英文路径只在 frontmatter 里写中文标题问题彻底解决。经验是content下的目录名和文件名尽量只用小写英文、数字和短横线。页面显示的中文标题靠 frontmatter 的title字段控制跟文件名没有关系。这样生成的 URL 干净好记也避免某些环境对非 ASCII 路径的处理不一致。5.3 构建成功但页面白屏检查资源相对路径默认主题里资源引用写的是相对路径比如../static/css/style.css之类的写法。如果你的站点部署在子目录比如https://example.com/blog/相对路径可能出错导致 CSS 加载不到、页面看起来像“白屏”。排查方法很简单打开页面源码看link或script引用的地址是不是 404。如果确实 404要么把链接改成绝对路径从https://写全要么调整 base URL 配置。我最推荐的方式是在模板里用 caveman 提供的站点基础 URL 变量去拼资源地址这样部署在域名根目录或子目录都不会出错。5.4 本地预览没更新可能是缓存魔咒如果你明显改了内容浏览器却还显示旧页面先别怪 caveman一般不是它的问题。优先检查浏览器缓存。静态资源尤其是 CSS浏览器会缓存得很顽固。有两个办法一是在本地预览时打开开发者工具并勾选“Disable cache”二是给 CSS 引用加上版本号参数比如style.css?v2强制刷新缓存。另外还有一个属于生成器本身的小坑如果 Markdown 文件没有被正确保存成 UTF-8 编码页面上的中文会变成乱码。最好在编辑器里统一设置 UTF-8避免出现本地显示正常、部署到线上乱码的怪问题。5.5 迁移旧站内容时容易忽略的三件事从其他生成器迁到 caveman我踩过几个跟生成器本身无关、但特别耽误时间的坑旧文章的短代码Hugo 的{{ figure srcxx }}这类写法在 caveman 里不会生效会原样渲染成文本。迁移时得手动改成标准 Markdown。日期格式差异有些生成器的日期字段是 ISO 字符串带时区caveman 更吃标准日期。不一致时请在原文件里统一格式。图片相对路径老站如果用类似../../images/foo.png的写法目录结构一变就容易裂。建议全部改成基于站点根目录的绝对路径引用。6. 实操中沉淀下来的几个小技巧6.1 给页面模板画“草稿”再写代码我一上来直接改模板结果频繁被 Go 模板语法的小细节绊倒。后来换了个做法先在纸上画出首页、列表页、文章页大概的长相标注清楚每块内容是从哪个数据来的再动手写模板代码。这样一来写{{ .Title }}的时候心里有数不会为了“把标题和大纲对上”而盲目瞎试。以后复制模板到新站点也非常顺利。6.2 用Makefile封装常用命令除了build.sh我还会在项目根目录放一个Makefile.PHONY: serve build deploy clean serve: caveman serve build: caveman build deploy: build rsync -avz --delete public/ useryour-server:/var/www/html/ clean: rm -rf public这样每天写文章时只需要make serve发版时make deploy不需要记命令参数。这个习惯花不了几分钟但能防止你半年后回来对自己的项目感到陌生。6.3 内容多的站点建议用 Git 管理分支caveman 的整个项目只有几个文本文件和一个产物目录非常适合用 Git 管理。我个人的做法是content、layout、config.toml正常提交public目录写进.gitignore不提交等 CI 或本地构建时再生成。这样仓库体积小、合并干净也方便在“草稿分支”和“发布分支”之间切换。就我自己的体验来说caveman 最好用的地方不是某个功能特别强大而是它让你重新找回“建站不该这么复杂”的感觉。如果你跟我一样主要诉求就是安静写字、打开页面足够快那它绝对值得一试。玩得开心。