在 Eleventy(11ty)整合 Pagefind:回到「最簡單」的做法
在把網站從 WordPress 搬到 11ty 之後,一直在研究一件事情,就是如果沒有了後端程式,也沒有資料庫,該怎麼做站內搜尋,Google Programmable Search Engine 也可以,但就走走看看有沒有別的選擇。
參考其他的 11ty 網站發現還蠻多人在用 Pagefind,而且這個工具是任何網頁架構都可以使用,不是只支持 11ty。
Pagefind 是為「靜態網站」設計的全文搜尋工具,不需要後端、不需要資料庫,搜尋全在前端完成。
而且老實說,Pagefind 本身真的不難。 難的是:一開始我完全用錯方式在理解它。
目錄
一開始的誤會:把 Pagefind 當成「一般前端套件」
最一開始,我直覺用熟悉的方式來:
pnpm install pagefind然後開始想:
- CSS 要怎麼 include?
- JS 要不要丟進 Vite?
- 要不要跟 11ty build 流程整合?
- 能不能 build 完 HTML 就自動跑 Pagefind?
於是開始把 Pagefind 拉進前端打包流程、寫 script、試著「一次 build 全部搞定」。
結果就是: 流程越來越複雜、錯誤越來越難 debug。
到這裡才意識到一件事——
Pagefind 不是前端套件,它是「建索引的工具」。
所以我又執行了
pnpm remove pagefind換個想法:Pagefind 只需要做一件事就好
我完全換了一個角度:
11ty 就負責把 HTML 生出來 Pagefind 只負責「讀這些 HTML,建立搜尋索引」
只要守住這個界線,事情瞬間變得很單純。
Step 1:直接 include Pagefind 產生的 CSS / JS
Pagefind 在建索引時,會自動產生 /pagefind/ 資料夾,裡面就包含 UI 所需的檔案。
所以什麼 bundler 都不要碰,直接 include:
<link href="/pagefind/pagefind-ui.css" rel="stylesheet">
<script src="/pagefind/pagefind-ui.js"></script>Step 2:放一個搜尋欄位容器
<div id="search"></div>Step 3:標記「真正要被搜尋的內容」
這一步非常重要,也是 Pagefind 最好用的地方之一。
記得在文章內容外層加上:
<div data-pagefind-body>
{{ content | safe }}
</div>這代表:
- Pagefind 只會索引這個區塊內的文字
- header、footer、選單、側邊欄都不會被吃進搜尋結果
- 搜尋結果乾淨很多
(也要注意:一旦用了 data-pagefind-body,沒有加這個 attribute 的頁面就不會被索引,這通常是好事。)
Step 4:初始化 PagefindUI
<script>
window.addEventListener("DOMContentLoaded", () => {
new PagefindUI({
element: "#search",
showSubResults: false,
});
});
</script>到這一步,前端其實就已經準備好了。
Step 5:建索引(這行才是 Pagefind 的本體)
pnpm dlx pagefind --site _site --glob "**/*.html"重點只有兩個:
- _site:11ty build 出來的資料夾
- **/*.html:全部 HTML 都拿來建索引
跑完之後,你會看到 _site/pagefind/ 被建立起來。
Step 6:收進 package.json
build 11ty site 和建立搜尋索引拆開:
{
"scripts": {
"build": "NODE_ENV=production ELEVENTY_PROFILE=true pnpm exec eleventy",
"build:pagefind": "pnpm dlx pagefind --site _site --glob \"**/*.html\""
}
}為什麼?
因為這樣:
- 出問題很好 debug
- 不會搞混是 11ty 還是 Pagefind 壞掉
- 想自動化,之後再加一行 pnpm build && pnpm build:pagefind 就好
最後:上傳
只要你的部署流程會把 _site/pagefind/ 一起上傳,搜尋就能直接在純靜態網站上跑起來。
不用 server、不用 API、不用資料庫。
結束了嗎,對啊,就醬。
進階補充
Pagefind × Barba.js 切頁時要注意的事
如果你跟我一樣,網站有用 Barba.js 做 PJAX / page transition,那你幾乎一定會踩到這個坑。
問題是什麼?
PagefindUI 預設會在:DOMContentLoaded 時初始化。
但 Barba 切頁時:
- 不會重新載入整個頁面
- #search 這個 DOM 可能被重建
- PagefindUI 早就綁在舊的 DOM 上了
結果就是:
- 搜尋框還在
- 但點了沒反應,或結果怪怪的
最穩的做法:切頁後重新初始化
概念只有一句話:
只要搜尋欄位被重新 render,就重建 PagefindUI
範例(Barba hooks):
let pagefindInstance = null;
function initPagefind() {
const searchEl = document.querySelector("#search");
if (!searchEl) return;
// 避免重複初始化
searchEl.innerHTML = "";
pagefindInstance = new PagefindUI({
element: "#search",
showSubResults: false,
});
}
window.addEventListener("DOMContentLoaded", initPagefind);
barba.hooks.afterEnter(() => {
initPagefind();
});
這樣做的好處:
- 不管是不是 PJAX 切頁
- 只要 #search 出現,就一定能用
- 不會卡在奇怪的「第一次正常,之後失效」
哪些頁面「不該被搜尋」
Pagefind 很貼心的一點是:你可以選擇性讓頁面完全不進索引。
情境舉例
- 標籤列表頁
- 分頁頁面(page/2、page/3)
- 純導覽頁、實驗頁
- 404 / redirect 頁
作法一:根本不加 data-pagefind-body
只有「文章內容」才包:
<div data-pagefind-body>
{{ content | safe }}
</div>其他頁面自然就被排除。
作法二:明確標記「不要索引」
如果你有某些頁面「結構很像文章,但不該被搜」,可以加:
<body data-pagefind-ignore>或包在區塊上:
<div data-pagefind-ignore>
...
</div>這在做實驗頁、A/B test、或 draft 頁面時很有用。
Pagefind 的搜尋品質,其實取決於你的 HTML 結構
Pagefind 不是魔法,它完全相信你的 HTML。
所以這幾件事,會直接影響搜尋結果好不好用:
1️⃣ 標題階層很重要
<h1>文章標題</h1>
<h2>段落標題</h2>
<h3>小節</h3>Pagefind 會用這些來:
- 判斷內容結構
- 顯示搜尋結果摘要
亂用 div + class,搜尋結果就會很扁平。
2️⃣ 不要把整篇文章塞進奇怪的 wrapper
例如:
<div class="prose">
<article>
<section>
<div>
{{ content }}
</div>
</section>
</article>
</div>這不是不行,但層級越乾淨,搜尋摘要越好看。
3️⃣ Code block 其實會被索引(而且有時很棒)
這點對技術部落格來說是加分:
- 指令
- API 名稱
- 錯誤訊息
都能被搜到。
如果你真的不想讓 code block 被搜,再局部加 data-pagefind-ignore 即可。
結語
Pagefind 不複雜,複雜的是我們一開始就想把它變得很複雜,難的是「想一次做到太完美」
對我來說,現在這套流程剛剛好:
- 好理解
- 好 debug
- 出問題也知道在哪一段
回頭看,其實整個流程只有一句話:
HTML 先 build 好,Pagefind 再來掃。
一開始是想把它「工程化」、自動化、整合到所有流程裡; 但對假設網站這件事來說,簡單、穩定,才是最重要的。不然就是回復備份流程做得快又方便也是 OK 的啦。
(靜態網站不管是備份還是回復舊版本都超方便的啦,一點都不害怕弄壞了什麼)



