CleanMyMac

在 Eleventy(11ty)整合 Pagefind:回到「最簡單」的做法

AI 畫的圖太強了吧, 又讚嘆了一次

在把網站從 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 的啦。

(靜態網站不管是備份還是回復舊版本都超方便的啦,一點都不害怕弄壞了什麼)