给静态博客加上中文搜索:Pagefind 接入实录

文章只有三篇的时候,「搜索」看起来是个多余的栏目:归档页一屏就能翻完。但静态博客的内容是慢慢长出来的,等文章多到归档页需要翻好几屏时再补搜索,就要重新审视内容结构了。所以建站之初,我把搜索当成和 RSS 一样的基础设施来对待。
静态站点没有后端,搜索只有两条路:要么在浏览器里加载全部文章的 JSON 再用脚本过滤,要么在构建阶段预先建好索引。前者实现简单,但成本随内容线性增长,手机上会明显变慢;后者多一道构建工序,换来的是「打开搜索框时才付出成本」。本站选了后者,具体来说是 Pagefind。
Pagefind 的工作方式
Pagefind 的思路很干净:astro build 结束后,对 dist/ 里的静态 HTML 再跑一次索引,索引产物写进 dist/pagefind/,随站点一起部署。浏览器打开搜索页时,先加载一个很小的入口脚本,之后根据输入的关键词按需拉取对应的索引分片——文章越多索引越大,但单次查询下载的分片不会同步膨胀。
最关键的机制是「范围标注」:Pagefind 默认索引整个 <body>,而导航、侧边栏、页脚这些模板文字会污染结果。给文章正文容器加上 data-pagefind-body 后,索引范围就只剩正文;标题可以再用 data-pagefind-weight 提权,这样搜文章标题里的词时,正确的文章会排在前面。
本站的标注大致是这样的:
<h1 data-pagefind-body data-pagefind-weight="10" data-pagefind-meta="title"> 文章标题</h1><div data-pagefind-body class="prose"> 正文内容……</div>data-pagefind-meta="title" 会让搜索结果卡片直接展示这条标题,而不是截取正文片段的开头。
一个让线上搜索 404 的陷阱
接入过程里踩过一个很有代表性的坑:Pagefind 的 --site 参数指向了错误的目录。
Pagefind 忠实地索引了那个目录,于是索引里出现了一批根本不存在的假路径,同时索引产物被输出到了不会随站点上传的位置。构建不报错、预览一切正常,直到部署上线,搜索框里输入任何词都是 404。
静态构建里「产物位置」和「工具默认行为」不匹配时,往往不会有任何报错。涉及索引、站点地图这类后处理,验收一定要放在真实产物上,而不是本地预览。
本站最终的解法是把 Pagefind 挪进了构建脚本:一个专门的 TS 脚本解析出真正的站点根目录,再以子进程方式运行 pagefind --site <root>。这也是为什么项目文档反复强调正式构建必须用完整的 pnpm build——直接跑 astro build 会跳过包括 Pagefind 在内的全部后处理,拿到一个搜索失效的产物。
中文搜索的表现
中文是搜索的经典难题:英文按空格分词,中文没有。Pagefind 对中文提供了开箱即用的支持,页面声明 lang="zh-CN" 时会自动启用中文分词,索引阶段完成切分,不需要再引入外部分词库。
实测本站,直接搜「酥酥鸡」「Firefly」「Nginx」都能命中预期文章,正文里的长词也能拆出合理的词组结果。需要说明边界:它是分词匹配,不是语义搜索——搜同义表述不会命中,拼音和错别字也不会被纠正。对一个内容量在几百篇以内的个人博客,这个能力边界完全够用。
验收清单
每次改动搜索相关配置后,我会过一遍这几项:
- 构建产物里
pagefind/pagefind.js确实存在; - 搜文章标题、正文关键词、英文专有名词各一次,结果符合预期;
- 搜一个不存在的词,空结果页正常显示而不是报错;
- 从搜索结果点进文章,落地页 URL 正确。
搜索是那种「平时感觉不到,坏了才发现」的功能,把它纳入构建验收清单,比事后排查便宜得多。
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!













