AstroとPagefindでサイト内検索を実装する
当ブログも記事が増えてきた。GitHub上で検索できるけどより簡単な方法を提供するためにPagefindを導入した。
Pagefindとは
Pagefindは、クライアントで動作するStaticな検索ライブラリ。
Astroのような静的サイトジェネレーター(SSG)との相性がよく、ページをビルドしたあとにインデックスを生成し、クライアントでそれを使って検索を行う。
Pagefindを導入する
Pagefindをインストールする
$ npm install -D pagefind
{
"scripts": {
"build": "astro check && astro build",
"postbuild": "pagefind --site dist"
}
}
インデックスを生成する
$ npm run build
> astro check && astro build
> pagefind --site dist
astro buildで生成されたファイルはdistディレクトリに出力される。pagefindではその出力されたファイルを参照するためpagefind --site distのように指定する。
pagefindを実行すると、インデックスやクライアントで利用するJavaScriptやCSSファイルなどが生成される。
.
├── dist
│ ├── index.html
│ ├── pagefind
│ │ ├── pagefind.js
│ │ ├── pagefind-ui.js // 今回は使わない
│ │ ├── pagefind-ui.css // 今回は使わない
│ │ ├── ...
│ │ └── index/...
│ └── ...
└── src
私のブログでは、検索UIを自分で実装したのでpagefind-ui.jsなどは使わない。
検索用コンポーネントを実装する
pagefindが生成したインデックスを使って検索するために、検索用コンポーネントを実装する。
// src/components/Search.astro
<search>
<button popovertarget="dialog">検索する</button>
<dialog id="dialog" popover>
<form>
<label for="input">検索キーワード</label>
<input type="search" id="input" placeholder="..." />
</form>
<div id="results"></div>
</search>
// クライアントで実行するため`is:inline`をつける
<script is:inline>
document.addEventListener('DOMContentLoaded', async () => {
// distに出力されたpagefind.jsを読み込む
const pagefind = await import('/path/to/pagefind/pagefind.js');
const input = document.getElementById('input');
const result = document.getElementById('results');
input.addEventListener('input', async (e) => {
const query = e.target.value.trim()
const searched = await pagefind.search(query);
const data = await Promise.all(
searched.results.map((r) => r.data()),
);
// WARN: setHTML()は2026年3月現在ではFirefoxのみ利用可能
// innsetHTMLを使う場合は、XSS攻撃に注意してください
result.setHTML(
data.map(d => {
return `
<div>
<a href="${d.url}">${d.meta.title}</a>
<p>${d.excerpt}</p>
</div>
`
}).join('')
)
});
})
</script>
ローカルで確認する
ローカルで検索する場合はページ全体をビルドし、インデックスを作成してからでないと利用できない。
$ npm run build
> astro check && astro build
> pagefind --site dist
$ npm run preview
検索対象に含める、除外する
data-*属性を使い、pagefindでインデックスするかどうか判定している。
検索結果に含める場合はdata-pagefind-body属性、除外する場合はdata-pagefind-ignore属性をつける。
<html>
<body>
<header>...</header>
<aside>...</aside>
<main data-pagefind-body>
<!-- ここに含まれるコンテンツが検索対象になる -->
<section id="demo" data-pagefind-ignore>
<!-- ここに含まれるコンテンツは検索対象から除外される -->
</section>
</main>
<footer>...</footer>
</body>
</html>
デモ
当サイト上部にある「検索する」ボタンをクリックして操作してください。