← 全部文章

搜索、评论、统计:三个可以先不做的功能

上线清单上的三件标配,我一件都没装。不是拖延,是逐个算过成本之后发现,它们的收益在「五篇文章」这个规模上并不成立。

一只特立独行的熊猫 · · 约 7 分钟读完 · · #web #writing

每个讲「怎么搭博客」的教程,都会给出一份上线清单。搜索、评论、统计,三件套,缺一个就显得业余。

我把这三样都放进了待办,然后一件都没做。

不是拖延。是我逐个算了一遍账,发现它们的收益在我这个规模上并不成立。

搜索

搜索解决的是一个很具体的问题:读者知道自己在找什么,但不知道它在哪。

这个前提很重要。如果读者只是「想随便看看」,搜索框帮不上忙——他没有关键词,点开输入框也只能盯着光标发呆。

我现在的归档页是一份按年份排列的索引,五篇文章一眼看得到头。读者要找「讲 Astro 的那篇」,滚一下就找到了。在这个规模下,搜索框不是功能,是一个需要先点击才能用的目录。

什么时候该加

有两个信号值得留意。

一是文章数。经验值大概在三十篇左右——超过这个数,归档页开始需要滚动,而滚动是一种不精确的检索。三十篇以内,索引比搜索快。

二是读者反馈。如果有人写信问你「你那篇讲某个话题的文章在哪儿」,而他刚刚翻过归档页,那搜索就该上场了。这条信号比文章数更准,因为它来自一次真实的失败。

加的话怎么加

静态站做搜索有个标准答案:Pagefind。它在构建完成后扫描 dist/,为每个 HTML 生成一份倒排索引,运行时靠一个几十 KB 的 Wasm 模块在前端查询。

它值得推荐的地方在加载时机:索引文件不随页面加载,而是等读者第一次真正用到搜索时才按需拉取。没有搜索行为的页面,为一个不存在的功能付出零字节。

// 只在读者触发搜索时才 import,避免给所有页面加负担
const pagefind = await import('/_pagefind/pagefind.js');
const search = await pagefind.search('静态站');
const results = await Promise.all(search.results.map((r) => r.data()));

这类工具的价值不在「搜索」本身,而在它承认了一件事:一个不常用的功能,不该让所有人付出代价。 这和我做其他取舍时用的是同一条思路。

评论

评论比搜索棘手,因为它处理的是人。

先问清楚:读者为什么会留言?我观察下来只有两种动机。一种是「我想说一句」——认同、补充、或者单纯想留下痕迹。另一种是「我有一个具体问题」。

这两种动机对应完全不同的设计。

三条路,各有代价

第三方托管,以 Disqus 为代表。接入成本最低,一段脚本。但代价是三重的:它会在你的页面上加载自己的追踪器和广告;评论数据存在别人的服务器上,你随时可能失去它,而且拿不回来;在国内的访问质量也不稳定。省下的那点时间,以后要连本带利还回去。

自建,数据库加反垃圾。这是最完整的方案,但它要求你有一台常驻服务器——静态博客最大的优势(没有后端、没有可被攻破的接口)就此抵消。为了一个评论区养一台机器,账算不过来。

基于 GitHub 的静态方案,Giscus 和 utterances 走这条路。评论存进仓库的 Issue,零后端,和静态站的技术栈贴合得很好。它的问题不在技术,在读者:留言要先有 GitHub 账号。 这会把相当一部分读者挡在门外——而他们恰恰可能是最有话要说的人。

<!-- 省略了 repo-id / category-id 等必要参数 -->
<script
  src="https://giscus.app/client.js"
  data-repo="you/your-blog"
  data-mapping="pathname"
  data-reactions-enabled="1"
  crossorigin="anonymous"
  async
></script>

我选的方案

邮件。成本是一个 mailto: 链接。

它有一个决定性的好处:写邮件的人,是真的想说点什么。 没有「随手留一句」,没有「+1」,也没有需要人工审核的垃圾评论。每一封都值得回。

代价要说清楚:它无法形成公开的讨论氛围。别人的回复你看不到,后来的人看不到前人问过什么,同一个问题可能被问三次。

我接受这个代价,因为博客不是论坛。论坛的价值在讨论,博客的价值在文章本身。如果以后发现有人反复问同一件事,那说明我该写一篇文章,而不是去维护一个评论区。

统计

统计是三件里最容易被默认接受的,也是最值得停下来问一句「为什么」的。

