Fragments of verbose memory

冗長な記憶の断片 - Web技術のメモをほぼ毎日更新

Feb 2, 2026 - 日記

Googleのsite:検索をやめて: 静的サイトにPagefindを導入

Googleのsite:検索をやめて: 静的サイトにPagefindを導入

このブログのサイト内検索を、Google検索(site:)からPagefind に移行しました。

Hugoで生成したpublic/をビルド後にPagefindでインデックス化し、静的サイト内で検索が完結する構成にします。

なぜGoogleのsite:検索が当てにならなくなったのか

このブログはHugo で生成した静的サイトで、Cloudflare Pages でホスティングしています。以前はナビゲーションバーにGoogleの検索フォームを設置して、site:blog.tumf.devでサイト内検索っぽく使っていました。

1
2
3
4
<form action="https://www.google.com/search" method="get">
  <input type="search" name="q" placeholder="Search" />
  <input type="hidden" name="as_sitesearch" value="blog.tumf.dev" />
</form>

この仕組みは「Googleが自サイトをインデックスしている」ことが前提です。インデックス状況に左右されると、検索結果が空になることがあります。

静的サイト検索の選択肢

Googleに依存しない検索方法を調べたところ、主に以下の選択肢がありました:

方法特徴日本語対応コスト
Algolia高機能・高速✅ 良好有料(無料枠あり)
Lunr.js軽量・クライアント検索⚠️ 弱い無料
Fuse.jsファジー検索✅ 良好無料
Pagefind静的・低帯域✅ 対応無料

個人ブログで有料のAlgoliaは避けたい。Lunr.jsは日本語の分かち書き(単語分割)が弱い。Fuse.jsは良さそうだが、Hugoとの統合例が少ない。

最終的にPagefindを選んだ理由は以下の通りです:

  1. 完全静的 - サーバー不要、Cloudflare Pagesと相性が良い
  2. 低帯域 - 検索時に必要なチャンクだけを読む設計
  3. 日本語対応 - CJK言語の分かち書きに対応
  4. 導入例が多い - 静的サイトでの採用例が多く、情報が見つけやすい

Pagefindの仕組み

Pagefindは「ビルド後にインデックスを生成する」アプローチを取ります。

1
2
3
4
5
# 1. Hugoでサイトをビルド
hugo

# 2. Pagefindでインデックス生成
npx -y pagefind --site public

生成されたインデックスはpublic/pagefind/ディレクトリに配置され、検索時にJavaScriptで読み込まれます。

インデックスサイズの最適化

Pagefindの特徴は「検索時に必要な部分だけ読み込む」点です。全文検索インデックスを複数のチャンクに分割し、検索クエリに応じて必要なチャンクのみをダウンロードします。

このブログ(約135記事)の場合:

Indexed 135 pages
Indexed 10,376 words
Created 27 index chunks

27個のチャンクに分割され、検索時に必要なチャンクだけが読み込まれるため、検索のために全インデックスをまとめてダウンロードせずに済みます。

導入手順

1. package.jsonの作成(任意)

Pagefindはnpmパッケージとして提供されているため、継続運用するならpackage.jsonに入れておくと楽です。毎回npxで実行するだけなら、このステップは省略できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{
  "name": "blog-tumf-dev",
  "scripts": {
    "build": "hugo && npx pagefind --site public",
    "serve": "hugo server -D"
  },
  "devDependencies": {
    "pagefind": "^1.4.0"
  }
}

2. HTML要素にdata属性を追加

Pagefindはdata-pagefind-body属性で検索対象を指定します。記事テンプレート(layouts/_default/single.html)を修正:

1
2
3
<article class="hentry" role="article" data-pagefind-body>
  <!-- 記事本文 -->
</article>

記事タイトルにはdata-pagefind-meta="title"を追加(layouts/partials/post_header.html):

1
2
3
<h1 class="entry-title" data-pagefind-meta="title">
  <a href="{{ .Permalink }}">{{ .Title }}</a>
</h1>

3. 検索UIの追加

