这个网站看起来不像一个普通博客。它没有在首页铺开一组文章卡片,也没有让最新内容占据最大的视觉位置。你进入它时,看到的是一本摊开的书:封面、目录、页码、纸张、书脊,还有那些被一页一页安放进去的文章。

这不是一个单纯的视觉玩具。把博客做成书,意味着整个系统都要跟着改变:写作入口要足够轻,发布过程要足够安静; Markdown 不能只是被直接渲染出来,它要先变成适合阅读的书页;长文不能无限向下滚动,它需要在浏览器里被重新分页;代码、表格、引用和诗歌也不能像网页组件一样跳出来,它们都要服从同一张纸。

这篇文章想讲的,就是这个网站如何从一个想法长成一套系统。它的外观看起来像设计问题,但背后几乎每一个选择,都有相应的技术在支撑。而这些技术最后又会回到同一个目的:让文字有一个安静、稳定、可以慢慢长大的容器。

我不想再做一个信息流

我不是反对信息流。它是互联网最自然的组织方式之一,尤其适合新闻、社交和短内容。但当我想保存一些更慢的文字时,它就显得太急了。它总是在刷新,总是在下坠,总是在用新的东西替换旧的东西。文章被塞进时间线里,变成一张卡片、一条记录、一个可以被继续往下滑走的片段。它当然高效,也很适合消费,但我不确定它适合保存。

博客最初吸引我的地方,不是它可以多快地发布内容,而是它可以慢慢沉积。一个人写下来的东西,不一定非要排成时间线。它也可以像书架,像一本持续增补的手稿,像某种被整理过的私人档案。读者不需要被最新的一篇文章追着走,他可以从目录进入,也可以从某一页停下。

所以我想让首页本身就是一本书。翻开之后先看到目录,再进入正文;文章不再只是列表中的一项,而是书页中的一部分。滚动是无尽的,翻页有始有终。边界不是限制,它让阅读有了停顿,也让每一次进入下一页都带一点小小的仪式感。

发布一篇新文章,在这里也不再像是往时间线里塞一条卡片,而更像是往书里添上一页。这个比喻听起来有些固执,但它决定了后面所有的技术选择:写作、发布、构建、分页、封面、代码块、表格、引用,最后都要回答同一个问题:它能不能成为这本书的一部分。

从一篇备忘录到一页书

写作最好发生在最轻的地方。对我来说,很多文章不是从一个完整的编辑器里开始的,而是从手机备忘录里的一句话开始的。它可能是在路上写下来的,也可能是在睡前补上的几段。这个时候如果还要打开后台、选择分类、上传封面、填写一堆字段,写作的动作就已经被发布流程打断了。

所以移动端发布链路被设计得很短。 iOS 备忘录里的内容通过 Shortcuts 快捷指令发到服务器的 /webhook,服务器脚本解析标题和正文,然后用 GitHub API 把它写成一篇 Markdown 文件。这个过程里,服务器不是文章的最终来源,它只是一个转交者。真正的源头仍然是 GitHub 仓库。

iOS 备忘录
  → Shortcuts 快捷指令
  → POST /webhook
  → 服务器脚本解析标题与正文
  → GitHub API 写入 Markdown
  → GitHub Actions 构建静态站点
  → rsync dist/ 到 AWS Lightsail

PC 端则更直接:写好文件,git push。不论文章从手机还是电脑进入仓库,后面的路径都一样。 GitHub Actions 在 runner 上安装依赖、构建 Astro 静态站点,然后把 dist/ 同步到 Lightsail 。服务器最终承载的不是一个复杂应用,而是一份已经构建好的静态产物,另加一个很小的 webhook 服务。

这条链路真正想实现的,并不是自动化本身。它只是让“写完”尽量接近“出版”。发布过程越安静,写作就越不容易被它打断。一个博客如果想慢慢长成一本书,它首先需要让新的一页足够容易被放进去。

Markdown 只是原稿

Markdown 很适合写作,但它不是最终的书页。它更像原稿:轻、干净、可保存,也足够接近文字本身。但如果直接把原稿交给浏览器,很多排版问题就会暴露出来。公式需要渲染,图片需要尺寸,代码需要高亮,中西文之间需要一点缝隙,文章标题也不应该在目录和正文里重复出现。

所以构建阶段有一层 book-renderer.js。它先把 Markdown 里的公式交给 KaTeX 预渲染,再用 marked 转成 HTML ;代码块通过 highlight.jsmarked-highlight 加上低饱和的语法高亮;本地图片会在构建时读取真实宽高,并写入 <img> 标签;中西文之间自动插入六分空,让英文单词和中文字符不要紧贴在一起;正文开头如果重复了文章标题,也会被去掉。

这些处理看起来琐碎,但它们直接影响书页能不能稳定。图片如果没有尺寸,浏览器加载完成前后高度会跳,分页就会不准;公式如果留给客户端临时解析,书页初始化会多一层不确定;中西文如果完全贴在一起,长文读起来会变得拥挤。这里的每一步都不是为了展示技术,而是为了让原稿变成可以安放在纸面上的内容。

最后,所有文章数据会被注入首页的 #book-data。此时它们已经不是 Markdown 文件,也还不是书页,而是一组等待被分页的 HTML 内容。作者写下的是原稿,系统负责把原稿整理成书页。这个边界对我很重要:写作时不要想着布局,布局应该由书来完成。

分页不是切割,是排版

网页天然倾向于无限延伸。只要内容还在,页面就继续往下长。书页则相反,它先给出一个边界,然后要求文字在这个边界里形成节奏。这个限制很麻烦,但也正是它让阅读有了停顿、翻页和重新开始。分页不是把长文硬切成几段,而是在一页纸的边界里重新安排文字的呼吸。

