# 静态站搜索的三种做法，以及它们各自真实的上限

日期：2026-08-14  
标签：搜索、静态站、索引

> 前端过滤、构建期索引、外挂服务——三种方案的取舍不在功能多少，在「索引存在哪」这一件事上。

来源：https://xiaoxusop.github.io/letterpress/static-site-search/
语言：zh-CN
类型：文章

## 正文

静态站没有后端，所以搜索必须换个地方放。只有三个位置：

**放进浏览器**、**放进构建产物**、**放到另一台服务器上**。

选哪种，取决于你愿意接受哪一种上限。

## 一、放进浏览器：把内容全量打到前端

最简单：构建时把全部文章导成一个 JSON，页面加载后在前端过滤。

```js
const results = all.filter(p => p.title.includes(q) || p.body.includes(q));
```

**上限在「全量」两个字上。** 索引就是全部正文，文章一多，用户为了搜一个词要下载整站内容。

50 篇中等长度的文章大约 1–2 MB。1000 篇就是几十 MB——搜索框还没用上，
流量已经烧完了。

它适合**几十篇以内**的站点。超过这个量级，你会开始想「能不能只加载摘要」，
而那正是第二种方案。

## 二、放进构建产物：只下载倒排索引

构建时切词、建倒排索引，把索引作为静态资源发布。用户只下载索引，不下载正文；
命中之后再按需取片段。

这一类的代表是 [Pagefind](https://pagefind.app/) 和 [Fuse.js](https://www.fusejs.io/)：

- **Fuse.js** 是纯前端模糊匹配，仍然需要你把待搜内容喂给它——所以它解决的是
  「怎么匹配」，没解决「内容放哪」
- **Pagefind** 是构建后跑的索引器，直接读你的 HTML 产物。它把索引切成小块，
  搜索时只加载相关分片——**这才是真正的「索引比正文小」**

Pages/Netlify 这类托管上，Pagefind 是当前最省事的选择：一条命令，
零后端，还能按语言分别处理。

**上限在分词上。**

Pagefind 对中文不做词干提取——中文本来也没有词形变化，所以这不影响正确性。
真正的影响是**召回**：它按字/词切分建索引，你搜「排版」能命中包含「排版」的段落，
但搜「网页排版」不一定能命中只写了「排版」的地方。

英文靠词干回退（running ↔ run）缓解这类问题，中文没有对应机制。
**这是静态站搜索的固有限制，不是某个库的缺陷。**

## 三、外挂服务：Algolia、Meilisearch 这类

把索引放到一台真的服务器上，查询走网络。

功能上限最高：分词器可换、同义词、拼写纠错、按字段加权、结果高亮、
搜索分析——都能做。

代价也很清楚：

- **多了一个要运维的东西。** 静态站的全部好处就是「没有服务器」，
  为了搜索把它加回来，这笔账要自己算
- **多了一个会挂的东西。** 搜索服务挂了，站还在，但搜索框废了
- **多了一个花钱的地方。** 免费额度通常按「记录数 × 查询次数」计

**它适合「搜索是核心功能」的站点**——文档站、电商、知识库。
对个人博客，通常是过度配置。

## 怎么选

| 文章数 | 建议 | 理由 |
|---|---|---|
| < 50 | 第一种就够 | 全量 JSON 也就几百 KB，省一套索引流程 |
| 50 – 几千 | 第二种 | 索引与正文分离，零运维 |
| 搜索本身是产品 | 第三种 | 只有它能在**分词**这一层做文章 |

**判断的关键不是功能表，是「索引存在哪」**：在用户浏览器里、在构建产物里、
还是在别人的服务器上。三种位置对应三种上限，没有例外。

## 顺带一句：搜索需要 JavaScript，这是要认的

前两种方案都跑在浏览器里，所以**没有 JS 就没有搜索**。

这不是可以糊弄过去的——正确的做法是给一个 `<noscript>` 出口，
让关掉 JS 的读者至少能走到归档页，而不是对着一个永远转圈的输入框。

相关条目：[内容协商](https://xiaoxusop.github.io/letterpress/wiki/content-negotiation/)。

## 参考

- Pagefind 官方文档（索引分片与多语言处理）
- Fuse.js 官方文档（模糊匹配算法与阈值）
- 本文提到的中文召回限制来自本站在 2026-09 的实测：搜「排版」7 条命中，
  搜「行高」6 条命中，而搜「网页排版」只有 4 条——少掉的那几条正是
  正文里只写了「排版」、没写完整四个字的地方

> 复现方式：`npm run build` 之后到 `/search/` 搜这三个词。
> **`npm run dev` 里搜不到任何东西**——索引是构建期（Pagefind）生成的，
> 开发服务器没有它。这个数字也会随内容增删而变化，上面记的是当时的快照。
