Typecho 的搜索在服务端处理;迁到纯静态站后,检索被拆成构建期整理数据、浏览器读取索引与筛选结果两个阶段。Waxy 现在已经采用独立的 search.json,并按站点配置分页展示结果,不再把全部卡片和检索正文预先铺进搜索页。
构建时:准备两组数据
索引生成器读取已发布文章,按日期倒序组织条目。标题、地址、日期、封面、摘要、字数、分类、标签和推荐状态用于绘制卡片;另一个 text 字段把标题、分类、标签与正文片段合在一起,压缩空白并转成小写,供浏览器匹配。
这份 JSON 是公开的静态文件。草稿不会作为已发布文章写入索引,但已进入索引的正文片段对访客可见,不能靠“搜索框里不展示”保护资料。需要控制访问权限的内容,可以先看前端加密的边界,再决定它是否应该发布。
在当前站点中,主题设置放在 _config.hexo-theme-waxy.yml:
search_path: /search/
search:
content_limit: 3000
content_limit 限制每篇正文进入索引的字符数,标题、分类和标签另外加入。提高上限能覆盖更靠后的内容,也会增加下载量。入口路径还需要有对应的搜索页面,不能只改配置里的名字。修改文章或设置后,要重新构建并发布索引与页面。
打开页面:先加载,再读当前输入
搜索页会请求站点根路径下的 search.json。下面这个独立函数只演示读取索引,真正的页面还会处理提示、卡片和分页;部署在子目录时,应传入包含该根路径的地址。
async function readIndex(url) {
const response = await fetch(url, { credentials: 'same-origin' });
if (!response.ok) throw new Error(`索引请求失败:${response.status}`);
return response.json();
}
不能只写 catch 就认为 HTTP 错误都被处理了:fetch 收到 404 等响应时仍可能正常得到 Response,所以代码先检查 ok。当前页面加载失败后会显示重试提示;索引未就绪时提交搜索用于重试,不会把失败状态伪装成“没有结果”。
这里还有一个容易漏掉的先后顺序:请求发出后,访客可能已经改过关键词。因此索引返回时读取的是输入框的当前值,而不是开始请求那一刻保存的旧值。等待期间改词或清空,也会把请求页码重置到第一页。
输入时:过滤全部条目,只渲染当前页
当前匹配是去掉首尾空白、忽略大小写后的子串查找。它没有自动分词、模糊纠错或相关性评分;输入带空格的短语,也不会被自动拆成几个独立条件。先理解这个口径,再判断一次未命中是否属于问题。
分页条数来自站点 _config.yml,没有第二个搜索专用条数参数:
per_page: 10
页面模板把值写入 data-per-page,浏览器先算完整命中集合与总数,再取当前页。per_page: 0 表示全部展示,不生成分页控件。下面是切片的核心逻辑,参数 perPage 应为页面提供的非负整数:
function resultPage(matches, requested, perPage) {
const total = perPage === 0 ? 1 : Math.max(1, Math.ceil(matches.length / perPage));
const raw = String(requested ?? '1');
const value = Number(raw);
const valid = /^\d+$/.test(raw) && Number.isSafeInteger(value) && value > 0 ? value : 1;
const current = Math.min(valid, total);
const start = (current - 1) * perPage;
return {
current, total,
items: perPage === 0 ? matches : matches.slice(start, start + perPage)
};
}
总命中数不会随着页切片变成“本页几条”。卡片在浏览器中按主题现有结构生成,只有当前页需要创建 DOM。分页因此降低的是卡片渲染规模,整份 JSON 仍需要下载;它没有把客户端检索变成服务端检索。
URL 也属于搜索状态
页码放在 page,关键词放在 q,例如 /search/?q=Hexo&page=2。分页使用普通链接,刷新后能重新得到同一页,浏览器返回也有正常的页面记录。输入新词或清空则回到第一页,并通过 replaceState 替换当前地址,不为每次按键新建一条历史。
中文、空格和 C++ 这样的词应交给 URLSearchParams 编码。手工拼接 q=C++ 可能把加号解释成空格。非法页码归一为第一页,超过实际页数会落到末页;若关键词只有一页结果,打开它的第二页地址被归一是预期行为。
怎样判断要不要换方案
先看网络面板中索引的传输体积和请求时间,再看输入时是否明显停顿、结果是否符合预期。本站内容会继续增加,不能用“十几篇够用”或固定篇数阈值替代检查。缓存能否复用也取决于部署时的 HTTP 响应头,不能只因为文件名没变就假定不会重新请求。
验证时至少覆盖:第二页刷新与返回、改词后的第一页、空结果、索引请求失败后重试,以及未加载完成就改输入。需要更完整覆盖时再调整正文上限;需要排序、纠错或更低的首包成本时,再评估专门索引、分片等方案。先定位下载、匹配还是渲染慢,才知道应该动哪一层。