给HUGO添加文章搜索功能
之前用 Hugo-theme-stack 主题,自带了搜索功能,我曾以为这种功能是 Hugo 默认就有,后来才知道,是外挂。
n
在更换 Bear cub 主题后,之前也没觉得这功能有多重要,但实际使用中发现,有时候记不起来一些事情,还得借助搜索才行。于是研究了下怎么外挂搜索。其实也很简单,只需四步。
n
在 content 目录下新建 search.md 页面
n
如果不需要放置在导航栏,内容填入默认信息即可,需要进导航栏的,效仿其他 md 文件设置。
nnnnnn
1---n2title: "搜索"n3date: 2025-11-20T00:00:00+08:00n4type: "search"n5layout: "search"n6---n7在此搜索本博客文章。
在 hugo 配置文件中添加 json 输出
n
比如我的hugo配置文件是 toml 格式,内容如下。
nnnnnn
1[outputs]n2 home = ["HTML", "RSS", "JSON"] # 添加 JSON 输出n3n4[outputFormats.JSON]n5 baseName = "index"n6 mediaType = "application/json"
在 layouts 目录下新建 /search/single.html 模板
n
模板内容可以参照其他模板修改,搜索功能可定制精确搜索或模糊搜索,大致内容如下。
nnnnnn
1{{ define "main" }}n 2<content>n 3 <h1>{{ .Title }}</h1>n 4 <div class="search-box">n 5 <inputn 6 id="search-input"n 7 class="search-input"n 8 type="text"n 9 placeholder="输入关键词搜索…"n 10 autocomplete="off"n 11 />n 12 </div>n 13n 14 <ul id="results" class="search-results">n 15 <li style="color:#666">请输入关键词搜索</li>n 16 </ul>n 17n 18</content>n 19n 20<script>n 21// ========== 工具函数 ==========n 22function escapeHtml(str) {n 23 if (!str) return "";n 24 return strn 25 .replace(/&/g, "&")n 26 .replace(/</g, "<")n 27 .replace(/>/g, ">");n 28}n 29n 30// ========== 精确搜索 ==========n 31function exactMatch(haystack, needle) {n 32 if (!haystack || !needle) return false;n 33 return haystack.toLowerCase().includes(needle.toLowerCase());n 34}n 35n 36// ========== 渲染结果 ==========n 37function renderResults(list) {n 38 const resultsEl = document.getElementById("results");n 39n 40 if (!list || list.length === 0) {n 41 resultsEl.innerHTML = '<li style="color:#666">未找到结果。</li>';n 42 return;n 43 }n 44n 45 const itemsHtml = list.map(item => {n 46 const title = escapeHtml(item.title || "(无标题)");n 47n 48 // 外链或本地链接n 49 const url = escapeHtml(item.link || item.url || "#");n 50 const isExternal = !!item.link;n 51n 52 const linkAttrs = isExternaln 53 ? ' target="_blank" rel="noopener noreferrer"'n 54 : "";n 55n 56 // 摘要:取 summary 或 content 开头一段n 57 const summaryRaw =n 58 item.summary ||n 59 (item.content ? item.content.slice(0, 200) + "…" : "");n 60n 61 const summary = escapeHtml(summaryRaw);n 62n 63 return `n 64 <li>n 65 <div class="sr-title-col">n 66 <a class="sr-title" href="${url}"${linkAttrs}>${title}</a>n 67 </div>n 68n 69 <div class="sr-snippet-col">n 70 <div class="sr-snippet">${summary}</div>n 71 </div>n 72 </li>n 73 `;n 74 }).join("");n 75n 76 resultsEl.innerHTML = itemsHtml;n 77}n 78n 79// ========== 加载 index.json 数据 ==========n 80async function loadIndex() {n 81 try {n 82 const res = await fetch("/index.json");n 83 return await res.json();n 84 } catch (err) {n 85 console.error("加载 index.json 失败:", err);n 86 return [];n 87 }n 88}n 89n 90// ========== 主逻辑 ==========n 91(async function () {n 92 const data = await loadIndex();n 93 const input = document.getElementById("search-input");n 94n 95 input.addEventListener("input", () => {n 96 const q = input.value.trim();n 97n 98 if (!q) {n 99 renderResults([]);n100 return;n101 }n102n103 // 精确搜索:title / content / tags 均可匹配n104 const result = data.filter(item =>n105 exactMatch(item.title, q) ||n106 exactMatch(item.content, q) ||n107 (item.tags || []).some(t => exactMatch(t, q))n108 );n109n110 renderResults(result);n111 });n112})();n113</script>n114{{ end }}
在 layouts 中新建 index.json 模板
n
主要就是将整个博客文档内容输出到 index.json 文件,搜索时直接在该文件内搜索,可依照自己需求进行修改,比如只搜索标题、摘要、标签等信息。
nnnnnn
1[n 2{{- $pages := where .Site.RegularPages "Type" "not in" (slice "page" "something-you-want-to-exclude") -}}n 3{{- $first := true -}}n 4{{- range $i, $p := $pages -}}n 5 {{- if not $first }},{{ end -}}n 6 {n 7 "title": {{ $p.Title | jsonify }},n 8 "url": {{ $p.RelPermalink | absURL | jsonify }},n 9 "date": {{ $p.Date.Format "2006-01-02" | jsonify }},n10 "summary": {{ with $p.Params.description }}{{ . | jsonify }}{{ else }}{{ $p.Summary | plainify | jsonify }}{{ end }},n11 "content": {{ $p.Plain | chomp | jsonify }},n12 "tags": {{ $p.Params.tags | jsonify }},n13 "categories": {{ $p.Params.categories | jsonify }}n14 }n15 {{- $first = false -}}n16{{- end -}}n17]
其他 CSS 配置
n
如果需要自定义搜索页面的 CSS,可以直接在主题 CSS 或自定义 custom.css 文件中添加,或者写在前边 /search/single.html 模板中也可以。
nnnnnn
1/* ====== 搜索框区域布局 ====== */n 2.search-box {n 3 max-width: 720px;n 4 margin: 24px 0 32px;n 5}n 6n 7.search-input {n 8 width: 100%;n 9 padding: 10px 14px;n 10 font-size: 16px;n 11n 12 border: 1px solid var(--border);n 13 border-radius: 8px;n 14n 15 background: var(--entry);n 16 color: var(--primary);n 17n 18 outline: none;n 19 transition: border-color .15s ease, box-shadow .15s ease;n 20}n 21n 22.search-box {n 23 max-width: 720px;n 24 margin: 24px 0 32px;n 25}n 26n 27.search-input {n 28 width: 100%;n 29 padding: 10px 14px;n 30 font-size: 16px;n 31n 32 border: 1px solid rgba(150, 150, 150, 0.35);n 33 border-radius: 8px;n 34n 35 background: var(--entry);n 36 color: var(--primary);n 37 outline: none;n 38n 39 transition: border-color .15s ease, box-shadow .15s ease;n 40}n 41n 42.search-input:focus {n 43 border-color: var(--text-highlight);n 44 box-shadow: 0 0 0 2px rgba(100, 108, 255, 0.20);n 45}n 46n 47.search-results {n 48 list-style: none;n 49 padding: 0;n 50 margin: 0;n 51}n 52n 53.search-results li {n 54 display: flex;n 55 align-items: flex-start;n 56 gap: 16px;n 57 padding: 12px 0;n 58 border-bottom: 1px solid rgba(0,0,0,0.04);n 59}n 60n 61.search-results .sr-title-col {n 62 flex: 0 0 40%;n 63 min-width: 180px;n 64 max-width: 420px;n 65}n 66n 67.search-results .sr-title {n 68 font-size: 1.02rem;n 69 line-height: 1.3;n 70 text-decoration: none;n 71 color: var(--primary);n 72}n 73n 74.search-results .sr-title[target="_blank"]::after {n 75 content: " ↪";n 76 font-weight: 400;n 77}n 78n 79.search-results .sr-snippet-col {n 80 flex: 1 1 60%;n 81}n 82n 83.search-results .sr-snippet {n 84 color: var(--secondary);n 85 font-size: 0.95rem;n 86 line-height: 1.5;n 87n 88 overflow: hidden;n 89 display: -webkit-box;n 90 -webkit-line-clamp: 3;n 91 -webkit-box-orient: vertical;n 92}n 93n 94@media (max-width: 500px) {n 95 .search-results li {n 96 flex-direction: column;n 97 gap: 8px;n 98 }n 99n100 .search-results .sr-title-col {n101 max-width: none;n102 }n103}