它到底解决什么问题?

「有多少人读过」——这个数字不会改变我写什么。一百个人读过和一千个人读过,我要做的事情一样:把下一段写清楚。它唯一的作用是提供情绪,而情绪是波动的,写作是长期的。

「哪篇被读得多」——这个数字确实有用,它会影响选题。但也正因为它有用,才危险:一旦开始盯着这个数字,写作就会不自觉地朝「写出来会有人点」的方向偏。数据会驯化选题。

真实成本

第三方统计脚本的体积通常在几十到上百 KB,而且它会给每一位读者装上一段追踪代码

这笔交易的实质是:用读者的隐私,换作者的一份自我报告。而这份报告我并不会真的拿来做决定。

替代方案

我用了一组很粗糙但诚实的东西。

RSS 订阅数。它不精确——很多阅读器不反馈数字,还有大量读者用匿名方式订阅。但它的方向是对的:订阅是一次主动选择,比一次偶然的页面访问有信息量得多。

还有收到邮件的频率。上个月有没有人写信,写了什么,这个信号比任何仪表盘都具体。

必须承认的盲区:我不知道谁在读,也不知道读完之后是什么感觉。 一篇文章发出去,可能只有三个人看到,也可能有三千个,我分不出来。

这个盲区是实实在在的代价。但我认为它换来的东西更值:不必为任何一个具体的人调整写法。

一条判断标准

把这三件事放在一起看,它们共享同一个判断标准:

这个功能,解决的是谁的、什么具体问题?

如果答案是「让这个站看起来更完整」——那它解决的是作者的焦虑,不是读者的问题。不做。

如果答案是「某个读者卡在了某个具体的步骤上」——做,并且立刻做。

举个具体的例子。「这篇文章更新于某日」解决的是「它是不是过时了」,这是读者的真实问题,该有。「阅读量 1247」解决的是「这篇受欢迎吗」,这是作者的虚荣,读者不需要。

一个反例

讨论「不做」的时候,容易滑向另一种极端:功能越少越高级。我不这么认为。

举个我差点做错的例子。我一度想去掉标签页,理由是「文章才五篇,标签是过度组织」。但标签回答的是另一类问题——按主题横向找,而归档页是按时间纵向排列的。它们不是同一件事的两个实现,是两种不同的入口。

所以判断标准还得补上半句:不是「这个功能能不能被现有功能替代」,而是「它回答的是不是同一个问题」。 能被替代的可以删,回答不同问题的不能。

什么情况下该加回来

我不想把「不做」说成一种永久的美德。这三样都有明确的触发条件:

  • 搜索:文章超过三十篇,或者有人翻过归档页之后仍然问你某篇在哪儿。
  • 评论:有三个人以上在邮件里问同一个问题。那时你已经有了一个真实的读者群,也知道了他们关心什么。
  • 统计:你需要向别人证明什么。接广告、求职、申请预算,这些场合可以用数据说话。到那时再装,并且装一个尊重读者隐私的。

把这些条件写下来,是为了让将来的自己不必重新纠结一遍。等条件真的触发了,就加,不要因为「当初说好了不加」而硬扛。

那省下来的力气花在哪

这三件事如果都做,大概要吃掉两个周末。省下来的时间,我花在了别的地方。

把代码高亮的浅色和深色各调一遍。Shiki 的双主题输出有个不显眼的坑:深色 token 走 --shiki-dark 这个 CSS 变量,浅色却是直接写在行内 style 上的默认值。如果只给深色写了覆盖规则、浅色那边照抄一份变量引用,浅色主题下所有 token 会一起回退成同一个继承色,整块代码看上去像没上色。构建日志不会报任何错。

把加载时的第一帧做对——主题脚本得跑在样式表之前,否则深色偏好的读者每次打开都会先看到一帧白。

把中文的行首标点自带的半格空白挤掉,把行尾标点悬挂出版心。

这些事没有一件能写进功能清单,也没办法用「有没有」来验收。但它们发生在读者真正在读的那个时刻,而搜索、评论、统计都发生在那个时刻之前或之后。

最后

这三件都不做,代价是我对读者的印象是模糊的。

但模糊也有模糊的好处。写的时候不用想着「他们会不会喜欢」,只需要想着「这样说是不是准确」。

工具选型里最容易被忽略的一点是:不装,也是一种决定。 它和装一样,需要给出理由。

全文完