Pagefind 入门:静态站的搜索方案
静态站想给用户一个搜索框,又不想为此起一个搜索服务——Pagefind 把这件事变成了「构建时多生成一个文件夹」。
静态站搜索的矛盾
动态站的搜索是后端的事:一个搜索接口查数据库/Elasticsearch 返回结果。静态站(Astro 纯静态输出)没有后端进程,搜索方案的选项就变得别扭:要么接 Algolia 这类云服务(外部依赖 + 收费),要么自建一个搜索服务(为一个小站起个 ES 太重了)。
Pagefind 的思路:搜索需要的不是服务,是数据。它在构建阶段扫描你产出的 HTML,把文本内容切成倒排索引文件,随静态资源一起部署;用户搜索时,浏览器直接加载索引文件、在本地完成检索。全程无服务端参与。
构建时: Astro 产出 HTML → Pagefind 扫描 → 生成 /pagefind/ 索引目录
部署时: 索引目录跟着静态资源一起上 CDN
运行时: 浏览器加载索引分片 → 本地检索 → 毫秒出结果
接入
pnpm add -D pagefind
// package.json
{
"scripts": {
"build": "astro build && pagefind --site dist"
}
}
--site dist 指向构建产物目录,Pagefind 扫描所有 HTML 生成索引。前端组件消费索引:
// 页面加载时不加载索引,用户聚焦搜索框才惰性加载
const pagefind = await import('/pagefind/pagefind.js');
await pagefind.search('docker');
或直接用官方 UI(一个带搜索框和结果列表的现成组件):
<link href="/pagefind/pagefind-ui.css" rel="stylesheet">
<script src="/pagefind/pagefind-ui.js"></script>
<div id="search"></div>
<script>
new PagefindUI({ element: '#search', showSubResults: true });
</script>
索引是怎么被优化的
Pagefind 的索引设计为按需分片加载:
- 索引按词根分片,搜索「dock」只加载包含这个词的索引片(几十 KB),不是全量索引(可能几 MB);
- 纯英文站索引极小;中文内容需要多字节分词支持,Pagefind 有
pagefind[ multiline, zh ]的中文实验性支持,中文站的常见替代是配合构建时的中文分词预处理,或改用 preview 模式的多语言索引。
标记内容边界用 data 属性:
<article data-pagefind-body> <!-- 只索引这里的内容 -->
<nav data-pagefind-ignore> <!-- 导航不进索引 -->
</article>
不标记时默认索引整个页面——加标记能显著提高结果质量(搜「docker」不该命中侧栏里的「Docker 分类」链接文字,该命中正文)。
性能特征与边界
规模上限:官方基准到 50 万页量级仍可用(索引分片数增多,但惰性加载兜住体验)。个人博客、文档站完全无压力。
更新时机:索引是构建时产物——内容更新必须重新构建部署。对本站这类「发文章即触发部署」的流程完全自然;但如果是数据库驱动的 SSR 站点(本站文章主体走后端接口渲染),Pagefind 只能覆盖构建时已知的静态页,动态内容要走别的搜索方案(本站文章搜索走的是后端 Lucene)。
离线能力:索引是静态文件,整站下载后搜索照样能用——配合 PWA 做「离线可搜的文档包」是它的独特玩法。
本站的实际分工
本站的搜索分了两条线,各自选了合适的工具:
| 内容类型 | 特点 | 方案 |
|---|---|---|
| 静态页面(指南、文档页) | 构建时已知 | Pagefind 本地索引 |
| 文章、知识库 | 数据库动态内容 | 后端 Lucene 搜索接口 |
这个分工本身就是一个架构决策示例:搜索方案跟着内容的生产方式走——构建时能确定的内容用构建时索引,运行时才确定的内容用运行时服务。
小结
Pagefind 把「给静态站加搜索」的成本压缩到构建命令里加一个步骤:零服务、零外部依赖、浏览器本地检索、按需分片加载。适合静态文档站、博客的页面级搜索;动态内容搜索则该走服务端方案。选型判断就一句话:内容在构建时是否已知。