<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>索引 · 此间札记</title><description>带有「索引」标签的文章订阅。</description><link>https://xiaoxusop.github.io/letterpress/</link><language>zh-CN</language><copyright>© XIAOXUsop</copyright><atom:link href="https://xiaoxusop.github.io/letterpress/tags/%E7%B4%A2%E5%BC%95/rss.xml" rel="self" type="application/rss+xml"/><item><title>静态站搜索的三种做法，以及它们各自真实的上限</title><link>https://xiaoxusop.github.io/letterpress/static-site-search/</link><guid isPermaLink="true">https://xiaoxusop.github.io/letterpress/static-site-search/</guid><description>前端过滤、构建期索引、外挂服务——三种方案的取舍不在功能多少，在「索引存在哪」这一件事上。</description><pubDate>Fri, 14 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;静态站没有后端，所以搜索必须换个地方放。只有三个位置：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;放进浏览器&lt;/strong&gt;、&lt;strong&gt;放进构建产物&lt;/strong&gt;、&lt;strong&gt;放到另一台服务器上&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;选哪种，取决于你愿意接受哪一种上限。&lt;/p&gt;
&lt;h2 id=&quot;一放进浏览器把内容全量打到前端&quot;&gt;一、放进浏览器：把内容全量打到前端&lt;a class=&quot;heading-anchor&quot; href=&quot;#一放进浏览器把内容全量打到前端&quot; aria-label=&quot;此小节的永久链接&quot; data-pagefind-ignore=&quot;&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;最简单：构建时把全部文章导成一个 JSON，页面加载后在前端过滤。&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark-dimmed&quot; style=&quot;background-color:#fff;--shiki-dark-bg:#22272e;color:#24292e;--shiki-dark:#adbac7; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;js&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F47067&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#6CB6FF&quot;&gt; results&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F47067&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#ADBAC7&quot;&gt; all.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#DCBDFB&quot;&gt;filter&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#ADBAC7&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#F69D50&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F47067&quot;&gt; =&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#ADBAC7&quot;&gt; p.title.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#DCBDFB&quot;&gt;includes&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#ADBAC7&quot;&gt;(q) &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F47067&quot;&gt;||&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#ADBAC7&quot;&gt; p.body.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#DCBDFB&quot;&gt;includes&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#ADBAC7&quot;&gt;(q));&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;上限在「全量」两个字上。&lt;/strong&gt; 索引就是全部正文，文章一多，用户为了搜一个词要下载整站内容。&lt;/p&gt;
&lt;p&gt;50 篇中等长度的文章大约 1–2 MB。1000 篇就是几十 MB——搜索框还没用上，
流量已经烧完了。&lt;/p&gt;
&lt;p&gt;它适合&lt;strong&gt;几十篇以内&lt;/strong&gt;的站点。超过这个量级，你会开始想「能不能只加载摘要」，
而那正是第二种方案。&lt;/p&gt;
&lt;h2 id=&quot;二放进构建产物只下载倒排索引&quot;&gt;二、放进构建产物：只下载倒排索引&lt;a class=&quot;heading-anchor&quot; href=&quot;#二放进构建产物只下载倒排索引&quot; aria-label=&quot;此小节的永久链接&quot; data-pagefind-ignore=&quot;&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;构建时切词、建倒排索引，把索引作为静态资源发布。用户只下载索引，不下载正文；
命中之后再按需取片段。&lt;/p&gt;
&lt;p&gt;这一类的代表是 &lt;a href=&quot;https://pagefind.app/&quot;&gt;Pagefind&lt;/a&gt; 和 &lt;a href=&quot;https://www.fusejs.io/&quot;&gt;Fuse.js&lt;/a&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Fuse.js&lt;/strong&gt; 是纯前端模糊匹配，仍然需要你把待搜内容喂给它——所以它解决的是
「怎么匹配」，没解决「内容放哪」&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pagefind&lt;/strong&gt; 是构建后跑的索引器，直接读你的 HTML 产物。它把索引切成小块，
搜索时只加载相关分片——&lt;strong&gt;这才是真正的「索引比正文小」&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Pages/Netlify 这类托管上，Pagefind 是当前最省事的选择：一条命令，
零后端，还能按语言分别处理。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;上限在分词上。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Pagefind 对中文不做词干提取——中文本来也没有词形变化，所以这不影响正确性。
真正的影响是&lt;strong&gt;召回&lt;/strong&gt;：它按字/词切分建索引，你搜「排版」能命中包含「排版」的段落，
但搜「网页排版」不一定能命中只写了「排版」的地方。&lt;/p&gt;
&lt;p&gt;英文靠词干回退（running ↔ run）缓解这类问题，中文没有对应机制。
&lt;strong&gt;这是静态站搜索的固有限制，不是某个库的缺陷。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;三外挂服务algoliameilisearch-这类&quot;&gt;三、外挂服务：Algolia、Meilisearch 这类&lt;a class=&quot;heading-anchor&quot; href=&quot;#三外挂服务algoliameilisearch-这类&quot; aria-label=&quot;此小节的永久链接&quot; data-pagefind-ignore=&quot;&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;把索引放到一台真的服务器上，查询走网络。&lt;/p&gt;
&lt;p&gt;功能上限最高：分词器可换、同义词、拼写纠错、按字段加权、结果高亮、
搜索分析——都能做。&lt;/p&gt;
&lt;p&gt;代价也很清楚：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;多了一个要运维的东西。&lt;/strong&gt; 静态站的全部好处就是「没有服务器」，
为了搜索把它加回来，这笔账要自己算&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;多了一个会挂的东西。&lt;/strong&gt; 搜索服务挂了，站还在，但搜索框废了&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;多了一个花钱的地方。&lt;/strong&gt; 免费额度通常按「记录数 × 查询次数」计&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;它适合「搜索是核心功能」的站点&lt;/strong&gt;——文档站、电商、知识库。
对个人博客，通常是过度配置。&lt;/p&gt;
&lt;h2 id=&quot;怎么选&quot;&gt;怎么选&lt;a class=&quot;heading-anchor&quot; href=&quot;#怎么选&quot; aria-label=&quot;此小节的永久链接&quot; data-pagefind-ignore=&quot;&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;div class=&quot;table-wrap&quot; tabindex=&quot;0&quot; role=&quot;region&quot; aria-label=&quot;可横向滚动的表格&quot; data-table-wrap=&quot;&quot;&gt;
























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;文章数&lt;/th&gt;&lt;th&gt;建议&lt;/th&gt;&lt;th&gt;理由&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&amp;#x3C; 50&lt;/td&gt;&lt;td&gt;第一种就够&lt;/td&gt;&lt;td&gt;全量 JSON 也就几百 KB，省一套索引流程&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;50 – 几千&lt;/td&gt;&lt;td&gt;第二种&lt;/td&gt;&lt;td&gt;索引与正文分离，零运维&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;搜索本身是产品&lt;/td&gt;&lt;td&gt;第三种&lt;/td&gt;&lt;td&gt;只有它能在&lt;strong&gt;分词&lt;/strong&gt;这一层做文章&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;判断的关键不是功能表，是「索引存在哪」&lt;/strong&gt;：在用户浏览器里、在构建产物里、
还是在别人的服务器上。三种位置对应三种上限，没有例外。&lt;/p&gt;
&lt;h2 id=&quot;顺带一句搜索需要-javascript这是要认的&quot;&gt;顺带一句：搜索需要 JavaScript，这是要认的&lt;a class=&quot;heading-anchor&quot; href=&quot;#顺带一句搜索需要-javascript这是要认的&quot; aria-label=&quot;此小节的永久链接&quot; data-pagefind-ignore=&quot;&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;前两种方案都跑在浏览器里，所以&lt;strong&gt;没有 JS 就没有搜索&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;这不是可以糊弄过去的——正确的做法是给一个 &lt;code&gt;&amp;#x3C;noscript&gt;&lt;/code&gt; 出口，
让关掉 JS 的读者至少能走到归档页，而不是对着一个永远转圈的输入框。&lt;/p&gt;
&lt;p&gt;相关条目：&lt;a href=&quot;/letterpress/wiki/content-negotiation/&quot;&gt;内容协商&lt;/a&gt;。&lt;/p&gt;
&lt;h2 id=&quot;参考&quot;&gt;参考&lt;a class=&quot;heading-anchor&quot; href=&quot;#参考&quot; aria-label=&quot;此小节的永久链接&quot; data-pagefind-ignore=&quot;&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Pagefind 官方文档（索引分片与多语言处理）&lt;/li&gt;
&lt;li&gt;Fuse.js 官方文档（模糊匹配算法与阈值）&lt;/li&gt;
&lt;li&gt;本文提到的中文召回限制来自本站在 2026-09 的实测：搜「排版」7 条命中，
搜「行高」6 条命中，而搜「网页排版」只有 4 条——少掉的那几条正是
正文里只写了「排版」、没写完整四个字的地方&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;复现方式：&lt;code&gt;npm run build&lt;/code&gt; 之后到 &lt;code&gt;/search/&lt;/code&gt; 搜这三个词。
&lt;strong&gt;&lt;code&gt;npm run dev&lt;/code&gt; 里搜不到任何东西&lt;/strong&gt;——索引是构建期（Pagefind）生成的，
开发服务器没有它。这个数字也会随内容增删而变化，上面记的是当时的快照。&lt;/p&gt;
&lt;/blockquote&gt;</content:encoded><category>搜索</category><category>静态站</category><category>索引</category><author>XIAOXUsop</author></item></channel></rss>