Pagefindは検索UIをプリビルドで提供しています。ナビゲーションバーに検索ボックスを追加(layouts/partials/pagefind-search.html):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
<form class="pagefind-form">
  <fieldset role="search">
    <div id="pagefind-search"></div>
  </fieldset>
</form>
<link href="/pagefind/pagefind-ui.css" rel="stylesheet">
<script src="/pagefind/pagefind-ui.js"></script>
<script>
  window.addEventListener('DOMContentLoaded', function() {
    new PagefindUI({
      element: "#pagefind-search",
      showSubResults: false,
      showImages: false,
      excerptLength: 20,
      translations: {
        placeholder: "Search",
        zero_results: "「[SEARCH_TERM]」に一致する記事はありません"
      }
    });
  });
</script>

4. インデックス対象の制限

デフォルトでは全HTMLファイルがインデックスされますが、下書きや一覧ページは除外したいため、pagefind.ymlで設定:

1
2
site: public
glob: "posts/**/*.html"

これでposts/配下の記事のみがインデックスされます。

5. 未来日付の記事を除外

Hugoはデフォルトで未来日付や下書きをビルドしませんが、CI/CDの設定やビルドフラグで含めている場合は、明示的に無効化しておくと安心です。

1
2
buildFuture = false
buildDrafts = false

6. Cloudflare Pagesのビルド設定

Cloudflare Pagesのビルドコマンドを以下に変更:

1
npm run build

または

1
hugo && npx -y pagefind --site public

日本語検索の制限と対策

Pagefindは日本語の分かち書き(単語分割)に対応していますが、いくつか制限があります。

制限1: ステミング未対応

英語では「running」を検索すると「run」もヒットしますが、日本語では「走る」「走った」「走っている」を個別に検索する必要があります。

制限2: 検索語によっては工夫が必要

インデックス時に分かち書きされるため、検索語によってはスペース区切りにするとヒットしやすくなる場合があります。

対策: 検索ヒント

検索ボックスのプレースホルダーを「Search」にして、日本語でも英語でも検索できることを示しています。また、検索結果が0件の場合は「スペース区切りで試してください」といったヒントを表示することも検討できます。

デザインの調整

Pagefind UIはデフォルトでモダンなデザインですが、既存のテーマに合わせるためCSSをカスタマイズしました。

元のGoogle検索フォームのスタイルを踏襲

元のテーマ(Octopress)では、検索ボックスは以下のスタイルでした:

1
2
3
4
5
6
7
body > nav form .search {
  padding: 0.3em 0.5em 0;
  font-size: 0.85em;
  border-radius: 0.5em;
  background-color: #f2f2f2;
  border: 1px solid #b3b3b3;
}

Pagefind UIのスタイルを上書きして、同じ見た目に調整:

1
2
3
4
5
6
7
.pagefind-ui .pagefind-ui__search-input {
  padding: 0.3em 0.5em !important;
  font-size: 0.85em !important;
  border-radius: 0.5em !important;
  background-color: #f2f2f2 !important;
  border: 1px solid #b3b3b3 !important;
}

検索結果のドロップダウン

検索結果はナビゲーションバーの下にドロップダウンで表示されるようにしました:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
.pagefind-ui .pagefind-ui__drawer {
  position: absolute !important;
  top: 100% !important;
  right: 0 !important;
  z-index: 1000 !important;
  background: #fff !important;
  border: 1px solid #ddd !important;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important;
  max-height: 400px !important;
  overflow-y: auto !important;
  width: 350px !important;
}

まとめ

Google検索(site:)からPagefindへの移行は、想像以上にスムーズでした。

良かった点:

  • 完全静的なので、Googleのインデックス状況に依存しない
  • 低帯域で高速(検索時に必要なチャンクだけ読み込む)
  • 日本語検索が実用レベル
  • デザインのカスタマイズが容易

注意点:

  • 日本語のステミングは未対応
  • 検索語によってはスペース区切りにするとヒットしやすい場合がある
  • ビルド時にインデックス生成が必要(ビルド時間が若干増加)

個人ブログや技術ドキュメントサイトで「Googleに頼らない検索」を実現したい方には、Pagefindは良い選択肢だと思います。

参考リンク