本地搜索设计:为什么个人博客可以先不用后端搜索服务
从 HUAN Lab 的搜索页和索引字段出发,分析轻量博客如何用本地索引覆盖标题、摘要、正文、分类和标签检索。
搜索是个人博客很容易拖到后面才做的功能。早期文章少,首页列表似乎够用;但只要内容开始跨越教程、项目复盘、阅读笔记和生活思考,单靠时间线就会变得笨重。HUAN Lab 当前选择了本地搜索:构建时准备搜索文档,浏览器里用 MiniSearch 建索引并返回结果。[source-search-page][source-search-box]
小型内容站的搜索不一定要从后端服务开始。只要文章规模可控,本地索引可以先覆盖大部分找回需求。
搜索文档从哪里来
搜索页 app/search/page.tsx 调用 getSearchDocuments(),再把文档列表传给客户端组件 SearchBox。[source-search-page] 这个分工说明搜索页本身仍然是 App Router 页面,而真正依赖输入状态和浏览器交互的部分放在 client component 里。
SearchBox 接收的文档类型来自 @/lib/search,并把这些文档加入 MiniSearch 实例。[source-search-box] 当前我没有在这里引入外部搜索服务,也没有看到运行时请求远程搜索接口;搜索体验主要依赖已经传入页面的本地文档。
这种实现适合当前阶段:
- 文章数量不大,本地索引体积可控
- 不需要额外部署搜索后端
- 搜索结果可以和静态生成页面一起发布
- 离线式内容结构更符合个人博客维护方式
字段权重体现信息架构
SearchBox 把 title、description、body、category 和 tags 作为可搜索字段,并在 searchOptions 里提高了标题、标签和分类的权重。[source-search-box]
const instance = new MiniSearch<SearchDocument>({
fields: ["title", "description", "body", "category", "tags"],
storeFields: ["slug", "title", "description", "category", "tags", "formattedDate", "readingTime"],
searchOptions: {
boost: { title: 3, tags: 2, category: 2 },
fuzzy: 0.2,
prefix: true
}
});这个权重设计背后有一个朴素判断:用户输入的关键词如果命中标题,通常比命中正文某个普通词更重要;如果命中标签或分类,也说明这篇文章在主题上更相关。[source-search-box] 正文仍然参与搜索,但不应该压过文章自身的主题标注。
搜索结果保留最小上下文
搜索结果展示的字段包括日期、阅读时间、分类、标题和摘要。[source-search-box] 这些信息足够帮助读者做第一次判断:这是不是我要找的主题?文章大概多长?它属于哪类内容?
这里有一个好的克制点:结果列表没有直接展示正文命中片段。对于早期博客,这可以减少实现复杂度;等文章更多、正文更长时,再考虑高亮命中片段也不迟。
和分类、标签的关系
本地搜索不是替代分类和标签,而是补足它们。分类适合稳定入口,标签适合主题聚合,搜索适合用户已经知道关键词时快速定位。HUAN Lab 的内容层提供了 getPostsByCategory、getPostsByTag、getCategoryCounts 和 getTagCounts,搜索组件又把 category 和 tags 纳入索引字段。[source-posts][source-search-box]
这意味着分类、标签和搜索共享同一批元数据,而不是维护三套独立结构。这样的信息架构更不容易漂移:
- 修改文章标签后,标签页和搜索索引一起更新
- 修改文章分类后,分类页和搜索权重一起更新
- 修改标题或摘要后,列表页和搜索结果一起更新
可能遇到的边界
本地搜索也有边界。当前实现更适合轻量博客,而不是大规模文档站。随着文章数量增长,后续可能需要关注:
- 首屏传输的搜索文档体积
- 中文分词和模糊匹配的实际效果
- 搜索结果排序是否符合读者预期
- 是否需要命中片段和关键词高亮
- 是否要把搜索索引拆成独立静态 JSON
这些都不是当前实现的反面。更准确地说,本地搜索是一个低运维成本的起点:先让内容可找回,再根据真实使用情况决定是否升级。
实践建议
如果继续沿着当前方案扩展,我会优先做三件小事:
- 给搜索页增加空查询时的热门标签或最近文章入口。
- 在搜索结果中显示匹配到的标签,帮助读者理解为什么命中。
- 当文章数量明显增加后,把搜索文档体积纳入构建检查。
这些改动都不需要推翻现有架构。它们只是围绕同一套文章元数据,把“能搜到”继续打磨成“更容易判断”。
参考资料
- [source-search-page]
app/search/page.tsx,搜索页获取搜索文档并渲染客户端搜索组件。 - [source-search-box]
components/search-box.tsx,MiniSearch 字段、权重、模糊匹配和结果展示逻辑。 - [source-posts]
lib/posts.ts,分类、标签、文章列表和派生字段的内容层实现。