这也是为什么分页没有放在构建阶段。 Node 环境可以解析 HTML ,却不是真正的浏览器排版引擎。字体加载、公式高度、图片尺寸、 CSS 继承、表格换行,这些东西最后都要落到浏览器里才知道真实高度。与其在服务端猜,不如让浏览器自己测量。

页面初始化时,paginator.js 会创建一个隐藏的测量容器。这个容器不可见,但它和真实书页使用同一份样式来源:文章样式来自 book-content.css, KaTeX 和代码高亮样式也会进入测量环境。然后引擎把段落、代码块、表格逐个放进去,读取 scrollHeight。高度合适,就继续;装不下,就进入拆分逻辑。

拆分并不是简单地截断字符串。普通段落交给 splitText,用二分搜索找到当前页能容纳的最大字符位置;代码块交给 splitPre,按行拆开,避免一行代码被切断;表格交给 splitTable,按 <tr> 边界分页,并在下一页保留表头。列表、引用、嵌套结构也会被尽量整理成可以跨页的形态。

目录页和正文页还有两套页码。目录使用罗马数字,正文从 1 开始。 Turn.js 需要的是物理页码,也就是第几张翻页;读者看到的是显示页码,也就是印在页脚的阅读秩序。算法在这里做了很多事,但它最终服务的不是算法本身,而是一页纸的尊严。读者看到的不是被随便挤出来的 HTML ,而是刚好在这一页停下来的文字。

纸张先于文字

拿到一本书,最先抵达人的往往不是内容,而是纸张。颜色、光线、厚度、翻页时露出的边缘,这些东西会先于句子建立一种信任。网页通常忽略这一层:白底、黑字、列表、卡片,内容当然可以被读到,但它很难让人相信自己正在面对一件有材料感的东西。

这个博客的纸面不是纯白。它更接近冷灰的象牙色,有轻微的渐变和纹理,也有上下边缘的压暗。书脊附近的阴影很淡,像纸页装订处自然形成的压痕。文字颜色也从纯黑退到暖褐,不是为了降低清晰度,而是为了减少刺眼。纸色并不只是背景,它决定了整本书的气场。

封面也是同一件事。它不是一张简单的背景图,而是一张手工拼接的精灵图。前封、内封、封底从同一张图里裁取,background-position 差一两个像素就会露出缝隙。 Turn.js 负责硬壳、书口、翻页和折角, CSS 负责把布料贴到正确的位置上。那几毫米的调整很细碎,但最后决定了它像不像一本真的书。

材质感不是装饰。它不会直接告诉读者“这篇文章很好”,但它会决定读者是否愿意慢下来。纸张先于文字,是因为阅读不只发生在眼睛里,也发生在身体对一个界面的信任里。

代码也要服从纸面

技术博客很容易长得像控制台。代码块是深色卡片,表格是数据面板,引用是醒目的组件,所有东西都在提醒读者:这里是网页。可如果首页已经是一册书,这些元素就不能再各自为政。代码、表格、引用和诗歌不是网页组件,它们是书页上的文字,也应该接受同一套排版秩序。

所以代码块被尽量去卡片化。它不再用大面积背景和圆角把自己托起来,而是只留下细线、留白和低饱和的语法高亮。行内代码也没有做成胶囊,只用很轻的底色和边界标记出来。滚动条仍然存在,但被压得很细:不是没有,只是不想让你注意到它。

表格采用更接近书籍和论文里的三线表:顶部一条线,表头下一条线,底部一条线。它的目的不是装饰数据,而是让行列关系在纸面上保持安静。引用也类似,它不应该像社交网络里的转帖卡片,而应该像正文里的另一种声调。字体、细线和留白足够把它从正文里分出来,不需要更多戏剧化的边框。

诗歌则需要另一种留白。带换行的段落会被识别成诗行段落,取消首行缩进,整体略微向右,让每一行仍然左对齐。它没有被居中摆成装饰品,因为左对齐的参差才是诗的肩膀。这里的技术只是很小的一点 CSS ,但它保护的是诗行自己的站姿。

这些细节都在做同一件事:把不同类型的内容重新带回纸面。一个技术博客不必长得像控制台,它也可以长得像书。技术内容不需要被削弱,但它需要被排版驯服。

克制是一种架构选择

很多东西都可以继续加。暗色模式、复杂动画、实时搜索、后台管理、更多交互、更精细的主题切换,它们都不是不能做。只是这本书不一定需要。一个界面越想证明自己能做很多事,就越容易把注意力从文字上拿走。

所以这个博客的架构也尽量克制。 Astro 负责静态构建, GitHub 是内容源头,服务器只承载静态文件和 webhook 服务。每日一句在定时任务里更新,文章通过仓库流转,浏览器运行时主要负责分页和翻页。除了这些必要动作,系统不试图把首页变成一个复杂的客户端应用。

体验上的克制也是如此。没有多余的动画,没有为了互动而互动的效果,也没有不断吸引注意力的模块。翻页已经足够,纸张已经足够,文字本身也应该足够。网站需要做的不是一直发声,而是在读者愿意停下来的时候,保持安静。

这个网站不是为了展示我能做多少东西,而是为了给写下来的东西一个稳定的容器。它会随着文章变厚,随着目录变长,随着一次次发布慢慢长成另一本形态。也许这仍然只是一个博客,但我希望它更像一本在浏览器里诞生的书。

好排版是看不见的。你看不到它的时候,它在工作。